File
Blob: src/workerd/api/node/module.c++
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | #include "module.h" |
| 5 | |
| 6 | #include <workerd/io/features.h> |
| 7 | #include <workerd/jsg/modules-new.h> |
| 8 | #include <workerd/jsg/url.h> |
| 9 | |
| 10 | namespace workerd::api::node { |
| 11 | |
| 12 | bool ModuleUtil::isBuiltin(kj::String specifier) { |
| 13 | return jsg::checkNodeSpecifier(specifier) != kj::none; |
| 14 | } |
| 15 | |
| 16 | jsg::JsValue ModuleUtil::createRequire(jsg::Lock& js, kj::String path) { |
| 17 | // Node.js requires that the specifier path is a File URL or an absolute |
| 18 | // file path string. To be compliant, we will convert whatever specifier |
| 19 | // is into a File URL if possible, then take the path as the actual |
| 20 | // specifier to use. |
| 21 | auto parsed = JSG_REQUIRE_NONNULL(jsg::Url::tryParse(path.asPtr(), "file:///"_kj), TypeError, |
| 22 | "The argument must be a file URL object, " |
| 23 | "a file URL string, or an absolute path string."); |
| 24 | |
| 25 | if (FeatureFlags::get(js).getNewModuleRegistry()) { |
| 26 | return jsg::JsValue(js.wrapReturningFunction(js.v8Context(), |
| 27 | [referrer = parsed.clone()](jsg::Lock& js, |
| 28 | const v8::FunctionCallbackInfo<v8::Value>& args) -> v8::Local<v8::Value> { |
| 29 | auto specifier = kj::str(args[0]); |
| 30 | if (jsg::isNodeJsCompatEnabled(js)) { |
| 31 | KJ_IF_SOME(nodeSpec, jsg::checkNodeSpecifier(specifier)) { |
| 32 | specifier = kj::mv(nodeSpec); |
| 33 | } |
| 34 | } |
| 35 | KJ_IF_SOME(val, |
| 36 | jsg::modules::ModuleRegistry::tryResolveModuleNamespace(js, specifier, |
| 37 | jsg::modules::ResolveContext::Type::BUNDLE, |
| 38 | jsg::modules::ResolveContext::Source::REQUIRE, referrer, |
| 39 | jsg::modules::UnwrapDefault::YES)) { |
| 40 | return val; |
| 41 | } |
| 42 | JSG_FAIL_REQUIRE(Error, kj::str("Module not found: ", specifier)); |
| 43 | })); |
| 44 | } |
| 45 | |
| 46 | // We do not currently handle specifiers as URLs, so let's treat any |
| 47 | // input that has query string params or hash fragments as errors. |
| 48 | if (parsed.getSearch().size() > 0 || parsed.getHash().size() > 0) { |
| 49 | JSG_FAIL_REQUIRE( |
| 50 | Error, "The specifier must not have query string parameters or hash fragments."); |
| 51 | } |
| 52 | |
| 53 | // The specifier must be a file: URL |
| 54 | JSG_REQUIRE(parsed.getProtocol() == "file:"_kj, TypeError, "The specifier must be a file: URL."); |
| 55 | |
| 56 | return jsg::JsValue(js.wrapReturningFunction(js.v8Context(), |
| 57 | [referrer = kj::str(parsed.getPathname())]( |
| 58 | jsg::Lock& js, const v8::FunctionCallbackInfo<v8::Value>& args) -> v8::Local<v8::Value> { |
| 59 | auto registry = jsg::ModuleRegistry::from(js); |
| 60 | |
| 61 | // TODO(soon): This will need to be updated to support the new module registry |
| 62 | // when that is fully implemented. |
| 63 | JSG_REQUIRE(registry != nullptr, Error, "Module registry not available."); |
| 64 | |
| 65 | auto ref = ([&] { |
| 66 | try { |
| 67 | return kj::Path::parse(referrer.slice(1)); |
| 68 | } catch (kj::Exception& e) { |
| 69 | JSG_FAIL_REQUIRE(Error, kj::str("Invalid referrer path: ", referrer.slice(1))); |
| 70 | } |
| 71 | })(); |
| 72 | |
| 73 | auto spec = kj::str(args[0]); |
| 74 | |
| 75 | if (jsg::isNodeJsCompatEnabled(js)) { |
| 76 | KJ_IF_SOME(nodeSpec, jsg::checkNodeSpecifier(spec)) { |
| 77 | spec = kj::mv(nodeSpec); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | static const kj::Path kRoot = kj::Path::parse(""); |
| 82 | |
| 83 | kj::Path targetPath = ([&] { |
| 84 | // If the specifier begins with one of our known prefixes, let's not resolve |
| 85 | // it against the referrer. |
| 86 | try { |
| 87 | if (spec.startsWith("node:") || spec.startsWith("cloudflare:") || |
| 88 | spec.startsWith("workerd:")) { |
| 89 | return kj::Path::parse(spec); |
| 90 | } |
| 91 | |
| 92 | return ref == kRoot ? kj::Path::parse(spec) : ref.parent().eval(spec); |
| 93 | } catch (kj::Exception&) { |
| 94 | JSG_FAIL_REQUIRE(Error, kj::str("Invalid specifier path: ", spec)); |
| 95 | } |
| 96 | })(); |
| 97 | |
| 98 | // require() is only exposed to worker bundle modules so the resolve here is only |
| 99 | // permitted to require worker bundle or built-in modules. Internal modules are |
| 100 | // excluded. |
| 101 | auto& info = JSG_REQUIRE_NONNULL( |
| 102 | registry->resolve(js, targetPath, ref, jsg::ModuleRegistry::ResolveOption::DEFAULT, |
| 103 | jsg::ModuleRegistry::ResolveMethod::REQUIRE, spec.asPtr()), |
| 104 | Error, "No such module \"", targetPath.toString(), "\"."); |
| 105 | |
| 106 | jsg::ModuleRegistry::RequireImplOptions options = |
| 107 | jsg::ModuleRegistry::RequireImplOptions::DEFAULT; |
| 108 | if (info.maybeSynthetic != kj::none) { |
| 109 | options = jsg::ModuleRegistry::RequireImplOptions::EXPORT_DEFAULT; |
| 110 | } |
| 111 | |
| 112 | return jsg::ModuleRegistry::requireImpl(js, info, options); |
| 113 | })); |
| 114 | } |
| 115 | |
| 116 | } // namespace workerd::api::node |