aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-05-24 12:01:59 -0700
committerGravatar GitHub <noreply@github.com> 2023-05-24 12:01:59 -0700
commitb3d5f37598ea4ca37a863f05ade94637477f3700 (patch)
tree7469b97e44f2af3d6fa857a5bf96de267a657344
parent31c967206ab0d3cb5c0e4bd636fa9668e778ec61 (diff)
downloadbun-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.cpp21
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp13
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h3
-rw-r--r--src/bun.js/builtins/WebCoreJSBuiltins.cpp8
-rw-r--r--src/bun.js/builtins/WebCoreJSBuiltins.h11
-rw-r--r--src/bun.js/builtins/ts/ImportMetaObject.ts93
-rw-r--r--test/cli/run/require-cache-fixture-b.cjs3
-rw-r--r--test/cli/run/require-cache-fixture.cjs23
-rw-r--r--test/cli/run/require-cache.test.js13
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);
+});