Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add transform for import.meta.resolve/main expressions #322

Merged
merged 7 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions lib/compiler_transforms.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { assertEquals } from "./test.deps.ts";
import { ts } from "./mod.deps.ts";
import { transformImportMeta } from "./compiler_transforms.ts";

function testImportReplacements(input: string, output: string) {
function testImportReplacements(input: string, output: string, cjs = true) {
const sourceFile = ts.createSourceFile(
"file.ts",
input,
ts.ScriptTarget.Latest,
);
const newSourceFile =
ts.transform(sourceFile, [transformImportMeta]).transformed[0];
const newSourceFile = ts.transform(sourceFile, [transformImportMeta], {
module: cjs ? ts.ModuleKind.CommonJS : ts.ModuleKind.ES2015,
}).transformed[0];
const text = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
}).printFile(newSourceFile);
Expand All @@ -34,3 +35,28 @@ Deno.test("transform import.meta.main expressions", () => {
}\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,
);
});

Deno.test("transform import.meta.resolve expressions", () => {
testImportReplacements(
"function test(specifier) { import.meta.resolve(specifier); }",
`function test(specifier) { new URL(specifier, require("url").pathToFileURL(__filename).href).href; }\n`,
);
});

Deno.test("transform import.meta.resolve expressions in esModule", () => {
testImportReplacements(
"function test(specifier) { import.meta.resolve(specifier); }",
`function test(specifier) { new URL(specifier, import.meta.url).href; }\n`,
false,
);
});
113 changes: 105 additions & 8 deletions lib/compiler_transforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,50 @@ export const transformImportMeta: ts.TransformerFactory<ts.SourceFile> = (
context,
) => {
const factory = context.factory;
const compilerModule = context.getCompilerOptions().module;
const isScriptModule = compilerModule === ts.ModuleKind.CommonJS ||
compilerModule === ts.ModuleKind.UMD;

return (sourceFile) => ts.visitEachChild(sourceFile, visitNode, context);

function visitNode(node: ts.Node): ts.Node {
// find `import.meta.url`
// find `import.meta.resolve`
if (
ts.isPropertyAccessExpression(node) &&
ts.isMetaProperty(node.expression) &&
node.expression.keywordToken === ts.SyntaxKind.ImportKeyword &&
ts.isIdentifier(node.name)
ts.isCallExpression(node) &&
node.arguments.length === 1 &&
isImportMetaProp(node.expression) &&
node.expression.name.escapedText === "resolve"
) {
if (node.name.escapedText === "url") {
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") {
return getReplacementImportMetaMain();
if (isScriptModule) {
return getReplacementImportMetaMainScript();
} else {
return getReplacementImportMetaMainEsm();
}
}
}

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
Expand All @@ -48,7 +71,7 @@ export const transformImportMeta: ts.TransformerFactory<ts.SourceFile> = (
);
}

function getReplacementImportMetaMain() {
function getReplacementImportMetaMainScript() {
// Copy and pasted from ts-ast-viewer.com
// (require.main === module)
return factory.createParenthesizedExpression(factory.createBinaryExpression(
Expand All @@ -60,4 +83,78 @@ export const transformImportMeta: ts.TransformerFactory<ts.SourceFile> = (
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),
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"),
),
undefined,
[
factory.createRegularExpressionLiteral("/\\/{3,}/"),
factory.createStringLiteral("///"),
],
),
),
);
}

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"),
),
],
),
factory.createIdentifier("href"),
);
}
};
4 changes: 2 additions & 2 deletions lib/pkg/dnt_wasm.generated.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ function makeMutClosure(arg0, arg1, dtor, f) {
}
function __wbg_adapter_38(arg0, arg1, arg2) {
wasm
._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h3790686e09592098(
._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h49cfec08c8849794(
arg0,
arg1,
addHeapObject(arg2),
Expand All @@ -253,7 +253,7 @@ function handleError(f, args) {
}
}
function __wbg_adapter_74(arg0, arg1, arg2, arg3) {
wasm.wasm_bindgen__convert__closures__invoke2_mut__ha93d7b4a3d10eaee(
wasm.wasm_bindgen__convert__closures__invoke2_mut__h242b2886b40ed1f9(
arg0,
arg1,
addHeapObject(arg2),
Expand Down
Binary file modified lib/pkg/dnt_wasm_bg.wasm
Binary file not shown.
6 changes: 5 additions & 1 deletion mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,11 @@ export async function build(options: BuildOptions): Promise<void> {
outDir: esmOutDir,
});
program = project.createProgram();
emit();
emit({
transformers: {
before: [compilerTransforms.transformImportMeta],
},
});
writeFile(
path.join(esmOutDir, "package.json"),
`{\n "type": "module"\n}\n`,
Expand Down
33 changes: 33 additions & 0 deletions rs-lib/src/polyfills/import_meta.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

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

use super::Polyfill;
use super::PolyfillVisitContext;
use crate::ScriptTarget;

pub struct ImportMetaPolyfill;

impl Polyfill for ImportMetaPolyfill {
fn use_for_target(&self, _target: ScriptTarget) -> bool {
true
}

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

fn get_file_text(&self) -> &'static str {
include_str!("./scripts/deno.import-meta.ts")
}
}
2 changes: 2 additions & 0 deletions rs-lib/src/polyfills/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::ScriptTarget;

mod array_find_last;
mod error_cause;
mod import_meta;
mod object_has_own;
mod string_replace_all;

Expand Down Expand Up @@ -42,6 +43,7 @@ fn all_polyfills() -> Vec<Box<dyn Polyfill>> {
Box::new(error_cause::ErrorCausePolyfill),
Box::new(string_replace_all::StringReplaceAllPolyfill),
Box::new(array_find_last::ArrayFindLastPolyfill),
Box::new(import_meta::ImportMetaPolyfill),
]
}

Expand Down
27 changes: 27 additions & 0 deletions rs-lib/src/polyfills/scripts/deno.import-meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
declare global {
interface ImportMeta {
/** A flag that indicates if the current module is the main module that was
* called when starting the program under Deno.
*
* ```ts
* if (import.meta.main) {
* // this was loaded as the main module, maybe do some bootstrapping
* }
* ```
*/
main: boolean;

/** A function that returns resolved specifier as if it would be imported
* using `import(specifier)`.
*
* ```ts
* console.log(import.meta.resolve("./foo.js"));
* // file:///dev/foo.js
* ```
*/
// @ts-ignore override
resolve(specifier: string): string;
}
}

export {}
3 changes: 3 additions & 0 deletions rs-lib/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,7 @@ async fn polyfills_all() {
"}\n",
"''.replaceAll('test', 'other');\n",
"[].findLast(() => true);\n",
"import.meta.main;\n",
),
)
.add_local_file("/mod.test.ts", "import * as mod from './mod.ts';");
Expand All @@ -1650,6 +1651,7 @@ async fn polyfills_all() {
"}\n",
"''.replaceAll('test', 'other');\n",
"[].findLast(() => true);\n",
"import.meta.main;\n",
),
),
(
Expand All @@ -1659,6 +1661,7 @@ async fn polyfills_all() {
include_str!("../src/polyfills/scripts/esnext.error-cause.ts"),
include_str!("../src/polyfills/scripts/es2021.string-replaceAll.ts"),
include_str!("../src/polyfills/scripts/esnext.array-findLast.ts"),
include_str!("../src/polyfills/scripts/deno.import-meta.ts"),
)
),
]
Expand Down