131 lines
3.3 KiB
JavaScript
131 lines
3.3 KiB
JavaScript
import * as acorn from "acorn";
|
|
|
|
export class ImportTransformer {
|
|
constructor(input, ast) {
|
|
this.parse(input, ast);
|
|
}
|
|
|
|
parse(input, ast) {
|
|
if(!input) {
|
|
throw new Error("Missing input to ImportTransformer, received: " + input)
|
|
}
|
|
|
|
this.originalSource = input;
|
|
|
|
if(ast) {
|
|
this.ast = ast;
|
|
} else {
|
|
this.ast = acorn.parse(input, {
|
|
sourceType: "module",
|
|
ecmaVersion: "latest"
|
|
});
|
|
}
|
|
}
|
|
|
|
static transformImportSource(str, sourceNode, indexOffset = 0, importMap = {}) {
|
|
let { start, end, value } = sourceNode;
|
|
// Could be improved by https://www.npmjs.com/package/@import-maps/resolve
|
|
let resolved = importMap?.imports && importMap?.imports[value];
|
|
if(resolved) {
|
|
return {
|
|
code: str.slice(0, start + 1 + indexOffset) + resolved + str.slice(end - 1 + indexOffset),
|
|
offset: resolved.length - value.length,
|
|
};
|
|
}
|
|
return {
|
|
code: str,
|
|
offset: 0
|
|
};
|
|
}
|
|
|
|
static transformImportCode(prefix, str, node, specifiers, sourceNode, indexOffset = 0) {
|
|
let { start, end } = node;
|
|
start += indexOffset;
|
|
end += indexOffset;
|
|
|
|
let { raw: rawSourceValue } = sourceNode;
|
|
|
|
let importDeclaration = str.slice(start, end - 1);
|
|
|
|
let specifierIndexes = [];
|
|
if(importDeclaration.startsWith("import ")) {
|
|
specifierIndexes[0] = "import ".length + start;
|
|
} else {
|
|
throw new Error(`Could not find \`import\` in import declaration: ${importDeclaration}`);
|
|
}
|
|
|
|
specifierIndexes[1] = specifiers[specifiers.length - 1].end + indexOffset;
|
|
|
|
// normalize away trailing } on import { a, b, c } specifiers
|
|
let split = str.slice(specifierIndexes[1]).split(" from ");
|
|
if(split.length > 0) {
|
|
specifierIndexes[1] += split[0].length;
|
|
}
|
|
|
|
let newImportString = `const ${str.slice(specifierIndexes[0], specifierIndexes[1])} = ${prefix}(${rawSourceValue})`;
|
|
let returnedCode = str.slice(0, start) + newImportString + str.slice(end - 1);
|
|
|
|
return {
|
|
code: returnedCode,
|
|
offset: returnedCode.length - str.length,
|
|
};
|
|
}
|
|
|
|
_transform(prefix) {
|
|
let input = this.originalSource;
|
|
let indexOffset = 0;
|
|
for(let node of this.ast.body) {
|
|
if(node.type === "ImportDeclaration") {
|
|
let ret = ImportTransformer.transformImportCode(prefix, input, node, node.specifiers, node.source, indexOffset)
|
|
input = ret.code;
|
|
indexOffset += ret.offset;
|
|
}
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
transformToDynamicImport() {
|
|
return this._transform("await import");
|
|
}
|
|
|
|
transformToRequire() {
|
|
return this._transform("require");
|
|
}
|
|
|
|
// alias for backwards compat
|
|
transform(...args) {
|
|
return this.transformWithImportMap(...args);
|
|
}
|
|
|
|
transformWithImportMap(importMap) {
|
|
if(!importMap) {
|
|
return this.originalSource;
|
|
}
|
|
|
|
let input = this.originalSource;
|
|
let indexOffset = 0;
|
|
for(let node of this.ast.body) {
|
|
if(node.type === "ImportDeclaration") {
|
|
if(importMap?.imports) {
|
|
let ret = ImportTransformer.transformImportSource(input, node.source, indexOffset, importMap);
|
|
input = ret.code;
|
|
indexOffset += ret.offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
hasImports() {
|
|
for(let node of this.ast.body) {
|
|
if(node.type === "ImportDeclaration") {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|