Skip to content
File

Blob: src/workerd/api/node/module.c++

4.4 KB
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 
10namespace workerd::api::node {
11 
12bool ModuleUtil::isBuiltin(kj::String specifier) {
13 return jsg::checkNodeSpecifier(specifier) != kj::none;
14}
15 
16jsg::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