diff options
author | 2023-05-22 18:51:05 -0700 | |
---|---|---|
committer | 2023-05-22 18:51:05 -0700 | |
commit | fc40c690ea30a632a8d0d9490321c50ec898d8a5 (patch) | |
tree | 6e3ca0bb2c02347006a6b2a09c4aa156b86bd770 /src/bun.js/builtins/ts/ImportMetaObject.ts | |
parent | 23d42dc2377440dedc9d8e423f1ea077507d62c8 (diff) | |
download | bun-fc40c690ea30a632a8d0d9490321c50ec898d8a5.tar.gz bun-fc40c690ea30a632a8d0d9490321c50ec898d8a5.tar.zst bun-fc40c690ea30a632a8d0d9490321c50ec898d8a5.zip |
Write out builtins with TypeScript + Minify them (#2999)
* start work drafting how builtins will work
* work on ts builtin
* builtins stuff so far
* builtins
* done for today
* continue work
* working on it
* bindings so far
* well, it builds. doesnt run
* IT RUNS
* still lots of ts errors but it is functional
* sloppy mode
Diffstat (limited to 'src/bun.js/builtins/ts/ImportMetaObject.ts')
-rw-r--r-- | src/bun.js/builtins/ts/ImportMetaObject.ts | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/src/bun.js/builtins/ts/ImportMetaObject.ts b/src/bun.js/builtins/ts/ImportMetaObject.ts new file mode 100644 index 000000000..6c66075c6 --- /dev/null +++ b/src/bun.js/builtins/ts/ImportMetaObject.ts @@ -0,0 +1,152 @@ +type ImportMetaObject = Partial<ImportMeta>; + +export function loadCJS2ESM(this: ImportMetaObject, resolvedSpecifier: string) { + var loader = Loader; + var queue = $createFIFO(); + var key = resolvedSpecifier; + while (key) { + // we need to explicitly check because state could be $ModuleFetch + // it will throw this error if we do not: + // $throwTypeError("Requested module is already fetched."); + var entry = loader.registry.$get(key); + + if (!entry || !entry.state || entry.state <= $ModuleFetch) { + $fulfillModuleSync(key); + entry = loader.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))) { + throw new TypeError(`require() async module "${key}" is unsupported`); + } else if (state === $promiseStateRejected) { + // TODO: use SyntaxError but preserve the specifier + throw new TypeError(`${reactionsOrResult?.message ?? "An error occurred"} while parsing module \"${key}\"`); + } + entry.module = module = reactionsOrResult; + } else if (moduleRecordPromise && !module) { + entry.module = module = moduleRecordPromise as LoaderModule; + } + + // 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<string>(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.resolve(depName, key); + var depEntry = loader.ensureRegistered(depKey); + if (depEntry.state < $ModuleLink) { + queue.push(depKey); + } + + $putByValDirect(dependencies, i, depEntry); + dependenciesMap.$set(depName, depEntry); + } + + entry.dependencies = dependencies; + // All dependencies resolved, set instantiate and satisfy field directly. + entry.instantiate = Promise.resolve(entry); + entry.satisfy = Promise.resolve(entry); + key = queue.shift(); + while (key && (loader.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. + throw new TypeError(`require() async module \"${resolvedSpecifier}\" is unsupported`); + } + + return loader.registry.$get(resolvedSpecifier); +} + +export function requireESM(this: ImportMetaObject, resolved) { + var entry = Loader.registry.$get(resolved); + + if (!entry || !entry.evaluated) { + entry = $loadCJS2ESM(resolved); + } + + if (!entry || !entry.evaluated || !entry.module) { + throw new TypeError(`require() failed to evaluate module "${resolved}". This is an internal consistentency error.`); + } + var exports = Loader.getModuleNamespaceObject(entry.module); + var commonJS = exports.default; + var cjs = commonJS?.[$commonJSSymbol]; + if (cjs === 0) { + return commonJS; + } else if (cjs && $isCallable(commonJS)) { + return commonJS(); + } + + return exports; +} + +export function internalRequire(this: ImportMetaObject, resolved) { + var cached = $requireMap.$get(resolved); + const last5 = resolved.substring(resolved.length - 5); + if (cached) { + if (last5 === ".node") { + return cached.exports; + } + return cached; + } + + // TODO: remove this hardcoding + if (last5 === ".json") { + var fs = (globalThis[Symbol.for("_fs")] ||= Bun.fs()); + var exports = JSON.parse(fs.readFileSync(resolved, "utf8")); + $requireMap.$set(resolved, exports); + return exports; + } else if (last5 === ".node") { + var module = { exports: {} }; + process.dlopen(module, resolved); + $requireMap.$set(resolved, module); + return module.exports; + } else if (last5 === ".toml") { + var fs = (globalThis[Symbol.for("_fs")] ||= Bun.fs()); + var exports = Bun.TOML.parse(fs.readFileSync(resolved, "utf8")); + $requireMap.$set(resolved, exports); + return exports; + } else { + var exports = $requireESM(resolved); + $requireMap.$set(resolved, exports); + return exports; + } +} + +$sloppy; +export function require(this: ImportMetaObject, name) { + var from = this?.path ?? arguments.callee.path; + + if (typeof name !== "string") { + throw new TypeError("require(name) must be a string"); + } + + return $internalRequire($resolveSync(name, from)); +} + +$getter; +export function main(this: ImportMetaObject) { + return this.path === Bun.main; +} |