diff options
author | 2023-06-01 18:04:09 -0700 | |
---|---|---|
committer | 2023-06-01 18:04:09 -0700 | |
commit | 3e84f18cc03a6bab898235ff2a9827b47a83aac9 (patch) | |
tree | 160b0bd3cf1b707bf68f500f0a82e85807a0fa99 | |
parent | 42606d6aed323fa24b6783b24624e9f57cb9ef9d (diff) | |
download | bun-3e84f18cc03a6bab898235ff2a9827b47a83aac9.tar.gz bun-3e84f18cc03a6bab898235ff2a9827b47a83aac9.tar.zst bun-3e84f18cc03a6bab898235ff2a9827b47a83aac9.zip |
Implement `__dirname` and `__filename`, allow direct eval in CommonJS (#3164)
* Implement `__dirname` and `__filename`, allow direct eval in CommonJS
* Fixup dirname and add test
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/bun.js/bindings/CommonJSModuleRecord.cpp | 21 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 16 | ||||
-rw-r--r-- | src/js_ast.zig | 4 | ||||
-rw-r--r-- | src/js_parser.zig | 46 | ||||
-rw-r--r-- | src/js_printer.zig | 34 | ||||
-rw-r--r-- | test/cli/run/require-cache-fixture.cjs | 14 | ||||
-rw-r--r-- | test/cli/run/require-cache.test.js | 2 |
7 files changed, 91 insertions, 46 deletions
diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index e3bf3a0db..a32d722d9 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -218,7 +218,7 @@ static Structure* internalCreateCommonJSModuleStructure( structure = structure->addPropertyTransition( vm, structure, - JSC::Identifier::fromString(vm, "fileName"_s), + JSC::Identifier::fromString(vm, "filename"_s), 0, offset); @@ -310,10 +310,15 @@ JSC::SourceCode createCommonJSModule( ? JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), source.commonJSExportsLen) : JSC::constructEmptyObject(globalObject, globalObject->objectPrototype()); - if (!globalObject->requireMap()->has(globalObject, requireMapKey)) { - globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject); + auto index = sourceURL.reverseFind('/', sourceURL.length()); + JSString* dirname = jsEmptyString(vm); + JSString* filename = requireMapKey; + if (index != WTF::notFound) { + dirname = JSC::jsSubstring(globalObject, requireMapKey, 0, index); } + globalObject->requireMap()->set(globalObject, requireMapKey, exportsObject); + JSC::SourceCode inputSource( JSC::StringSourceProvider::create(sourceCodeString, JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath(sourceURL)), @@ -344,6 +349,16 @@ JSC::SourceCode createCommonJSModule( scopeExtensionObject->putDirectOffset( vm, 2, + dirname); + + scopeExtensionObject->putDirectOffset( + vm, + 3, + filename); + + scopeExtensionObject->putDirectOffset( + vm, + 4, requireFunction); auto* executable = JSC::DirectEvalExecutable::create( diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 2a6be49b2..0a453a9c8 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2619,7 +2619,7 @@ void GlobalObject::finishCreation(VM& vm) JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype( globalObject, globalObject->objectPrototype(), - 3); + 5); JSC::PropertyOffset offset; auto& vm = globalObject->vm(); @@ -2640,6 +2640,20 @@ void GlobalObject::finishCreation(VM& vm) structure = structure->addPropertyTransition( vm, structure, + JSC::Identifier::fromString(vm, "__dirname"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, + JSC::Identifier::fromString(vm, "__filename"_s), + 0, + offset); + + structure = structure->addPropertyTransition( + vm, + structure, JSC::Identifier::fromString(vm, "require"_s), JSC::PropertyAttribute::Function | JSC::PropertyAttribute::Builtin | 0, offset); diff --git a/src/js_ast.zig b/src/js_ast.zig index c3585ef38..707e83fa0 100644 --- a/src/js_ast.zig +++ b/src/js_ast.zig @@ -4141,6 +4141,7 @@ pub const Expr = struct { e_class, e_require_string, e_require_call_target, + e_require_resolve_call_target, e_commonjs_export_identifier, @@ -4796,6 +4797,7 @@ pub const Expr = struct { e_require_string: E.RequireString, e_require_resolve_string: E.RequireResolveString, e_require_call_target: void, + e_require_resolve_call_target: void, e_missing: E.Missing, e_this: E.This, @@ -7612,6 +7614,7 @@ pub const Macro = struct { e_require_resolve_string: E.RequireResolveString, e_require_string: E.RequireString, e_require_call_target: void, + e_require_resolve_call_target: void, g_property: *G.Property, @@ -7660,6 +7663,7 @@ pub const Macro = struct { e_if, e_require_resolve_string, e_require_call_target, + e_require_resolve_call_target, e_import, e_this, e_class, diff --git a/src/js_parser.zig b/src/js_parser.zig index 0d15ffd9e..5f46507d7 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -16483,14 +16483,12 @@ fn NewParser_( // require.resolve(FOO) => import.meta.resolveSync(FOO, pathsObject) return p.newExpr( E.Call{ - .target = p.newExpr( - E.Dot{ - .target = p.newExpr(E.ImportMeta{}, e_.target.loc), - .name = "resolveSync", - .name_loc = e_.target.data.e_dot.name_loc, + .target = Expr{ + .data = .{ + .e_require_resolve_call_target = {}, }, - e_.target.loc, - ), + .loc = e_.target.loc, + }, .args = e_.args, .close_paren_loc = e_.close_paren_loc, }, @@ -21088,15 +21086,23 @@ fn NewParser_( // // })(module, exports, require); .bun_js => { - var args = allocator.alloc(Arg, 3) catch unreachable; - args[0] = Arg{ - .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty), - }; - args[1] = Arg{ - .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), - }; - args[2] = Arg{ - .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), + var args = allocator.alloc(Arg, 5) catch unreachable; + args[0..5].* = .{ + Arg{ + .binding = p.b(B.Identifier{ .ref = p.module_ref }, logger.Loc.Empty), + }, + Arg{ + .binding = p.b(B.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), + }, + Arg{ + .binding = p.b(B.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), + }, + Arg{ + .binding = p.b(B.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), + }, + Arg{ + .binding = p.b(B.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), + }, }; var total_stmts_count: usize = 0; for (parts) |part| { @@ -21124,15 +21130,17 @@ fn NewParser_( }, logger.Loc.Empty, ); - var call_args = allocator.alloc(Expr, 4) catch unreachable; + var call_args = allocator.alloc(Expr, 6) catch unreachable; // - // (function(module, exports, require) {}).call(exports, module, exports, require) - call_args[0..4].* = .{ + // (function(module, exports, require, __dirname, __filename) {}).call(exports, module, exports, require, __dirname, __filename) + call_args[0..6].* = .{ p.newExpr(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), p.newExpr(E.Identifier{ .ref = p.module_ref }, logger.Loc.Empty), p.newExpr(E.Identifier{ .ref = p.exports_ref }, logger.Loc.Empty), p.newExpr(E.Identifier{ .ref = p.require_ref }, logger.Loc.Empty), + p.newExpr(E.Identifier{ .ref = p.dirname_ref }, logger.Loc.Empty), + p.newExpr(E.Identifier{ .ref = p.filename_ref }, logger.Loc.Empty), }; const call = p.newExpr( diff --git a/src/js_printer.zig b/src/js_printer.zig index 8a540edf9..6cce8bb6e 100644 --- a/src/js_printer.zig +++ b/src/js_printer.zig @@ -2124,26 +2124,6 @@ fn NewPrinter( const is_unbound_eval = !e.is_direct_eval and p.isUnboundEvalIdentifier(e.target); - if (is_unbound_eval) { - if (e.args.len == 1 and e.args.ptr[0].data == .e_string and is_bun_platform) { - // prisma: - // - // eval("__dirname") - // - // We don't have a __dirname variable defined in our ESM <> CJS compat mode - // (Perhaps we should change that for cases like this?) - // - // - if (e.args.ptr[0].data.e_string.eqlComptime("__dirname")) { - p.print("import.meta.dir"); - return; - } else if (e.args.ptr[0].data.e_string.eqlComptime("__filename")) { - p.print("import.meta.file"); - return; - } - } - } - if (wrap) { p.print("("); } @@ -2155,9 +2135,10 @@ fn NewPrinter( p.stmt_start = p.writer.written; } } - // We don't ever want to accidentally generate a direct eval expression here + // We only want to generate an unbound eval() in CommonJS p.call_target = e.target.data; - if (is_unbound_eval) { + + if (is_unbound_eval and p.options.module_type != .cjs) { p.print("(0, "); p.printExpr(e.target, .postfix, ExprFlag.None()); p.print(")"); @@ -2196,6 +2177,15 @@ fn NewPrinter( p.print("import.meta.require"); } }, + .e_require_resolve_call_target => { + p.addSourceMapping(expr.loc); + + if (p.options.module_type == .cjs or !is_bun_platform) { + p.print("require.resolve"); + } else { + p.print("import.meta.resolveSync"); + } + }, .e_require_string => |e| { if (rewrite_esm_to_cjs and p.importRecord(e.import_record_index).is_legacy_bundled) { p.printIndent(); diff --git a/test/cli/run/require-cache-fixture.cjs b/test/cli/run/require-cache-fixture.cjs index 012f60589..b04e751ac 100644 --- a/test/cli/run/require-cache-fixture.cjs +++ b/test/cli/run/require-cache-fixture.cjs @@ -1,6 +1,18 @@ -// So it could be run in Node.js +// This fixture is intended to be able to run in both Node.js and Bun const Bun = (globalThis.Bun ??= { gc() {} }); +const { resolve } = require("path"); + +if (__filename !== resolve(module.filename)) { + console.error(__filename, module.id); + throw new Error("__filename !== module.id"); +} + +if (__dirname !== resolve(module.filename, "../")) { + console.error(__filename, module.id); + throw new Error("__dirname !== module.filename"); +} + const foo = require("./require-cache-fixture-b.cjs"); exports.foo = foo; diff --git a/test/cli/run/require-cache.test.js b/test/cli/run/require-cache.test.js index 2f22ec139..e20470f9d 100644 --- a/test/cli/run/require-cache.test.js +++ b/test/cli/run/require-cache.test.js @@ -2,10 +2,12 @@ import { test, expect } from "bun:test"; import { bunEnv, bunExe } from "harness"; import { join } from "path"; +// This also tests __dirname and __filename test("require.cache", () => { const { stdout, exitCode } = Bun.spawnSync({ cmd: [bunExe(), "run", join(import.meta.dir, "require-cache-fixture.cjs")], env: bunEnv, + stderr: "inherit", }); expect(stdout.toString().trim().endsWith("--pass--")).toBe(true); |