Skip to content

Commit

Permalink
fix: new import.meta implement by ponyfill
Browse files Browse the repository at this point in the history
in commonjs, import.meta.resolve use nodejs buildin require.resolve
in esmodule, import.meta.resolve use nodejs buildin import.meta.resolve or createRequest(filename).resolve
add import.meta.filename / import.meta.dirname support
  • Loading branch information
Gaubee committed Sep 23, 2024
1 parent dbf13c1 commit 628e83e
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 159 deletions.
1 change: 1 addition & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"rs-lib/src/polyfills/scripts/",
"tests/declaration_import_project/npm",
"tests/import_map_project/npm",
"tests/import_meta_project/npm",
"tests/json_module_project/npm",
"tests/module_mappings_project/npm",
"tests/node_types_project/npm",
Expand Down
48 changes: 29 additions & 19 deletions lib/compiler_transforms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,69 @@ import { assertEquals } from "@std/assert";
import { ts } from "@ts-morph/bootstrap";
import { transformImportMeta } from "./compiler_transforms.ts";

function testImportReplacements(input: string, output: string, cjs = true) {
function testImportReplacements(
input: string,
output: string,
module: ts.ModuleKind,
) {
const sourceFile = ts.createSourceFile(
"file.ts",
input,
ts.ScriptTarget.Latest,
);
const newSourceFile = ts.transform(sourceFile, [transformImportMeta], {
module: cjs ? ts.ModuleKind.CommonJS : ts.ModuleKind.ES2015,
module,
}).transformed[0];
const text = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
}).printFile(newSourceFile);

assertEquals(text, output);
}
const testImportReplacementsEsm = (input: string, output: string) =>
testImportReplacements(input, output, ts.ModuleKind.ES2015);
const testImportReplacementsCjs = (input: string, output: string) =>
testImportReplacements(input, output, ts.ModuleKind.CommonJS);

Deno.test("transform import.meta.url expressions", () => {
testImportReplacements(
Deno.test("transform import.meta.url expressions in commonjs", () => {
testImportReplacementsCjs(
"function test() { new URL(import.meta.url); }",
`function test() { new URL(require("url").pathToFileURL(__filename).href); }\n`,
`function test() { new URL(globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).url); }\n`,
);
});
Deno.test("transform import.meta.url expressions in esModule", () => {
testImportReplacementsEsm(
"function test() { new URL(import.meta.url); }",
`function test() { new URL(globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).url); }\n`,
);
});

Deno.test("transform import.meta.main expressions", () => {
testImportReplacements(
Deno.test("transform import.meta.main expressions in commonjs", () => {
testImportReplacementsCjs(
"if (import.meta.main) { console.log('main'); }",
`if ((require.main === module)) {
`if (globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).main) {
console.log("main");
}\n`,
);
});

Deno.test("transform import.meta.main expressions in esModule", () => {
testImportReplacements(
"if (import.meta.main) { console.log('main'); }",
`if ((import.meta.url === ("file:///" + process.argv[1].replace(/\\\\/g, "/")).replace(/\\/{3,}/, "///"))) {
console.log("main");
}\n`,
false,
testImportReplacementsEsm(
"export const isMain = import.meta.main;",
`export const isMain = globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).main;\n`,
);
});

Deno.test("transform import.meta.resolve expressions", () => {
testImportReplacements(
testImportReplacementsCjs(
"function test(specifier) { import.meta.resolve(specifier); }",
`function test(specifier) { new URL(specifier, require("url").pathToFileURL(__filename).href).href; }\n`,
`function test(specifier) { globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).resolve(specifier); }\n`,
);
});

Deno.test("transform import.meta.resolve expressions in esModule", () => {
testImportReplacements(
testImportReplacementsEsm(
"function test(specifier) { import.meta.resolve(specifier); }",
`function test(specifier) { new URL(specifier, import.meta.url).href; }\n`,
false,
`function test(specifier) { globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).resolve(specifier); }\n`,
);
});
157 changes: 31 additions & 126 deletions lib/compiler_transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,147 +14,52 @@ export const transformImportMeta: ts.TransformerFactory<ts.SourceFile> = (
return (sourceFile) => ts.visitEachChild(sourceFile, visitNode, context);

function visitNode(node: ts.Node): ts.Node {
// find `import.meta.resolve`
if (
ts.isCallExpression(node) &&
node.arguments.length === 1 &&
isImportMetaProp(node.expression) &&
node.expression.name.escapedText === "resolve"
) {
return ts.visitEachChild(
getReplacementImportMetaResolve(node.arguments),
visitNode,
context,
);
} else if (isImportMetaProp(node)) {
// find `import.meta.url` or `import.meta.main`
if (node.name.escapedText === "url" && isScriptModule) {
return getReplacementImportMetaUrl();
} else if (node.name.escapedText === "main") {
if (isScriptModule) {
return getReplacementImportMetaMainScript();
} else {
return getReplacementImportMetaMainEsm();
}
// find `import.meta`
if (ts.isMetaProperty(node)) {
if (isScriptModule) {
return getReplacementImportMetaScript();
} else {
return getReplacementImportMetaEsm();
}
}

return ts.visitEachChild(node, visitNode, context);
}

function isImportMetaProp(
node: ts.Node,
): node is ts.PropertyAccessExpression & { name: ts.Identifier } {
return ts.isPropertyAccessExpression(node) &&
ts.isMetaProperty(node.expression) &&
node.expression.keywordToken === ts.SyntaxKind.ImportKeyword &&
ts.isIdentifier(node.name);
}

function getReplacementImportMetaUrl() {
// Copy and pasted from ts-ast-viewer.com
// require("url").pathToFileURL(__filename).href
return factory.createPropertyAccessExpression(
factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createCallExpression(
factory.createIdentifier("require"),
undefined,
[factory.createStringLiteral("url")],
),
factory.createIdentifier("pathToFileURL"),
),
undefined,
[factory.createIdentifier("__filename")],
),
factory.createIdentifier("href"),
);
}

function getReplacementImportMetaMainScript() {
function getReplacementImportMeta(
symbolFor: string,
argumentsArray: readonly ts.Expression[],
) {
// Copy and pasted from ts-ast-viewer.com
// (require.main === module)
return factory.createParenthesizedExpression(factory.createBinaryExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier("require"),
factory.createIdentifier("main"),
),
factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
factory.createIdentifier("module"),
));
}

function getReplacementImportMetaMainEsm() {
// Copy and pasted from ts-ast-viewer.com
// (import.meta.url === ('file:///'+process.argv[1].replace(/\\/g,'/')).replace(/\/{3,}/,'///'));
// 1. `process.argv[1]` is fullpath;
// 2. Win's path is `E:\path\to\main.mjs`, replace to `E:/path/to/main.mjs`
return factory.createParenthesizedExpression(
factory.createBinaryExpression(
factory.createPropertyAccessExpression(
factory.createMetaProperty(
ts.SyntaxKind.ImportKeyword,
factory.createIdentifier("meta"),
),
factory.createIdentifier("url"),
),
factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),
// globalThis[Symbol.for('import-meta-ponyfill')](...args)
return factory.createCallExpression(
factory.createElementAccessExpression(
factory.createIdentifier("globalThis"),
factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createParenthesizedExpression(
factory.createBinaryExpression(
factory.createStringLiteral("file:///"),
factory.createToken(ts.SyntaxKind.PlusToken),
factory.createCallExpression(
factory.createPropertyAccessExpression(
factory.createElementAccessExpression(
factory.createPropertyAccessExpression(
factory.createIdentifier("process"),
factory.createIdentifier("argv"),
),
factory.createNumericLiteral("1"),
),
factory.createIdentifier("replace"),
),
undefined,
[
factory.createRegularExpressionLiteral("/\\\\/g"),
factory.createStringLiteral("/"),
],
),
),
),
factory.createIdentifier("replace"),
factory.createIdentifier("Symbol"),
factory.createIdentifier("for"),
),
undefined,
[
factory.createRegularExpressionLiteral("/\\/{3,}/"),
factory.createStringLiteral("///"),
],
[factory.createStringLiteral(symbolFor)],
),
),
undefined,
argumentsArray,
);
}

function getReplacementImportMetaResolve(args: ts.NodeArray<ts.Expression>) {
// Copy and pasted from ts-ast-viewer.com
// new URL(specifier, import.meta.url).href
return factory.createPropertyAccessExpression(
factory.createNewExpression(
factory.createIdentifier("URL"),
undefined,
[
...args,
factory.createPropertyAccessExpression(
factory.createMetaProperty(
ts.SyntaxKind.ImportKeyword,
factory.createIdentifier("meta"),
),
factory.createIdentifier("url"),
),
],
function getReplacementImportMetaScript() {
return getReplacementImportMeta("import-meta-ponyfill-commonjs", [
factory.createIdentifier("require"),
factory.createIdentifier("module"),
]);
}
function getReplacementImportMetaEsm() {
return getReplacementImportMeta("import-meta-ponyfill-esmodule", [
factory.createMetaProperty(
ts.SyntaxKind.ImportKeyword,
factory.createIdentifier("meta"),
),
factory.createIdentifier("href"),
);
]);
}
};
8 changes: 2 additions & 6 deletions rs-lib/src/polyfills/import_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use deno_ast::view::Expr;
use deno_ast::view::Node;
use deno_ast::SourceRanged;

use super::Polyfill;
use super::PolyfillVisitContext;
Expand All @@ -15,13 +14,10 @@ impl Polyfill for ImportMetaPolyfill {
true
}

fn visit_node(&self, node: Node, context: &PolyfillVisitContext) -> bool {
fn visit_node(&self, node: Node, _context: &PolyfillVisitContext) -> bool {
if let Node::MemberExpr(expr) = node {
if let Expr::MetaProp(_meta) = expr.obj {
let text = expr.prop.text_fast(context.program);
if text == "main" || text == "resolve" {
return true;
}
return true;
}
}
false
Expand Down
Loading

0 comments on commit 628e83e

Please sign in to comment.