diff options
Diffstat (limited to '')
-rw-r--r-- | integration/bunjs-only-snippets/baz.js | 2 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/resolve-typescript-file.tsx | 1 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/resolve.test.js | 106 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/tsconfig.json | 10 | ||||
-rw-r--r-- | src/javascript/jsc/api/bun.zig | 42 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/BunBuiltinNames.h | 1 | ||||
-rw-r--r-- | src/javascript/jsc/bindings/ZigGlobalObject.cpp | 67 |
7 files changed, 198 insertions, 31 deletions
diff --git a/integration/bunjs-only-snippets/baz.js b/integration/bunjs-only-snippets/baz.js new file mode 100644 index 000000000..5837bb3bb --- /dev/null +++ b/integration/bunjs-only-snippets/baz.js @@ -0,0 +1,2 @@ +// this file is used in resolve.test.js +export default {}; diff --git a/integration/bunjs-only-snippets/resolve-typescript-file.tsx b/integration/bunjs-only-snippets/resolve-typescript-file.tsx new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/integration/bunjs-only-snippets/resolve-typescript-file.tsx @@ -0,0 +1 @@ +export default {}; diff --git a/integration/bunjs-only-snippets/resolve.test.js b/integration/bunjs-only-snippets/resolve.test.js new file mode 100644 index 000000000..d5d14dfef --- /dev/null +++ b/integration/bunjs-only-snippets/resolve.test.js @@ -0,0 +1,106 @@ +import { it, expect } from "bun:test"; +import { mkdirSync, writeFileSync } from "fs"; +import { join } from "path"; + +it("import.meta.resolve", async () => { + expect(await import.meta.resolve("./resolve.test.js")).toBe(import.meta.url); + + expect(await import.meta.resolve("./resolve.test.js", import.meta.url)).toBe( + import.meta.url + ); + + expect( + // optional second param can be any path, including a dir + await import.meta.resolve( + "./bunjs-only-snippets/resolve.test.js", + join(import.meta.url, "../") + ) + ).toBe(import.meta.url); + + // can be a package path + expect((await import.meta.resolve("react", import.meta.url)).length > 0).toBe( + true + ); + + // file extensions are optional + expect(await import.meta.resolve("./resolve.test")).toBe(import.meta.url); + + // works with tsconfig.json "paths" + expect(await import.meta.resolve("foo/bar")).toBe( + join(import.meta.url, "../baz.js") + ); + + // works with package.json "exports" + writePackageJSONExportsFixture(); + expect(await import.meta.resolve("package-json-exports/baz")).toBe( + join(import.meta.url, "../node_modules/package-json-exports/foo/bar.js") + ); + + expect(await import.meta.resolve("./resolve-typescript-file.tsx")).toBe( + join(import.meta.url, "../resolve-typescript-file.tsx") + ); + expect(await import.meta.resolve("./resolve-typescript-file.js")).toBe( + join(import.meta.url, "../resolve-typescript-file.tsx") + ); + + // works with typescript edgecases like: + // - If the file ends with .js and it doesn't exist, try again with .ts and .tsx + expect(await import.meta.resolve("./resolve-typescript-file.js")).toBe( + join(import.meta.url, "../resolve-typescript-file.tsx") + ); + expect(await import.meta.resolve("./resolve-typescript-file.tsx")).toBe( + join(import.meta.url, "../resolve-typescript-file.tsx") + ); + + try { + await import.meta.resolve("THIS FILE DOESNT EXIST"); + throw new Error("Test failed"); + } catch (exception) { + expect(exception instanceof ResolveError).toBe(true); + expect(exception.referrer).toBe(import.meta.url); + expect(exception.name).toBe("ResolveError"); + } +}); + +// the slightly lower level API, which doesn't prefill the second param +// and expects a directory instead of a filepath +it("Bun.resolve", async () => { + expect(await Bun.resolve("./resolve.test.js", import.meta.dir)).toBe( + import.meta.url + ); +}); + +// synchronous +it("Bun.resolveSync", () => { + expect(Bun.resolveSync("./resolve.test.js", import.meta.dir)).toBe( + import.meta.url + ); +}); + +function writePackageJSONExportsFixture() { + try { + mkdirSync( + join(import.meta.dir, "./node_modules/package-json-exports/foo"), + { + recursive: true, + } + ); + } catch (exception) {} + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-exports/foo/bar.js"), + "export const bar = 1;" + ); + writeFileSync( + join(import.meta.dir, "./node_modules/package-json-exports/package.json"), + JSON.stringify( + { + name: "package-json-exports", + exports: { + "./baz": "./foo/bar.js", + }, + }, + null, + 2 + ) + ); +} diff --git a/integration/bunjs-only-snippets/tsconfig.json b/integration/bunjs-only-snippets/tsconfig.json new file mode 100644 index 000000000..e76eae4ac --- /dev/null +++ b/integration/bunjs-only-snippets/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "lib": ["esnext", { "replace": "node", "with": "dom" }], + "baseUrl": ".", + "paths": { + "foo/bar": ["baz.js"] + }, + "typeRoots": ["./node_modules/@types"] + } +} diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig index e2af7ed95..c66c5f0f1 100644 --- a/src/javascript/jsc/api/bun.zig +++ b/src/javascript/jsc/api/bun.zig @@ -698,7 +698,7 @@ fn doResolve( return null; } - return doResolveWithArgs(ctx, specifier.getZigString(ctx.ptr()), from.getZigString(ctx.ptr()), exception); + return doResolveWithArgs(ctx, specifier.getZigString(ctx.ptr()), from.getZigString(ctx.ptr()), exception, false); } fn doResolveWithArgs( @@ -706,15 +706,25 @@ fn doResolveWithArgs( specifier: ZigString, from: ZigString, exception: js.ExceptionRef, + comptime is_file_path: bool, ) ?JSC.JSValue { var errorable: ErrorableZigString = undefined; - VirtualMachine.resolveForAPI( - &errorable, - ctx.ptr(), - specifier, - from, - ); + if (comptime is_file_path) { + VirtualMachine.resolve( + &errorable, + ctx.ptr(), + specifier, + from, + ); + } else { + VirtualMachine.resolveForAPI( + &errorable, + ctx.ptr(), + specifier, + from, + ); + } if (!errorable.success) { exception.* = bun.cast(JSC.JSValueRef, errorable.result.err.ptr.?); @@ -759,22 +769,7 @@ export fn Bun__resolve( ) JSC.JSValue { var exception_ = [1]JSC.JSValueRef{null}; var exception = &exception_; - const value = doResolveWithArgs(global.ref(), specifier.getZigString(global), source.getZigString(global), exception) orelse { - return JSC.JSPromise.rejectedPromiseValue(global, JSC.JSValue.fromRef(exception[0])); - }; - return JSC.JSPromise.resolvedPromiseValue(global, value); -} - -export fn Bun__resolveSync( - global: *JSGlobalObject, - specifier: JSValue, - source: JSValue, - exception_: ?*JSValue, -) JSC.JSValue { - var exception_ = [1]JSC.JSValueRef{null}; - var exception = &exception_; - exception_.* = exception[0]; - const value = doResolveWithArgs(global.ref(), specifier.getZigString(global), source.getZigString(global), exception) orelse { + const value = doResolveWithArgs(global.ref(), specifier.getZigString(global), source.getZigString(global), exception, true) orelse { return JSC.JSPromise.rejectedPromiseValue(global, JSC.JSValue.fromRef(exception[0])); }; return JSC.JSPromise.resolvedPromiseValue(global, value); @@ -783,7 +778,6 @@ export fn Bun__resolveSync( comptime { if (!is_bindgen) { _ = Bun__resolve; - _ = Bun__resolveSync; } } diff --git a/src/javascript/jsc/bindings/BunBuiltinNames.h b/src/javascript/jsc/bindings/BunBuiltinNames.h index 5600f9675..709ab28a5 100644 --- a/src/javascript/jsc/bindings/BunBuiltinNames.h +++ b/src/javascript/jsc/bindings/BunBuiltinNames.h @@ -67,6 +67,7 @@ using namespace JSC; macro(parse) \ macro(relative) \ macro(resolve) \ + macro(url) \ macro(sep) \ macro(delimiter) \ macro(toNamespacedPath) \ diff --git a/src/javascript/jsc/bindings/ZigGlobalObject.cpp b/src/javascript/jsc/bindings/ZigGlobalObject.cpp index ab35ff4d9..16d4786a3 100644 --- a/src/javascript/jsc/bindings/ZigGlobalObject.cpp +++ b/src/javascript/jsc/bindings/ZigGlobalObject.cpp @@ -491,6 +491,55 @@ static JSC_DEFINE_HOST_FUNCTION(functionATOB, return JSC::JSValue::encode(JSC::jsString(vm, WTF::String(decodedData->data(), decodedData->size()))); } +extern "C" JSC__JSValue Bun__resolve(JSC::JSGlobalObject* global, JSC__JSValue specifier, JSC__JSValue from); + +static JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolve); + +static JSC_DEFINE_HOST_FUNCTION(functionImportMeta__resolve, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + + switch (callFrame->argumentCount()) { + case 0: { + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + // not "requires" because "require" could be confusing + JSC::throwTypeError(globalObject, scope, "import.meta.resolve needs 1 argument (a string)"_s); + scope.release(); + return JSC::JSValue::encode(JSC::JSValue {}); + } + default: { + JSC::JSValue moduleName = callFrame->argument(0); + + if (moduleName.isUndefinedOrNull()) { + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSC::throwTypeError(globalObject, scope, "import.meta.resolve expects a string"_s); + scope.release(); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + JSC__JSValue from; + + if (callFrame->argumentCount() > 1) { + from = JSC::JSValue::encode(callFrame->argument(1)); + } else { + JSC::JSObject* thisObject = JSC::jsDynamicCast<JSC::JSObject*>(vm, callFrame->thisValue()); + if (UNLIKELY(!thisObject)) { + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSC::throwTypeError(globalObject, scope, "import.meta.resolve must be bound to an import.meta object"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + auto clientData = Bun::clientData(vm); + + from = JSC::JSValue::encode(thisObject->get(globalObject, clientData->builtinNames().urlPublicName())); + } + + return Bun__resolve(globalObject, JSC::JSValue::encode(moduleName), from); + } + } +} + // This is not a publicly exposed API currently. // This is used by the bundler to make Response, Request, FetchEvent, // and any other objects available globally. @@ -728,19 +777,23 @@ JSC::JSObject* GlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObje metaProperties->putDirect( vm, clientData->builtinNames().filePublicName(), JSC::jsSubstring(globalObject, keyString, index + 1, keyString->length() - index - 1)); + + metaProperties->putDirect( + vm, clientData->builtinNames().filePublicName(), + JSC::jsSubstring(globalObject, keyString, index + 1, keyString->length() - index - 1)); + + metaProperties->putDirect(vm, clientData->builtinNames().resolvePublicName(), + JSC::JSFunction::create(vm, JSC::jsCast<JSC::JSGlobalObject*>(globalObject), 0, + WTF::String("resolve"), functionImportMeta__resolve), + 0); } metaProperties->putDirect(vm, clientData->builtinNames().pathPublicName(), key); + // this is a lie + metaProperties->putDirect(vm, clientData->builtinNames().urlPublicName(), key); RETURN_IF_EXCEPTION(scope, nullptr); - // metaProperties->putDirect(vm, Identifier::fromString(vm, "resolve"), - // globalObject->globalThis() - // ->get(vm, Identifier::fromString("Bun")) - // .getObject() - // ->get(vm, Identifier::fromString("resolve"))); ); - // RETURN_IF_EXCEPTION(scope, nullptr); - return metaProperties; } |