|
| 1 | +#include "commonjs.h" |
| 2 | + |
| 3 | +#include "modules.h" |
| 4 | + |
| 5 | +namespace workerd::jsg { |
| 6 | + |
| 7 | +v8::Local<v8::Value> CommonJsModuleContext::require(jsg::Lock& js, kj::String specifier) { |
| 8 | + auto modulesForResolveCallback = getModulesForResolveCallback(js.v8Isolate); |
| 9 | + KJ_REQUIRE(modulesForResolveCallback != nullptr, "didn't expect resolveCallback() now"); |
| 10 | + |
| 11 | + if (isNodeJsCompatEnabled(js)) { |
| 12 | + KJ_IF_SOME(nodeSpec, checkNodeSpecifier(specifier)) { |
| 13 | + specifier = kj::mv(nodeSpec); |
| 14 | + } |
| 15 | + } |
| 16 | + |
| 17 | + kj::Path targetPath = ([&] { |
| 18 | + // If the specifier begins with one of our known prefixes, let's not resolve |
| 19 | + // it against the referrer. |
| 20 | + if (specifier.startsWith("node:") || specifier.startsWith("cloudflare:") || |
| 21 | + specifier.startsWith("workerd:")) { |
| 22 | + return kj::Path::parse(specifier); |
| 23 | + } |
| 24 | + return path.parent().eval(specifier); |
| 25 | + })(); |
| 26 | + |
| 27 | + // require() is only exposed to worker bundle modules so the resolve here is only |
| 28 | + // permitted to require worker bundle or built-in modules. Internal modules are |
| 29 | + // excluded. |
| 30 | + auto& info = JSG_REQUIRE_NONNULL(modulesForResolveCallback->resolve(js, targetPath, path, |
| 31 | + ModuleRegistry::ResolveOption::DEFAULT, |
| 32 | + ModuleRegistry::ResolveMethod::REQUIRE, specifier.asPtr()), |
| 33 | + Error, "No such module \"", targetPath.toString(), "\"."); |
| 34 | + // Adding imported from suffix here not necessary like it is for resolveCallback, since we have a |
| 35 | + // js stack that will include the parent module's name and location of the failed require(). |
| 36 | + |
| 37 | + ModuleRegistry::RequireImplOptions options = ModuleRegistry::RequireImplOptions::DEFAULT; |
| 38 | + if (getCommonJsExportDefault(js.v8Isolate)) { |
| 39 | + options = ModuleRegistry::RequireImplOptions::EXPORT_DEFAULT; |
| 40 | + } |
| 41 | + |
| 42 | + return ModuleRegistry::requireImpl(js, info, options); |
| 43 | +} |
| 44 | + |
| 45 | +CommonJsModuleObject::CommonJsModuleObject(jsg::Lock& js) |
| 46 | + : exports(js.v8Isolate, v8::Object::New(js.v8Isolate)) {} |
| 47 | + |
| 48 | +v8::Local<v8::Value> CommonJsModuleObject::getExports(jsg::Lock& js) { |
| 49 | + return exports.getHandle(js); |
| 50 | +} |
| 51 | +void CommonJsModuleObject::setExports(jsg::Value value) { |
| 52 | + exports = kj::mv(value); |
| 53 | +} |
| 54 | + |
| 55 | +void CommonJsModuleObject::visitForMemoryInfo(MemoryTracker& tracker) const { |
| 56 | + tracker.trackField("exports", exports); |
| 57 | +} |
| 58 | + |
| 59 | +// ====================================================================================== |
| 60 | + |
| 61 | +NodeJsModuleContext::NodeJsModuleContext(jsg::Lock& js, kj::Path path) |
| 62 | + : module(jsg::alloc<NodeJsModuleObject>(js, path.toString(true))), |
| 63 | + path(kj::mv(path)), |
| 64 | + exports(js.v8Ref(module->getExports(js))) {} |
| 65 | + |
| 66 | +v8::Local<v8::Value> NodeJsModuleContext::require(jsg::Lock& js, kj::String specifier) { |
| 67 | + // If it is a bare specifier known to be a Node.js built-in, then prefix the |
| 68 | + // specifier with node: |
| 69 | + bool isNodeBuiltin = false; |
| 70 | + auto resolveOption = jsg::ModuleRegistry::ResolveOption::DEFAULT; |
| 71 | + KJ_IF_SOME(spec, checkNodeSpecifier(specifier)) { |
| 72 | + specifier = kj::mv(spec); |
| 73 | + isNodeBuiltin = true; |
| 74 | + resolveOption = jsg::ModuleRegistry::ResolveOption::BUILTIN_ONLY; |
| 75 | + } |
| 76 | + |
| 77 | + // TODO(cleanup): This implementation from here on is identical to the |
| 78 | + // CommonJsModuleContext::require. We should consolidate these as the |
| 79 | + // next step. |
| 80 | + |
| 81 | + auto modulesForResolveCallback = jsg::getModulesForResolveCallback(js.v8Isolate); |
| 82 | + KJ_REQUIRE(modulesForResolveCallback != nullptr, "didn't expect resolveCallback() now"); |
| 83 | + |
| 84 | + kj::Path targetPath = ([&] { |
| 85 | + // If the specifier begins with one of our known prefixes, let's not resolve |
| 86 | + // it against the referrer. |
| 87 | + if (specifier.startsWith("node:") || specifier.startsWith("cloudflare:") || |
| 88 | + specifier.startsWith("workerd:")) { |
| 89 | + return kj::Path::parse(specifier); |
| 90 | + } |
| 91 | + return path.parent().eval(specifier); |
| 92 | + })(); |
| 93 | + |
| 94 | + // require() is only exposed to worker bundle modules so the resolve here is only |
| 95 | + // permitted to require worker bundle or built-in modules. Internal modules are |
| 96 | + // excluded. |
| 97 | + auto& info = |
| 98 | + JSG_REQUIRE_NONNULL(modulesForResolveCallback->resolve(js, targetPath, path, resolveOption, |
| 99 | + ModuleRegistry::ResolveMethod::REQUIRE, specifier.asPtr()), |
| 100 | + Error, "No such module \"", targetPath.toString(), "\"."); |
| 101 | + // Adding imported from suffix here not necessary like it is for resolveCallback, since we have a |
| 102 | + // js stack that will include the parent module's name and location of the failed require(). |
| 103 | + |
| 104 | + if (!isNodeBuiltin) { |
| 105 | + JSG_REQUIRE_NONNULL( |
| 106 | + info.maybeSynthetic, TypeError, "Cannot use require() to import an ES Module."); |
| 107 | + } |
| 108 | + |
| 109 | + return ModuleRegistry::requireImpl(js, info, ModuleRegistry::RequireImplOptions::EXPORT_DEFAULT); |
| 110 | +} |
| 111 | + |
| 112 | +v8::Local<v8::Value> NodeJsModuleContext::getBuffer(jsg::Lock& js) { |
| 113 | + auto value = require(js, kj::str("node:buffer")); |
| 114 | + JSG_REQUIRE(value->IsObject(), TypeError, "Invalid node:buffer implementation"); |
| 115 | + auto module = value.As<v8::Object>(); |
| 116 | + auto buffer = js.v8Get(module, "Buffer"_kj); |
| 117 | + JSG_REQUIRE(buffer->IsFunction(), TypeError, "Invalid node:buffer implementation"); |
| 118 | + return buffer; |
| 119 | +} |
| 120 | + |
| 121 | +v8::Local<v8::Value> NodeJsModuleContext::getProcess(jsg::Lock& js) { |
| 122 | + auto value = require(js, kj::str("node:process")); |
| 123 | + JSG_REQUIRE(value->IsObject(), TypeError, "Invalid node:process implementation"); |
| 124 | + return value; |
| 125 | +} |
| 126 | + |
| 127 | +kj::String NodeJsModuleContext::getFilename() { |
| 128 | + return path.toString(true); |
| 129 | +} |
| 130 | + |
| 131 | +kj::String NodeJsModuleContext::getDirname() { |
| 132 | + return path.parent().toString(true); |
| 133 | +} |
| 134 | + |
| 135 | +jsg::Ref<NodeJsModuleObject> NodeJsModuleContext::getModule(jsg::Lock& js) { |
| 136 | + return module.addRef(); |
| 137 | +} |
| 138 | + |
| 139 | +v8::Local<v8::Value> NodeJsModuleContext::getExports(jsg::Lock& js) { |
| 140 | + return exports.getHandle(js); |
| 141 | +} |
| 142 | + |
| 143 | +void NodeJsModuleContext::setExports(jsg::Value value) { |
| 144 | + exports = kj::mv(value); |
| 145 | +} |
| 146 | + |
| 147 | +NodeJsModuleObject::NodeJsModuleObject(jsg::Lock& js, kj::String path) |
| 148 | + : exports(js.v8Isolate, v8::Object::New(js.v8Isolate)), |
| 149 | + path(kj::mv(path)) {} |
| 150 | + |
| 151 | +v8::Local<v8::Value> NodeJsModuleObject::getExports(jsg::Lock& js) { |
| 152 | + return exports.getHandle(js); |
| 153 | +} |
| 154 | + |
| 155 | +void NodeJsModuleObject::setExports(jsg::Value value) { |
| 156 | + exports = kj::mv(value); |
| 157 | +} |
| 158 | + |
| 159 | +kj::StringPtr NodeJsModuleObject::getPath() { |
| 160 | + return path; |
| 161 | +} |
| 162 | + |
| 163 | +} // namespace workerd::jsg |
0 commit comments