File
Blob: src/workerd/api/commonjs.c++
| 1 | #include "commonjs.h" |
| 2 | |
| 3 | #include <workerd/io/features.h> |
| 4 | #include <workerd/jsg/jsg.h> |
| 5 | #include <workerd/jsg/modules-new.h> |
| 6 | #include <workerd/jsg/resource.h> |
| 7 | |
| 8 | namespace workerd::api { |
| 9 | |
| 10 | CommonJsModuleContext::CommonJsModuleContext(jsg::Lock& js, kj::Path path) |
| 11 | : module(js.alloc<CommonJsModuleObject>(js, path.toString(true))), |
| 12 | pathOrSpecifier(kj::mv(path)), |
| 13 | exports(js, module->getExports(js)) {} |
| 14 | |
| 15 | CommonJsModuleContext::CommonJsModuleContext(jsg::Lock& js, const jsg::Url& specifier) |
| 16 | : module(js.alloc<CommonJsModuleObject>(js, kj::str(specifier.getHref()))), |
| 17 | pathOrSpecifier(specifier.clone()), |
| 18 | exports(js, module->getExports(js)) {} |
| 19 | |
| 20 | jsg::JsValue CommonJsModuleContext::require(jsg::Lock& js, kj::String specifier) { |
| 21 | if (isNodeJsCompatEnabled(js)) { |
| 22 | KJ_IF_SOME(nodeSpec, jsg::checkNodeSpecifier(specifier)) { |
| 23 | specifier = kj::mv(nodeSpec); |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | if (FeatureFlags::get(js).getNewModuleRegistry()) { |
| 28 | auto& referrer = KJ_ASSERT_NONNULL(pathOrSpecifier.tryGet<jsg::Url>()); |
| 29 | KJ_IF_SOME(ns, |
| 30 | jsg::modules::ModuleRegistry::tryResolveModuleNamespace(js, specifier, |
| 31 | jsg::modules::ResolveContext::Type::BUNDLE, |
| 32 | jsg::modules::ResolveContext::Source::REQUIRE, referrer, |
| 33 | jsg::modules::UnwrapDefault::YES)) { |
| 34 | return ns; |
| 35 | } |
| 36 | JSG_FAIL_REQUIRE(Error, kj::str("Module not found: ", specifier)); |
| 37 | } |
| 38 | |
| 39 | auto& path = KJ_ASSERT_NONNULL(pathOrSpecifier.tryGet<kj::Path>()); |
| 40 | |
| 41 | auto modulesForResolveCallback = jsg::getModulesForResolveCallback(js.v8Isolate); |
| 42 | KJ_REQUIRE(modulesForResolveCallback != nullptr, "didn't expect resolveCallback() now"); |
| 43 | |
| 44 | kj::Path targetPath = ([&] { |
| 45 | // If the specifier begins with one of our known prefixes, let's not resolve |
| 46 | // it against the referrer. |
| 47 | if (specifier.startsWith("node:") || specifier.startsWith("cloudflare:") || |
| 48 | specifier.startsWith("workerd:")) { |
| 49 | return kj::Path::parse(specifier); |
| 50 | } |
| 51 | return path.parent().eval(specifier); |
| 52 | })(); |
| 53 | |
| 54 | // require() is only exposed to worker bundle modules so the resolve here is only |
| 55 | // permitted to require worker bundle or built-in modules. Internal modules are |
| 56 | // excluded. |
| 57 | auto& info = |
| 58 | JSG_REQUIRE_NONNULL(modulesForResolveCallback->resolve(js, targetPath, path, |
| 59 | jsg::ModuleRegistry::ResolveOption::DEFAULT, |
| 60 | jsg::ModuleRegistry::ResolveMethod::REQUIRE, specifier.asPtr()), |
| 61 | Error, "No such module \"", targetPath.toString(), "\"."); |
| 62 | // Adding imported from suffix here not necessary like it is for resolveCallback, since we have a |
| 63 | // js stack that will include the parent module's name and location of the failed require(). |
| 64 | |
| 65 | auto options = jsg::ModuleRegistry::RequireImplOptions::DEFAULT; |
| 66 | if (FeatureFlags::get(js).getExportCommonJsDefaultNamespace()) { |
| 67 | options = jsg::ModuleRegistry::RequireImplOptions::EXPORT_DEFAULT; |
| 68 | } |
| 69 | |
| 70 | return jsg::ModuleRegistry::requireImpl(js, info, options); |
| 71 | } |
| 72 | |
| 73 | void CommonJsModuleContext::visitForMemoryInfo(jsg::MemoryTracker& tracker) const { |
| 74 | tracker.trackField("exports", exports); |
| 75 | KJ_SWITCH_ONEOF(pathOrSpecifier) { |
| 76 | KJ_CASE_ONEOF(path, kj::Path) { |
| 77 | tracker.trackFieldWithSize("path", path.size()); |
| 78 | } |
| 79 | KJ_CASE_ONEOF(specifier, jsg::Url) { |
| 80 | tracker.trackField("specifier", specifier); |
| 81 | } |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | kj::String CommonJsModuleContext::getFilename() const { |
| 86 | KJ_SWITCH_ONEOF(pathOrSpecifier) { |
| 87 | KJ_CASE_ONEOF(path, kj::Path) { |
| 88 | return path.toString(true); |
| 89 | } |
| 90 | KJ_CASE_ONEOF(specifier, jsg::Url) { |
| 91 | // The specifier is a URL. We want to parse it as a path and |
| 92 | // return just the filename portion. |
| 93 | // TODO(soon): kj::Path::parse() requires a kj::StringPtr but |
| 94 | // the path name here is a kj::ArrayPtr<const char>. We can |
| 95 | // avoid an extraneous copy here by updating kj::Path::parse |
| 96 | // to also accept a kj::ArrayPtr<const char>. |
| 97 | auto path = kj::str(specifier.getPathname().slice(1)); |
| 98 | auto filename = kj::Path::parse(path).basename(); |
| 99 | return filename.toString(false); |
| 100 | } |
| 101 | } |
| 102 | KJ_UNREACHABLE; |
| 103 | } |
| 104 | |
| 105 | kj::String CommonJsModuleContext::getDirname() const { |
| 106 | KJ_SWITCH_ONEOF(pathOrSpecifier) { |
| 107 | KJ_CASE_ONEOF(path, kj::Path) { |
| 108 | return path.parent().toString(true); |
| 109 | } |
| 110 | KJ_CASE_ONEOF(specifier, jsg::Url) { |
| 111 | // The specifier is a URL. We want to parse it as a path and |
| 112 | // return just the directory portion. |
| 113 | auto path = kj::str(specifier.getPathname().slice(1)); |
| 114 | auto pathObj = kj::Path::parse(path); |
| 115 | return pathObj.parent().toString(true); |
| 116 | } |
| 117 | } |
| 118 | KJ_UNREACHABLE; |
| 119 | } |
| 120 | |
| 121 | jsg::Ref<CommonJsModuleObject> CommonJsModuleContext::getModule(jsg::Lock& js) { |
| 122 | return module.addRef(); |
| 123 | } |
| 124 | |
| 125 | jsg::JsValue CommonJsModuleContext::getExports(jsg::Lock& js) const { |
| 126 | return exports.getHandle(js); |
| 127 | } |
| 128 | void CommonJsModuleContext::setExports(jsg::Lock& js, jsg::JsValue value) { |
| 129 | exports = jsg::JsRef(js, value); |
| 130 | } |
| 131 | |
| 132 | CommonJsModuleObject::CommonJsModuleObject(jsg::Lock& js, kj::String path) |
| 133 | : exports(js, js.obj()), |
| 134 | path(kj::mv(path)) {} |
| 135 | |
| 136 | jsg::JsValue CommonJsModuleObject::getExports(jsg::Lock& js) const { |
| 137 | return exports.getHandle(js); |
| 138 | } |
| 139 | void CommonJsModuleObject::setExports(jsg::Lock& js, jsg::JsValue value) { |
| 140 | exports = jsg::JsRef(js, value); |
| 141 | } |
| 142 | |
| 143 | kj::StringPtr CommonJsModuleObject::getPath() const { |
| 144 | return path; |
| 145 | } |
| 146 | |
| 147 | void CommonJsModuleObject::visitForMemoryInfo(jsg::MemoryTracker& tracker) const { |
| 148 | tracker.trackField("exports", exports); |
| 149 | tracker.trackField("path", path); |
| 150 | } |
| 151 | } // namespace workerd::api |