diff options
author | 2023-05-24 12:01:59 -0700 | |
---|---|---|
committer | 2023-05-24 12:01:59 -0700 | |
commit | b3d5f37598ea4ca37a863f05ade94637477f3700 (patch) | |
tree | 7469b97e44f2af3d6fa857a5bf96de267a657344 | |
parent | 31c967206ab0d3cb5c0e4bd636fa9668e778ec61 (diff) | |
download | bun-b3d5f37598ea4ca37a863f05ade94637477f3700.tar.gz bun-b3d5f37598ea4ca37a863f05ade94637477f3700.tar.zst bun-b3d5f37598ea4ca37a863f05ade94637477f3700.zip |
Implement `require.cache` (#3045)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/bun.js/bindings/ImportMetaObject.cpp | 21 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 13 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 3 | ||||
-rw-r--r-- | src/bun.js/builtins/WebCoreJSBuiltins.cpp | 8 | ||||
-rw-r--r-- | src/bun.js/builtins/WebCoreJSBuiltins.h | 11 | ||||
-rw-r--r-- | src/bun.js/builtins/ts/ImportMetaObject.ts | 93 | ||||
-rw-r--r-- | test/cli/run/require-cache-fixture-b.cjs | 3 | ||||
-rw-r--r-- | test/cli/run/require-cache-fixture.cjs | 23 | ||||
-rw-r--r-- | test/cli/run/require-cache.test.js | 13 |
9 files changed, 187 insertions, 1 deletions
diff --git a/src/bun.js/bindings/ImportMetaObject.cpp b/src/bun.js/bindings/ImportMetaObject.cpp index 9c421d839..38cbeba47 100644 --- a/src/bun.js/bindings/ImportMetaObject.cpp +++ b/src/bun.js/bindings/ImportMetaObject.cpp @@ -229,6 +229,24 @@ JSC::Structure* Zig::ImportMetaObject::createResolveFunctionStructure(JSC::VM& v return JSRequireResolveFunction::createStructure(vm, globalObject, prototype); } +JSC_DEFINE_CUSTOM_GETTER(jsRequireCacheGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + Zig::GlobalObject* thisObject = jsCast<Zig::GlobalObject*>(globalObject); + return JSValue::encode(thisObject->lazyRequireCacheObject()); +} + +JSC_DEFINE_CUSTOM_SETTER(jsRequireCacheSetter, + (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, + JSC::EncodedJSValue value, JSC::PropertyName propertyName)) +{ + JSObject* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue)); + if (!thisObject) + return false; + + thisObject->putDirect(globalObject->vm(), propertyName, JSValue::decode(value), 0); + return true; +} + JSObject* Zig::ImportMetaObject::createRequireFunction(VM& vm, JSGlobalObject* lexicalGlobalObject, const WTF::String& pathString) { Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(lexicalGlobalObject); @@ -237,6 +255,7 @@ JSObject* Zig::ImportMetaObject::createRequireFunction(VM& vm, JSGlobalObject* l auto clientData = WebCore::clientData(vm); requireFunction->putDirect(vm, clientData->builtinNames().pathPublicName(), jsString(vm, pathString), PropertyAttribute::DontEnum | 0); requireFunction->putDirect(vm, clientData->builtinNames().resolvePublicName(), resolveFunction, PropertyAttribute::Function | PropertyAttribute::DontDelete | 0); + requireFunction->putDirectCustomAccessor(vm, Identifier::fromString(vm, "cache"_s), JSC::CustomGetterSetter::create(vm, jsRequireCacheGetter, jsRequireCacheSetter), 0); return requireFunction; } @@ -418,7 +437,7 @@ void ImportMetaObjectPrototype::finishCreation(VM& vm, JSGlobalObject* globalObj builtinNames.mainPublicName(), GetterSetter::create(vm, globalObject, JSFunction::create(vm, importMetaObjectMainCodeGenerator(vm), globalObject), nullptr), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin | 0); - + this->putDirect(vm, Identifier::fromString(vm, "primordials"_s), jsUndefined(), JSC::PropertyAttribute::DontEnum | 0); String requireString = "[[require]]"_s; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index b3da8a98f..92568f1a9 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2566,6 +2566,18 @@ void GlobalObject::finishCreation(VM& vm) Base::finishCreation(vm); ASSERT(inherits(info())); + m_lazyRequireCacheObject.initLater( + [](const Initializer<JSObject>& init) { + JSC::VM& vm = init.vm; + JSC::JSGlobalObject* globalObject = init.owner; + + auto* function = JSFunction::create(vm, static_cast<JSC::FunctionExecutable*>(importMetaObjectCreateRequireCacheCodeGenerator(vm)), globalObject); + + NakedPtr<JSC::Exception> returnedException = nullptr; + auto result = JSC::call(globalObject, function, JSC::getCallData(function), globalObject, ArgList(), returnedException); + init.set(result.toObject(globalObject)); + }); + // Change prototype from null to object for synthetic modules. m_moduleNamespaceObjectStructure.initLater( [](const Initializer<Structure>& init) { @@ -3752,6 +3764,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_requireResolveFunctionStructure.visit(visitor); thisObject->m_resolveFunctionPrototype.visit(visitor); thisObject->m_dnsObject.visit(visitor); + thisObject->m_lazyRequireCacheObject.visit(visitor); thisObject->m_vmModuleContextMap.visit(visitor); thisObject->m_bunSleepThenCallback.visit(visitor); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index f489b3942..c7c792a2c 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -250,6 +250,8 @@ public: Structure* requireResolveFunctionStructure() { return m_requireResolveFunctionStructure.getInitializedOnMainThread(this); } JSObject* requireResolveFunctionPrototype() { return m_resolveFunctionPrototype.getInitializedOnMainThread(this); } + JSObject* lazyRequireCacheObject() { return m_lazyRequireCacheObject.getInitializedOnMainThread(this); } + JSFunction* bunSleepThenCallback() { return m_bunSleepThenCallback.getInitializedOnMainThread(this); } JSObject* dnsObject() { return m_dnsObject.getInitializedOnMainThread(this); } @@ -466,6 +468,7 @@ private: LazyProperty<JSGlobalObject, JSObject> m_resolveFunctionPrototype; LazyProperty<JSGlobalObject, JSObject> m_dnsObject; LazyProperty<JSGlobalObject, JSWeakMap> m_vmModuleContextMap; + LazyProperty<JSGlobalObject, JSObject> m_lazyRequireCacheObject; LazyProperty<JSGlobalObject, JSFunction> m_bunSleepThenCallback; LazyProperty<JSGlobalObject, Structure> m_cachedGlobalObjectStructure; diff --git a/src/bun.js/builtins/WebCoreJSBuiltins.cpp b/src/bun.js/builtins/WebCoreJSBuiltins.cpp index 898765f86..ab93a17ea 100644 --- a/src/bun.js/builtins/WebCoreJSBuiltins.cpp +++ b/src/bun.js/builtins/WebCoreJSBuiltins.cpp @@ -2208,6 +2208,14 @@ const int s_importMetaObjectInternalRequireCodeLength = 569; static const JSC::Intrinsic s_importMetaObjectInternalRequireCodeIntrinsic = JSC::NoIntrinsic; const char* const s_importMetaObjectInternalRequireCode = "(function (n){\"use strict\";var _=@requireMap.@get(n);const i=n.substring(n.length-5);if(_){if(i===\".node\")return _.exports;return _}if(i===\".json\"){var S=globalThis[Symbol.for(\"_fs\")]||=@Bun.fs(),F=JSON.parse(S.readFileSync(n,\"utf8\"));return @requireMap.@set(n,F),F}else if(i===\".node\"){var b={exports:{}};return process.dlopen(b,n),@requireMap.@set(n,b),b.exports}else if(i===\".toml\"){var S=globalThis[Symbol.for(\"_fs\")]||=@Bun.fs(),F=@Bun.TOML.parse(S.readFileSync(n,\"utf8\"));return @requireMap.@set(n,F),F}else{var F=@requireESM(n);return @requireMap.@set(n,F),F}})\n"; +// createRequireCache +const JSC::ConstructAbility s_importMetaObjectCreateRequireCacheCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; +const JSC::ConstructorKind s_importMetaObjectCreateRequireCacheCodeConstructorKind = JSC::ConstructorKind::None; +const JSC::ImplementationVisibility s_importMetaObjectCreateRequireCacheCodeImplementationVisibility = JSC::ImplementationVisibility::Public; +const int s_importMetaObjectCreateRequireCacheCodeLength = 888; +static const JSC::Intrinsic s_importMetaObjectCreateRequireCacheCodeIntrinsic = JSC::NoIntrinsic; +const char* const s_importMetaObjectCreateRequireCacheCode = "(function (){\"use strict\";class r{id;parent;filename;children=[];paths=[];constructor(_){this.id=_;const c=_.lastIndexOf(\"/\");if(c!==-1&&_.length>c+1)this.filename=_.substring(c+1);else this.filename=_}get loaded(){return!0}require(_){return @internalRequire(@resolveSync(_,this.id))}get exports(){return @requireMap.@get(this.id)\?\?{}}set exports(_){@requireMap.@set(this.id,_)}}var w=new Map;return new Proxy({},{get(_,c){if(@requireMap.@get(c)){var b=w.@get(c);if(!b)b=new r(c),w.@set(c,b);return b}},set(_,c,t){if(!w.@has(c))w.@set(c,new r(c));return @requireMap.@set(c,t\?.exports),!0},has(_,c){return @requireMap.@has(c)},deleteProperty(_,c){return w.@delete(c),@requireMap.@delete(c),@Loader.registry.@delete(c)},ownKeys(_){return[...@requireMap.@keys()]},getPrototypeOf(_){return null},getOwnPropertyDescriptor(_,c){if(@requireMap.@has(c))return{configurable:!0,enumerable:!0}}})})\n"; + // require const JSC::ConstructAbility s_importMetaObjectRequireCodeConstructAbility = JSC::ConstructAbility::CannotConstruct; const JSC::ConstructorKind s_importMetaObjectRequireCodeConstructorKind = JSC::ConstructorKind::None; diff --git a/src/bun.js/builtins/WebCoreJSBuiltins.h b/src/bun.js/builtins/WebCoreJSBuiltins.h index 2f1fd3816..3414ed847 100644 --- a/src/bun.js/builtins/WebCoreJSBuiltins.h +++ b/src/bun.js/builtins/WebCoreJSBuiltins.h @@ -3964,6 +3964,14 @@ extern const JSC::ConstructAbility s_importMetaObjectInternalRequireCodeConstruc extern const JSC::ConstructorKind s_importMetaObjectInternalRequireCodeConstructorKind; extern const JSC::ImplementationVisibility s_importMetaObjectInternalRequireCodeImplementationVisibility; +// createRequireCache +#define WEBCORE_BUILTIN_IMPORTMETAOBJECT_CREATEREQUIRECACHE 1 +extern const char* const s_importMetaObjectCreateRequireCacheCode; +extern const int s_importMetaObjectCreateRequireCacheCodeLength; +extern const JSC::ConstructAbility s_importMetaObjectCreateRequireCacheCodeConstructAbility; +extern const JSC::ConstructorKind s_importMetaObjectCreateRequireCacheCodeConstructorKind; +extern const JSC::ImplementationVisibility s_importMetaObjectCreateRequireCacheCodeImplementationVisibility; + // require #define WEBCORE_BUILTIN_IMPORTMETAOBJECT_REQUIRE 1 extern const char* const s_importMetaObjectRequireCode; @@ -3984,6 +3992,7 @@ extern const JSC::ImplementationVisibility s_importMetaObjectMainCodeImplementat macro(loadCJS2ESM, importMetaObjectLoadCJS2ESM, 1) \ macro(requireESM, importMetaObjectRequireESM, 1) \ macro(internalRequire, importMetaObjectInternalRequire, 1) \ + macro(createRequireCache, importMetaObjectCreateRequireCache, 0) \ macro(require, importMetaObjectRequire, 1) \ macro(main, importMetaObjectMain, 0) \ @@ -3991,6 +4000,7 @@ extern const JSC::ImplementationVisibility s_importMetaObjectMainCodeImplementat macro(importMetaObjectLoadCJS2ESMCode, loadCJS2ESM, ASCIILiteral(), s_importMetaObjectLoadCJS2ESMCodeLength) \ macro(importMetaObjectRequireESMCode, requireESM, ASCIILiteral(), s_importMetaObjectRequireESMCodeLength) \ macro(importMetaObjectInternalRequireCode, internalRequire, ASCIILiteral(), s_importMetaObjectInternalRequireCodeLength) \ + macro(importMetaObjectCreateRequireCacheCode, createRequireCache, ASCIILiteral(), s_importMetaObjectCreateRequireCacheCodeLength) \ macro(importMetaObjectRequireCode, require, ASCIILiteral(), s_importMetaObjectRequireCodeLength) \ macro(importMetaObjectMainCode, main, "get main"_s, s_importMetaObjectMainCodeLength) \ @@ -3998,6 +4008,7 @@ extern const JSC::ImplementationVisibility s_importMetaObjectMainCodeImplementat macro(loadCJS2ESM) \ macro(requireESM) \ macro(internalRequire) \ + macro(createRequireCache) \ macro(require) \ macro(main) \ diff --git a/src/bun.js/builtins/ts/ImportMetaObject.ts b/src/bun.js/builtins/ts/ImportMetaObject.ts index 6c66075c6..9ce53c192 100644 --- a/src/bun.js/builtins/ts/ImportMetaObject.ts +++ b/src/bun.js/builtins/ts/ImportMetaObject.ts @@ -135,6 +135,99 @@ export function internalRequire(this: ImportMetaObject, resolved) { } } +export function createRequireCache() { + class Module { + id; + parent; + filename; + children = []; + paths = []; + + constructor(filename) { + this.id = filename; + // TODO: windows + const lastSlash = filename.lastIndexOf("/"); + if (lastSlash !== -1 && filename.length > lastSlash + 1) { + this.filename = filename.substring(lastSlash + 1); + } else { + this.filename = filename; + } + } + + get loaded() { + return true; + } + + require(path) { + return $internalRequire($resolveSync(path, this.id)); + } + + get exports() { + return $requireMap.$get(this.id) ?? {}; + } + + set exports(value) { + $requireMap.$set(this.id, value); + } + } + + var moduleMap = new Map(); + + return new Proxy( + {}, + { + get(target, key: string) { + const entry = $requireMap.$get(key); + if (entry) { + var mod = moduleMap.$get(key); + if (!mod) { + mod = new Module(key); + moduleMap.$set(key, mod); + } + return mod; + } + }, + set(target, key: string, value) { + if (!moduleMap.$has(key)) { + moduleMap.$set(key, new Module(key)); + } + + $requireMap.$set(key, value?.exports); + + return true; + }, + + has(target, key: string) { + return $requireMap.$has(key); + }, + + deleteProperty(target, key: string) { + moduleMap.$delete(key); + $requireMap.$delete(key); + return Loader.registry.$delete(key); + }, + + ownKeys(target) { + return [...$requireMap.$keys()]; + }, + + // In Node, require.cache has a null prototype + getPrototypeOf(target) { + return null; + }, + + getOwnPropertyDescriptor(target, key: string) { + if ($requireMap.$has(key)) { + return { + configurable: true, + enumerable: true, + }; + } + }, + }, + ); +} + $sloppy; export function require(this: ImportMetaObject, name) { var from = this?.path ?? arguments.callee.path; diff --git a/test/cli/run/require-cache-fixture-b.cjs b/test/cli/run/require-cache-fixture-b.cjs new file mode 100644 index 000000000..8f8e40ff0 --- /dev/null +++ b/test/cli/run/require-cache-fixture-b.cjs @@ -0,0 +1,3 @@ +exports.foo = 123; +exports.bar = 456; +exports.baz = 789; diff --git a/test/cli/run/require-cache-fixture.cjs b/test/cli/run/require-cache-fixture.cjs new file mode 100644 index 000000000..0186c4b75 --- /dev/null +++ b/test/cli/run/require-cache-fixture.cjs @@ -0,0 +1,23 @@ +const foo = require("./require-cache-fixture-b.cjs"); + +exports.foo = foo; + +if (require.cache[require.resolve("./require-cache-fixture-b.cjs")].exports !== exports.foo) { + throw new Error("exports.foo !== require.cache[require.resolve('./require-cache-fixture-b')]"); +} + +delete require.cache[require.resolve("./require-cache-fixture-b.cjs")]; + +exports.bar = require("./require-cache-fixture-b.cjs"); + +if (require.cache[require.resolve("./require-cache-fixture-b.cjs")].exports !== exports.bar) { + throw new Error("exports.bar !== require.cache[require.resolve('./require-cache-fixture-b')]"); +} + +if (require.cache[require.resolve("./require-cache-fixture-b.cjs")].exports === exports.foo) { + throw new Error("exports.bar === exports.foo"); +} + +console.log(require.cache); + +console.log("\n--pass--\n"); diff --git a/test/cli/run/require-cache.test.js b/test/cli/run/require-cache.test.js new file mode 100644 index 000000000..2f22ec139 --- /dev/null +++ b/test/cli/run/require-cache.test.js @@ -0,0 +1,13 @@ +import { test, expect } from "bun:test"; +import { bunEnv, bunExe } from "harness"; +import { join } from "path"; + +test("require.cache", () => { + const { stdout, exitCode } = Bun.spawnSync({ + cmd: [bunExe(), "run", join(import.meta.dir, "require-cache-fixture.cjs")], + env: bunEnv, + }); + + expect(stdout.toString().trim().endsWith("--pass--")).toBe(true); + expect(exitCode).toBe(0); +}); |