diff options
author | 2022-06-24 06:59:47 -0700 | |
---|---|---|
committer | 2022-06-24 06:59:47 -0700 | |
commit | 7bb75f55530e52447b9c68bc5b0908bf734ba184 (patch) | |
tree | e30b431d6f257824f2821c56a2ec01136938cc5e /src/bun.js/builtins | |
parent | 6d6a89780b10816de38c465b1e6bb583979feacd (diff) | |
download | bun-7bb75f55530e52447b9c68bc5b0908bf734ba184.tar.gz bun-7bb75f55530e52447b9c68bc5b0908bf734ba184.tar.zst bun-7bb75f55530e52447b9c68bc5b0908bf734ba184.zip |
Add dynamic require support
Diffstat (limited to 'src/bun.js/builtins')
-rw-r--r-- | src/bun.js/builtins/BunBuiltinNames.h | 6 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.cpp | 122 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.h | 16 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/WebCoreJSBuiltinInternals.h | 82 | ||||
-rw-r--r-- | src/bun.js/builtins/js/JSZigGlobalObject.js | 131 |
5 files changed, 351 insertions, 6 deletions
diff --git a/src/bun.js/builtins/BunBuiltinNames.h b/src/bun.js/builtins/BunBuiltinNames.h index 1b49338c0..6acd30a8b 100644 --- a/src/bun.js/builtins/BunBuiltinNames.h +++ b/src/bun.js/builtins/BunBuiltinNames.h @@ -101,6 +101,7 @@ using namespace JSC; macro(flush) \ macro(flushAlgorithm) \ macro(format) \ + macro(fulfillModuleSync) \ macro(get) \ macro(getInternalWritableStream) \ macro(handleEvent) \ @@ -125,9 +126,11 @@ using namespace JSC; macro(join) \ macro(kind) \ macro(lazy) \ - macro(lazyStreamPrototypeMap) \ macro(lazyLoad) \ + macro(lazyStreamPrototypeMap) \ + macro(loadModule) \ macro(localStreams) \ + macro(main) \ macro(makeDOMException) \ macro(makeGetterTypeError) \ macro(makeThisTypeError) \ @@ -174,6 +177,7 @@ using namespace JSC; macro(releaseLock) \ macro(removeEventListener) \ macro(require) \ + macro(requireModule) \ macro(resolve) \ macro(resolveSync) \ macro(resume) \ diff --git a/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.cpp b/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.cpp index 05863cc27..421ba1181 100644 --- a/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.cpp +++ b/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.cpp @@ -49,7 +49,7 @@ namespace WebCore { const JSC::ConstructAbility s_jsZigGlobalObjectRequireCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_jsZigGlobalObjectRequireCodeConstructorKind = JSC::ConstructorKind::None; -const int s_jsZigGlobalObjectRequireCodeLength = 1225; +const int s_jsZigGlobalObjectRequireCodeLength = 1221; static const JSC::Intrinsic s_jsZigGlobalObjectRequireCodeIntrinsic = JSC::NoIntrinsic; const char* const s_jsZigGlobalObjectRequireCode = "(function (name) {\n" \ @@ -57,7 +57,7 @@ const char* const s_jsZigGlobalObjectRequireCode = " if (typeof name !== \"string\") {\n" \ " @throwTypeError(\"require() expects a string as its argument\");\n" \ " }\n" \ - "\n" \ + " \n" \ " const resolved = this.resolveSync(name, this.path);\n" \ " var requireCache = (globalThis[Symbol.for(\"_requireCache\")] ||= new @Map);\n" \ " var cached = requireCache.@get(resolved);\n" \ @@ -69,6 +69,7 @@ const char* const s_jsZigGlobalObjectRequireCode = " return cached;\n" \ " }\n" \ "\n" \ + "\n" \ " //\n" \ " if (resolved.endsWith(\".json\")) {\n" \ " var fs = (globalThis[Symbol.for(\"_fs\")] ||= Bun.fs());\n" \ @@ -85,9 +86,124 @@ const char* const s_jsZigGlobalObjectRequireCode = " var exports = Bun.TOML.parse(fs.readFileSync(resolved, \"utf8\"));\n" \ " requireCache.@set(resolved, exports);\n" \ " return exports;\n" \ + " } else {\n" \ + " var exports = this.requireModule(this, resolved);\n" \ + " requireCache.@set(resolved, exports);\n" \ + " return exports;\n" \ + " }\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsZigGlobalObjectLoadModuleCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsZigGlobalObjectLoadModuleCodeConstructorKind = JSC::ConstructorKind::None; +const int s_jsZigGlobalObjectLoadModuleCodeLength = 2783; +static const JSC::Intrinsic s_jsZigGlobalObjectLoadModuleCodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsZigGlobalObjectLoadModuleCode = + "(function (meta, resolvedSpecifier) {\n" \ + " \"use strict\";\n" \ + " var queue = @createFIFO();\n" \ + " var key = resolvedSpecifier;\n" \ + " \n" \ + " var Loader = globalThis.Loader;\n" \ + " var registry = Loader.registry;\n" \ + " while (key) {\n" \ + " @fulfillModuleSync(key);\n" \ + " var entry = registry.@get(key);\n" \ + "\n" \ + " //\n" \ + " //\n" \ + " //\n" \ + " //\n" \ + " var sourceCodeObject = @getPromiseInternalField(entry.fetch, @promiseFieldReactionsOrResult);\n" \ + " \n" \ + "\n" \ + " //\n" \ + " //\n" \ + " //\n" \ + " var moduleRecordPromise = Loader.parseModule(key, sourceCodeObject);\n" \ + " var module = entry.module;\n" \ + " if (!module && moduleRecordPromise && @isPromise(moduleRecordPromise)) {\n" \ + " var reactionsOrResult = @getPromiseInternalField(moduleRecordPromise, @promiseFieldReactionsOrResult);\n" \ + " var flags = @getPromiseInternalField(moduleRecordPromise, @promiseFieldFlags);\n" \ + " var state = flags & @promiseStateMask;\n" \ + "\n" \ + " //\n" \ + " if (state === @promiseStatePending || (reactionsOrResult && @isPromise(reactionsOrResult))) {\n" \ + " @throwTypeError(`require() async module \\\"${key}\\\" is unsupported`);\n" \ + " \n" \ + " } else if (state === @promiseStateRejected) {\n" \ + " //\n" \ + " //\n" \ + " @throwTypeError(`${reactionsOrResult?.message ?? \"An error occurred\"} while parsing module \\\"${key}\\\"`);\n" \ + " }\n" \ + " entry.module = module = reactionsOrResult;\n" \ + " } else if (moduleRecordPromise && !module) {\n" \ + " entry.module = module = moduleRecordPromise;\n" \ + " }\n" \ + "\n" \ + " //\n" \ + " @setStateToMax(entry, @ModuleLink);\n" \ + " var dependenciesMap = module.dependenciesMap;\n" \ + " var requestedModules = Loader.requestedModules(module);\n" \ + " var dependencies = @newArrayWithSize(requestedModules.length);\n" \ + " \n" \ + " for (var i = 0, length = requestedModules.length; i < length; ++i) {\n" \ + " var depName = requestedModules[i];\n" \ + "\n" \ + " //\n" \ + " //\n" \ + " var depKey = depName[0] === '/' ? depName : Loader.resolveSync(depName, key, @undefined);\n" \ + " var depEntry = Loader.ensureRegistered(depKey);\n" \ + "\n" \ + " if (depEntry.state < @ModuleLink) {\n" \ + " queue.push(depKey);\n" \ + " }\n" \ + "\n" \ + " @putByValDirect(dependencies, i, depEntry);\n" \ + " dependenciesMap.@set(depName, depEntry);\n" \ + " }\n" \ + "\n" \ + " entry.dependencies = dependencies;\n" \ + " key = queue.shift();\n" \ + " while (key && ((registry.@get(key)?.state ?? @ModuleFetch) >= @ModuleLink)) {\n" \ + " key = queue.shift();\n" \ + " }\n" \ + " }\n" \ + "\n" \ + " var linkAndEvaluateResult = Loader.linkAndEvaluateModule(resolvedSpecifier, @undefined);\n" \ + " if (linkAndEvaluateResult && @isPromise(linkAndEvaluateResult)) {\n" \ + " //\n" \ + " //\n" \ + " @throwTypeError(`require() async module \\\"${resolvedSpecifier}\\\" is unsupported`);\n" \ + " }\n" \ + "\n" \ + " return Loader.registry.@get(resolvedSpecifier);\n" \ + "})\n" \ +; + +const JSC::ConstructAbility s_jsZigGlobalObjectRequireModuleCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_jsZigGlobalObjectRequireModuleCodeConstructorKind = JSC::ConstructorKind::None; +const int s_jsZigGlobalObjectRequireModuleCodeLength = 613; +static const JSC::Intrinsic s_jsZigGlobalObjectRequireModuleCodeIntrinsic = JSC::NoIntrinsic; +const char* const s_jsZigGlobalObjectRequireModuleCode = + "(function (meta, resolved) {\n" \ + " \"use strict\";\n" \ + " var Loader = globalThis.Loader;\n" \ + " var entry = Loader.registry.@get(resolved);\n" \ + "\n" \ + " if (!entry || !entry.evaluated) {\n" \ + " entry = this.loadModule(meta, resolved); \n" \ " }\n" \ "\n" \ - " @throwTypeError(`Dynamic require isn't supported for file type: ${resolved.subsring(resolved.lastIndexOf(\".\") + 1) || resolved}`);\n" \ + " if (!entry || !entry.evaluated || !entry.module) {\n" \ + " @throwTypeError(`require() failed to evaluate module \\\"${resolved}\\\". This is an internal consistentency error.`);\n" \ + " }\n" \ + " var exports = Loader.getModuleNamespaceObject(entry.module);\n" \ + " var commonJS = exports.default;\n" \ + " if (commonJS && @isObject(commonJS) && Symbol.for(\"CommonJS\") in commonJS) {\n" \ + " return commonJS();\n" \ + " }\n" \ + " return exports;\n" \ "})\n" \ ; diff --git a/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.h b/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.h index f02eec836..31092981d 100644 --- a/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.h +++ b/src/bun.js/builtins/cpp/JSZigGlobalObjectBuiltins.h @@ -51,17 +51,33 @@ extern const char* const s_jsZigGlobalObjectRequireCode; extern const int s_jsZigGlobalObjectRequireCodeLength; extern const JSC::ConstructAbility s_jsZigGlobalObjectRequireCodeConstructAbility; extern const JSC::ConstructorKind s_jsZigGlobalObjectRequireCodeConstructorKind; +extern const char* const s_jsZigGlobalObjectLoadModuleCode; +extern const int s_jsZigGlobalObjectLoadModuleCodeLength; +extern const JSC::ConstructAbility s_jsZigGlobalObjectLoadModuleCodeConstructAbility; +extern const JSC::ConstructorKind s_jsZigGlobalObjectLoadModuleCodeConstructorKind; +extern const char* const s_jsZigGlobalObjectRequireModuleCode; +extern const int s_jsZigGlobalObjectRequireModuleCodeLength; +extern const JSC::ConstructAbility s_jsZigGlobalObjectRequireModuleCodeConstructAbility; +extern const JSC::ConstructorKind s_jsZigGlobalObjectRequireModuleCodeConstructorKind; #define WEBCORE_FOREACH_JSZIGGLOBALOBJECT_BUILTIN_DATA(macro) \ macro(require, jsZigGlobalObjectRequire, 1) \ + macro(loadModule, jsZigGlobalObjectLoadModule, 2) \ + macro(requireModule, jsZigGlobalObjectRequireModule, 2) \ #define WEBCORE_BUILTIN_JSZIGGLOBALOBJECT_REQUIRE 1 +#define WEBCORE_BUILTIN_JSZIGGLOBALOBJECT_LOADMODULE 1 +#define WEBCORE_BUILTIN_JSZIGGLOBALOBJECT_REQUIREMODULE 1 #define WEBCORE_FOREACH_JSZIGGLOBALOBJECT_BUILTIN_CODE(macro) \ macro(jsZigGlobalObjectRequireCode, require, ASCIILiteral(), s_jsZigGlobalObjectRequireCodeLength) \ + macro(jsZigGlobalObjectLoadModuleCode, loadModule, ASCIILiteral(), s_jsZigGlobalObjectLoadModuleCodeLength) \ + macro(jsZigGlobalObjectRequireModuleCode, requireModule, ASCIILiteral(), s_jsZigGlobalObjectRequireModuleCodeLength) \ #define WEBCORE_FOREACH_JSZIGGLOBALOBJECT_BUILTIN_FUNCTION_NAME(macro) \ + macro(loadModule) \ macro(require) \ + macro(requireModule) \ #define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \ JSC::FunctionExecutable* codeName##Generator(JSC::VM&); diff --git a/src/bun.js/builtins/cpp/WebCoreJSBuiltinInternals.h b/src/bun.js/builtins/cpp/WebCoreJSBuiltinInternals.h index c52f65d85..fc5e2406a 100644 --- a/src/bun.js/builtins/cpp/WebCoreJSBuiltinInternals.h +++ b/src/bun.js/builtins/cpp/WebCoreJSBuiltinInternals.h @@ -1,5 +1,87 @@ //clang-format off namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } +namespace Zig { class GlobalObject; } /* * Copyright (c) 2015 Igalia * Copyright (c) 2015 Igalia S.L. diff --git a/src/bun.js/builtins/js/JSZigGlobalObject.js b/src/bun.js/builtins/js/JSZigGlobalObject.js index cb3446159..7b82067ec 100644 --- a/src/bun.js/builtins/js/JSZigGlobalObject.js +++ b/src/bun.js/builtins/js/JSZigGlobalObject.js @@ -28,7 +28,7 @@ function require(name) { if (typeof name !== "string") { @throwTypeError("require() expects a string as its argument"); } - + const resolved = this.resolveSync(name, this.path); var requireCache = (globalThis[Symbol.for("_requireCache")] ||= new @Map); var cached = requireCache.@get(resolved); @@ -40,6 +40,7 @@ function require(name) { return cached; } + // TODO: remove this hardcoding if (resolved.endsWith(".json")) { var fs = (globalThis[Symbol.for("_fs")] ||= Bun.fs()); @@ -56,7 +57,133 @@ function require(name) { var exports = Bun.TOML.parse(fs.readFileSync(resolved, "utf8")); requireCache.@set(resolved, exports); return exports; + } else { + var exports = this.requireModule(this, resolved); + requireCache.@set(resolved, exports); + return exports; + } +} + +function loadModule(meta, resolvedSpecifier) { + "use strict"; + var Loader = globalThis.Loader; + + var queue = @createFIFO(); + var key = resolvedSpecifier; + var registry = Loader.registry; + while (key) { + @fulfillModuleSync(key); + var entry = registry.@get(key); + + // entry.fetch is a Promise<SourceCode> + // SourceCode is not a string, it's a JSC::SourceCode object + // this pulls it out of the promise without delaying by a tick + // the promise is already fullfilled by @fullfillModuleSync + var sourceCodeObject = @getPromiseInternalField( + entry.fetch, + @promiseFieldReactionsOrResult + ); + + // parseModule() returns a Promise, but the value is already fulfilled + // so we just pull it out of the promise here once again + // But, this time we do it a little more carefully because this is a JSC function call and not bun source code + var moduleRecordPromise = Loader.parseModule(key, sourceCodeObject); + var module = entry.module; + if (!module && moduleRecordPromise && @isPromise(moduleRecordPromise)) { + var reactionsOrResult = @getPromiseInternalField( + moduleRecordPromise, + @promiseFieldReactionsOrResult + ); + var flags = @getPromiseInternalField( + moduleRecordPromise, + @promiseFieldFlags + ); + var state = flags & @promiseStateMask; + + // this branch should never happen, but just to be safe + if ( + state === @promiseStatePending || + (reactionsOrResult && @isPromise(reactionsOrResult)) + ) { + @throwTypeError(`require() async module \"${key}\" is unsupported`); + } else if (state === @promiseStateRejected) { + // this branch happens if there is a syntax error and somehow bun didn't catch it + // "throw" is unsupported here, so we use "throwTypeError" (TODO: use SyntaxError but preserve the specifier) + @throwTypeError( + `${ + reactionsOrResult?.message ?? "An error occurred" + } while parsing module \"${key}\"` + ); + } + entry.module = module = reactionsOrResult; + } else if (moduleRecordPromise && !module) { + entry.module = module = moduleRecordPromise; + } + + // This is very similar to "requestInstantiate" in ModuleLoader.js in JavaScriptCore. + @setStateToMax(entry, @ModuleLink); + var dependenciesMap = module.dependenciesMap; + var requestedModules = Loader.requestedModules(module); + var dependencies = @newArrayWithSize(requestedModules.length); + + for (var i = 0, length = requestedModules.length; i < length; ++i) { + var depName = requestedModules[i]; + + // optimization: if it starts with a slash then it's an absolute path + // we don't need to run the resolver a 2nd time + var depKey = + depName[0] === "/" + ? depName + : Loader.resolveSync(depName, key, @undefined); + var depEntry = Loader.ensureRegistered(depKey); + + if (depEntry.state < @ModuleLink) { + queue.push(depKey); + } + + @putByValDirect(dependencies, i, depEntry); + dependenciesMap.@set(depName, depEntry); + } + + entry.dependencies = dependencies; + key = queue.shift(); + while (key && (registry.@get(key)?.state ?? @ModuleFetch) >= @ModuleLink) { + key = queue.shift(); + } + } + + var linkAndEvaluateResult = Loader.linkAndEvaluateModule( + resolvedSpecifier, + @undefined + ); + if (linkAndEvaluateResult && @isPromise(linkAndEvaluateResult)) { + // if you use top-level await, or any dependencies use top-level await, then we throw here + // this means the module will still actually load eventually, but that's okay. + @throwTypeError( + `require() async module \"${resolvedSpecifier}\" is unsupported` + ); + } + + return Loader.registry.@get(resolvedSpecifier); + +} + +function requireModule(meta, resolved) { + "use strict"; + var Loader = globalThis.Loader; + var entry = Loader.registry.@get(resolved); + + if (!entry || !entry.evaluated) { + entry = this.loadModule(meta, resolved); } - @throwTypeError(`Dynamic require isn't supported for file type: ${resolved.subsring(resolved.lastIndexOf(".") + 1) || resolved}`); + if (!entry || !entry.evaluated || !entry.module) { + @throwTypeError(`require() failed to evaluate module \"${resolved}\". This is an internal consistentency error.`); + } + var exports = Loader.getModuleNamespaceObject(entry.module); + var commonJS = exports.default; + if (commonJS && @isObject(commonJS) && Symbol.for("CommonJS") in commonJS) { + return commonJS(); + } + return exports; } |