2024-11-03 17:41:45 +01:00

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;
}
}