summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorguybedford <guybedford@gmail.com>2018-08-28 17:28:46 +0200
committerMyles Borins <mylesborins@google.com>2019-03-27 15:52:11 -0400
commitb1094dbe19f31f7a69ad16d193748f610b159073 (patch)
treebb623ea607ed54c27ef36ee9453f5623e19cbb90 /src
parent39141426d46a7e55d93ad2e8efa12ed86d223522 (diff)
downloadandroid-node-v8-b1094dbe19f31f7a69ad16d193748f610b159073.tar.gz
android-node-v8-b1094dbe19f31f7a69ad16d193748f610b159073.tar.bz2
android-node-v8-b1094dbe19f31f7a69ad16d193748f610b159073.zip
esm: phase two of new esm implementation
This PR updates the current `--experimental-modules` implementation based on the work of the modules team and reflects Phase 2 of our new modules plan. The largest differences from the current implementation include * `packge.type` which can be either `module` or `commonjs` - `type: "commonjs"`: - `.js` is parsed as commonjs - default for entry point without an extension is commonjs - `type: "module"`: - `.js` is parsed as esm - does not support loading JSON or Native Module by default - default for entry point without an extension is esm * `--entry-type=[mode]` - allows you set the type on entry point. * A new file extension `.cjs`. - this is specifically to support importing commonjs in the `module` mode. - this is only in the esm loader, the commonjs loader remains untouched, but the extension will work in the old loader if you use the full file path. * `--es-module-specifier-resolution=[type]` - options are `explicit` (default) and `node` - by default our loader will not allow for optional extensions in the import, the path for a module must include the extension if there is one - by default our loader will not allow for importing directories that have an index file - developers can use `--es-module-specifier-resolution=node` to enable the commonjs specifier resolution algorithm - This is not a “feature” but rather an implementation for experimentation. It is expected to change before the flag is removed * `--experimental-json-loader` - the only way to import json when `"type": "module"` - when enable all `import 'thing.json'` will go through the experimental loader independent of mode - based on https://github.com/whatwg/html/issues/4315 * You can use `package.main` to set an entry point for a module - the file extensions used in main will be resolved based on the `type` of the module Refs: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md Refs: https://github.com/GeoffreyBooth/node-import-file-specifier-resolution-proposal Refs: https://github.com/nodejs/modules/pull/180 Refs: https://github.com/nodejs/ecmascript-modules/pull/6 Refs: https://github.com/nodejs/ecmascript-modules/pull/12 Refs: https://github.com/nodejs/ecmascript-modules/pull/28 Refs: https://github.com/nodejs/modules/issues/255 Refs: https://github.com/whatwg/html/issues/4315 Refs: https://github.com/w3c/webcomponents/issues/770 Co-authored-by: Myles Borins <MylesBorins@google.com> Co-authored-by: John-David Dalton <john.david.dalton@gmail.com> Co-authored-by: Evan Plaice <evanplaice@gmail.com> Co-authored-by: Geoffrey Booth <webmaster@geoffreybooth.com> Co-authored-by: Michaël Zasso <targos@protonmail.com> PR-URL: https://github.com/nodejs/node/pull/26745 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Ben Coe <bencoe@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/env.h11
-rw-r--r--src/module_wrap.cc490
-rw-r--r--src/module_wrap.h11
-rw-r--r--src/node_errors.h4
-rw-r--r--src/node_options.cc45
-rw-r--r--src/node_options.h3
6 files changed, 411 insertions, 153 deletions
diff --git a/src/env.h b/src/env.h
index 55018b6d40..743c236827 100644
--- a/src/env.h
+++ b/src/env.h
@@ -77,11 +77,13 @@ struct PackageConfig {
enum class Exists { Yes, No };
enum class IsValid { Yes, No };
enum class HasMain { Yes, No };
+ enum PackageType : uint32_t { None = 0, CommonJS, Module };
- Exists exists;
- IsValid is_valid;
- HasMain has_main;
- std::string main;
+ const Exists exists;
+ const IsValid is_valid;
+ const HasMain has_main;
+ const std::string main;
+ const PackageType type;
};
} // namespace loader
@@ -141,6 +143,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
V(channel_string, "channel") \
V(chunks_sent_since_last_write_string, "chunksSentSinceLastWrite") \
V(code_string, "code") \
+ V(commonjs_string, "commonjs") \
V(config_string, "config") \
V(constants_string, "constants") \
V(crypto_dsa_string, "dsa") \
diff --git a/src/module_wrap.cc b/src/module_wrap.cc
index 56149d0cc7..622deed45f 100644
--- a/src/module_wrap.cc
+++ b/src/module_wrap.cc
@@ -29,7 +29,6 @@ using v8::HandleScope;
using v8::Integer;
using v8::IntegrityLevel;
using v8::Isolate;
-using v8::JSON;
using v8::Just;
using v8::Local;
using v8::Maybe;
@@ -46,7 +45,13 @@ using v8::String;
using v8::Undefined;
using v8::Value;
-static const char* const EXTENSIONS[] = {".mjs", ".js", ".json", ".node"};
+static const char* const EXTENSIONS[] = {
+ ".mjs",
+ ".cjs",
+ ".js",
+ ".json",
+ ".node"
+};
ModuleWrap::ModuleWrap(Environment* env,
Local<Object> object,
@@ -471,100 +476,233 @@ std::string ReadFile(uv_file file) {
return contents;
}
-enum CheckFileOptions {
- LEAVE_OPEN_AFTER_CHECK,
- CLOSE_AFTER_CHECK
+enum DescriptorType {
+ FILE,
+ DIRECTORY,
+ NONE
};
-Maybe<uv_file> CheckFile(const std::string& path,
- CheckFileOptions opt = CLOSE_AFTER_CHECK) {
+// When DescriptorType cache is added, this can also return
+// Nothing for the "null" cache entries.
+inline Maybe<uv_file> OpenDescriptor(const std::string& path) {
uv_fs_t fs_req;
- if (path.empty()) {
- return Nothing<uv_file>();
- }
-
uv_file fd = uv_fs_open(nullptr, &fs_req, path.c_str(), O_RDONLY, 0, nullptr);
uv_fs_req_cleanup(&fs_req);
+ if (fd < 0) return Nothing<uv_file>();
+ return Just(fd);
+}
- if (fd < 0) {
- return Nothing<uv_file>();
- }
-
- uv_fs_fstat(nullptr, &fs_req, fd, nullptr);
- uint64_t is_directory = fs_req.statbuf.st_mode & S_IFDIR;
+inline void CloseDescriptor(uv_file fd) {
+ uv_fs_t fs_req;
+ uv_fs_close(nullptr, &fs_req, fd, nullptr);
uv_fs_req_cleanup(&fs_req);
+}
- if (is_directory) {
- CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, fd, nullptr));
+inline DescriptorType CheckDescriptorAtFile(uv_file fd) {
+ uv_fs_t fs_req;
+ int rc = uv_fs_fstat(nullptr, &fs_req, fd, nullptr);
+ if (rc == 0) {
+ uint64_t is_directory = fs_req.statbuf.st_mode & S_IFDIR;
uv_fs_req_cleanup(&fs_req);
- return Nothing<uv_file>();
+ return is_directory ? DIRECTORY : FILE;
}
+ uv_fs_req_cleanup(&fs_req);
+ return NONE;
+}
- if (opt == CLOSE_AFTER_CHECK) {
- CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, fd, nullptr));
- uv_fs_req_cleanup(&fs_req);
- }
+// TODO(@guybedford): Add a DescriptorType cache layer here.
+// Should be directory based -> if path/to/dir doesn't exist
+// then the cache should early-fail any path/to/dir/file check.
+DescriptorType CheckDescriptorAtPath(const std::string& path) {
+ Maybe<uv_file> fd = OpenDescriptor(path);
+ if (fd.IsNothing()) return NONE;
+ DescriptorType type = CheckDescriptorAtFile(fd.FromJust());
+ CloseDescriptor(fd.FromJust());
+ return type;
+}
- return Just(fd);
+Maybe<std::string> ReadIfFile(const std::string& path) {
+ Maybe<uv_file> fd = OpenDescriptor(path);
+ if (fd.IsNothing()) return Nothing<std::string>();
+ DescriptorType type = CheckDescriptorAtFile(fd.FromJust());
+ if (type != FILE) return Nothing<std::string>();
+ std::string source = ReadFile(fd.FromJust());
+ CloseDescriptor(fd.FromJust());
+ return Just(source);
}
using Exists = PackageConfig::Exists;
using IsValid = PackageConfig::IsValid;
using HasMain = PackageConfig::HasMain;
+using PackageType = PackageConfig::PackageType;
-const PackageConfig& GetPackageConfig(Environment* env,
- const std::string& path) {
+Maybe<const PackageConfig*> GetPackageConfig(Environment* env,
+ const std::string& path,
+ const URL& base) {
auto existing = env->package_json_cache.find(path);
if (existing != env->package_json_cache.end()) {
- return existing->second;
+ const PackageConfig* pcfg = &existing->second;
+ if (pcfg->is_valid == IsValid::No) {
+ std::string msg = "Invalid JSON in '" + path +
+ "' imported from " + base.ToFilePath();
+ node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str());
+ return Nothing<const PackageConfig*>();
+ }
+ return Just(pcfg);
}
- Maybe<uv_file> check = CheckFile(path, LEAVE_OPEN_AFTER_CHECK);
- if (check.IsNothing()) {
+
+ Maybe<std::string> source = ReadIfFile(path);
+
+ if (source.IsNothing()) {
auto entry = env->package_json_cache.emplace(path,
- PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "" });
- return entry.first->second;
+ PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "",
+ PackageType::None });
+ return Just(&entry.first->second);
}
+ std::string pkg_src = source.FromJust();
+
Isolate* isolate = env->isolate();
v8::HandleScope handle_scope(isolate);
- std::string pkg_src = ReadFile(check.FromJust());
- uv_fs_t fs_req;
- CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, check.FromJust(), nullptr));
- uv_fs_req_cleanup(&fs_req);
-
- Local<String> src;
- if (!String::NewFromUtf8(isolate,
- pkg_src.c_str(),
- v8::NewStringType::kNormal,
- pkg_src.length()).ToLocal(&src)) {
- auto entry = env->package_json_cache.emplace(path,
- PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "" });
- return entry.first->second;
- }
-
- Local<Value> pkg_json_v;
Local<Object> pkg_json;
-
- if (!JSON::Parse(env->context(), src).ToLocal(&pkg_json_v) ||
- !pkg_json_v->ToObject(env->context()).ToLocal(&pkg_json)) {
- auto entry = env->package_json_cache.emplace(path,
- PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "" });
- return entry.first->second;
+ {
+ Local<Value> src;
+ Local<Value> pkg_json_v;
+ Local<Context> context = env->context();
+
+ if (!ToV8Value(context, pkg_src).ToLocal(&src) ||
+ !v8::JSON::Parse(context, src.As<String>()).ToLocal(&pkg_json_v) ||
+ !pkg_json_v->ToObject(context).ToLocal(&pkg_json)) {
+ env->package_json_cache.emplace(path,
+ PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "",
+ PackageType::None });
+ std::string msg = "Invalid JSON in '" + path +
+ "' imported from " + base.ToFilePath();
+ node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str());
+ return Nothing<const PackageConfig*>();
+ }
}
Local<Value> pkg_main;
HasMain has_main = HasMain::No;
std::string main_std;
if (pkg_json->Get(env->context(), env->main_string()).ToLocal(&pkg_main)) {
- has_main = HasMain::Yes;
+ if (pkg_main->IsString()) {
+ has_main = HasMain::Yes;
+ }
Utf8Value main_utf8(isolate, pkg_main);
main_std.assign(std::string(*main_utf8, main_utf8.length()));
}
+ PackageType pkg_type = PackageType::None;
+ Local<Value> type_v;
+ if (pkg_json->Get(env->context(), env->type_string()).ToLocal(&type_v)) {
+ if (type_v->StrictEquals(env->module_string())) {
+ pkg_type = PackageType::Module;
+ } else if (type_v->StrictEquals(env->commonjs_string())) {
+ pkg_type = PackageType::CommonJS;
+ }
+ // ignore unknown types for forwards compatibility
+ }
+
+ Local<Value> exports_v;
+ if (pkg_json->Get(env->context(),
+ env->exports_string()).ToLocal(&exports_v) &&
+ (exports_v->IsObject() || exports_v->IsString() ||
+ exports_v->IsBoolean())) {
+ Persistent<Value> exports;
+ exports.Reset(env->isolate(), exports_v);
+
+ auto entry = env->package_json_cache.emplace(path,
+ PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std,
+ pkg_type });
+ return Just(&entry.first->second);
+ }
+
auto entry = env->package_json_cache.emplace(path,
- PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std });
- return entry.first->second;
+ PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std,
+ pkg_type });
+ return Just(&entry.first->second);
+}
+
+Maybe<const PackageConfig*> GetPackageScopeConfig(Environment* env,
+ const URL& resolved,
+ const URL& base) {
+ URL pjson_url("./package.json", &resolved);
+ while (true) {
+ Maybe<const PackageConfig*> pkg_cfg =
+ GetPackageConfig(env, pjson_url.ToFilePath(), base);
+ if (pkg_cfg.IsNothing()) return pkg_cfg;
+ if (pkg_cfg.FromJust()->exists == Exists::Yes) return pkg_cfg;
+
+ URL last_pjson_url = pjson_url;
+ pjson_url = URL("../package.json", pjson_url);
+
+ // Terminates at root where ../package.json equals ../../package.json
+ // (can't just check "/package.json" for Windows support).
+ if (pjson_url.path() == last_pjson_url.path()) {
+ auto entry = env->package_json_cache.emplace(pjson_url.ToFilePath(),
+ PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "",
+ PackageType::None });
+ const PackageConfig* pcfg = &entry.first->second;
+ return Just(pcfg);
+ }
+ }
+}
+
+/*
+ * Legacy CommonJS main resolution:
+ * 1. let M = pkg_url + (json main field)
+ * 2. TRY(M, M.js, M.json, M.node)
+ * 3. TRY(M/index.js, M/index.json, M/index.node)
+ * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
+ * 5. NOT_FOUND
+ */
+inline bool FileExists(const URL& url) {
+ return CheckDescriptorAtPath(url.ToFilePath()) == FILE;
+}
+Maybe<URL> LegacyMainResolve(const URL& pjson_url,
+ const PackageConfig& pcfg) {
+ URL guess;
+ if (pcfg.has_main == HasMain::Yes) {
+ // Note: fs check redundances will be handled by Descriptor cache here.
+ if (FileExists(guess = URL("./" + pcfg.main, pjson_url))) {
+ return Just(guess);
+ }
+ if (FileExists(guess = URL("./" + pcfg.main + ".js", pjson_url))) {
+ return Just(guess);
+ }
+ if (FileExists(guess = URL("./" + pcfg.main + ".json", pjson_url))) {
+ return Just(guess);
+ }
+ if (FileExists(guess = URL("./" + pcfg.main + ".node", pjson_url))) {
+ return Just(guess);
+ }
+ if (FileExists(guess = URL("./" + pcfg.main + "/index.js", pjson_url))) {
+ return Just(guess);
+ }
+ // Such stat.
+ if (FileExists(guess = URL("./" + pcfg.main + "/index.json", pjson_url))) {
+ return Just(guess);
+ }
+ if (FileExists(guess = URL("./" + pcfg.main + "/index.node", pjson_url))) {
+ return Just(guess);
+ }
+ // Fallthrough.
+ }
+ if (FileExists(guess = URL("./index.js", pjson_url))) {
+ return Just(guess);
+ }
+ // So fs.
+ if (FileExists(guess = URL("./index.json", pjson_url))) {
+ return Just(guess);
+ }
+ if (FileExists(guess = URL("./index.node", pjson_url))) {
+ return Just(guess);
+ }
+ // Not found.
+ return Nothing<URL>();
}
enum ResolveExtensionsOptions {
@@ -575,17 +713,14 @@ enum ResolveExtensionsOptions {
template <ResolveExtensionsOptions options>
Maybe<URL> ResolveExtensions(const URL& search) {
if (options == TRY_EXACT_NAME) {
- std::string filePath = search.ToFilePath();
- Maybe<uv_file> check = CheckFile(filePath);
- if (!check.IsNothing()) {
+ if (FileExists(search)) {
return Just(search);
}
}
for (const char* extension : EXTENSIONS) {
URL guess(search.path() + extension, &search);
- Maybe<uv_file> check = CheckFile(guess.ToFilePath());
- if (!check.IsNothing()) {
+ if (FileExists(guess)) {
return Just(guess);
}
}
@@ -597,93 +732,150 @@ inline Maybe<URL> ResolveIndex(const URL& search) {
return ResolveExtensions<ONLY_VIA_EXTENSIONS>(URL("index", search));
}
-Maybe<URL> ResolveMain(Environment* env, const URL& search) {
- URL pkg("package.json", &search);
-
- const PackageConfig& pjson =
- GetPackageConfig(env, pkg.ToFilePath());
- // Note invalid package.json should throw in resolver
- // currently we silently ignore which is incorrect
- if (pjson.exists == Exists::No ||
- pjson.is_valid == IsValid::No ||
- pjson.has_main == HasMain::No) {
+Maybe<URL> FinalizeResolution(Environment* env,
+ const URL& resolved,
+ const URL& base) {
+ if (env->options()->es_module_specifier_resolution == "node") {
+ Maybe<URL> file = ResolveExtensions<TRY_EXACT_NAME>(resolved);
+ if (!file.IsNothing()) {
+ return file;
+ }
+ if (resolved.path().back() != '/') {
+ file = ResolveIndex(URL(resolved.path() + "/", &base));
+ } else {
+ file = ResolveIndex(resolved);
+ }
+ if (!file.IsNothing()) {
+ return file;
+ }
+ std::string msg = "Cannot find module '" + resolved.path() +
+ "' imported from " + base.ToFilePath();
+ node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
return Nothing<URL>();
}
- if (!ShouldBeTreatedAsRelativeOrAbsolutePath(pjson.main)) {
- return Resolve(env, "./" + pjson.main, search, IgnoreMain);
+
+ const std::string& path = resolved.ToFilePath();
+ if (CheckDescriptorAtPath(path) != FILE) {
+ std::string msg = "Cannot find module '" + path +
+ "' imported from " + base.ToFilePath();
+ node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
+ return Nothing<URL>();
}
- return Resolve(env, pjson.main, search, IgnoreMain);
+
+ return Just(resolved);
}
-Maybe<URL> ResolveModule(Environment* env,
- const std::string& specifier,
- const URL& base) {
- URL parent(".", base);
- URL dir("");
- do {
- dir = parent;
- Maybe<URL> check =
- Resolve(env, "./node_modules/" + specifier, dir, CheckMain);
- if (!check.IsNothing()) {
- const size_t limit = specifier.find('/');
- const size_t spec_len =
- limit == std::string::npos ? specifier.length() :
- limit + 1;
- std::string chroot =
- dir.path() + "node_modules/" + specifier.substr(0, spec_len);
- if (check.FromJust().path().substr(0, chroot.length()) != chroot) {
- return Nothing<URL>();
+Maybe<URL> PackageMainResolve(Environment* env,
+ const URL& pjson_url,
+ const PackageConfig& pcfg,
+ const URL& base) {
+ if (pcfg.exists == Exists::Yes) {
+ if (pcfg.has_main == HasMain::Yes) {
+ URL resolved(pcfg.main, pjson_url);
+ const std::string& path = resolved.ToFilePath();
+ if (CheckDescriptorAtPath(path) == FILE) {
+ return Just(resolved);
+ }
+ }
+ if (env->options()->es_module_specifier_resolution == "node") {
+ if (pcfg.has_main == HasMain::Yes) {
+ return FinalizeResolution(env, URL(pcfg.main, pjson_url), base);
+ } else {
+ return FinalizeResolution(env, URL("index", pjson_url), base);
+ }
+ }
+ if (pcfg.type != PackageType::Module) {
+ Maybe<URL> resolved = LegacyMainResolve(pjson_url, pcfg);
+ if (!resolved.IsNothing()) {
+ return resolved;
}
- return check;
- } else {
- // TODO(bmeck) PREVENT FALLTHROUGH
}
- parent = URL("..", &dir);
- } while (parent.path() != dir.path());
+ }
+ std::string msg = "Cannot find main entry point for '" +
+ URL(".", pjson_url).ToFilePath() + "' imported from " +
+ base.ToFilePath();
+ node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
return Nothing<URL>();
}
-Maybe<URL> ResolveDirectory(Environment* env,
- const URL& search,
- PackageMainCheck check_pjson_main) {
- if (check_pjson_main) {
- Maybe<URL> main = ResolveMain(env, search);
- if (!main.IsNothing())
- return main;
+Maybe<URL> PackageResolve(Environment* env,
+ const std::string& specifier,
+ const URL& base) {
+ size_t sep_index = specifier.find('/');
+ if (specifier[0] == '@' && (sep_index == std::string::npos ||
+ specifier.length() == 0)) {
+ std::string msg = "Invalid package name '" + specifier +
+ "' imported from " + base.ToFilePath();
+ node::THROW_ERR_INVALID_MODULE_SPECIFIER(env, msg.c_str());
+ return Nothing<URL>();
+ }
+ bool scope = false;
+ if (specifier[0] == '@') {
+ scope = true;
+ sep_index = specifier.find('/', sep_index + 1);
}
- return ResolveIndex(search);
+ std::string pkg_name = specifier.substr(0,
+ sep_index == std::string::npos ? std::string::npos : sep_index);
+ std::string pkg_subpath;
+ if ((sep_index == std::string::npos ||
+ sep_index == specifier.length() - 1)) {
+ pkg_subpath = "";
+ } else {
+ pkg_subpath = "." + specifier.substr(sep_index);
+ }
+ URL pjson_url("./node_modules/" + pkg_name + "/package.json", &base);
+ std::string pjson_path = pjson_url.ToFilePath();
+ std::string last_path;
+ do {
+ DescriptorType check =
+ CheckDescriptorAtPath(pjson_path.substr(0, pjson_path.length() - 13));
+ if (check != DIRECTORY) {
+ last_path = pjson_path;
+ pjson_url = URL((scope ?
+ "../../../../node_modules/" : "../../../node_modules/") +
+ pkg_name + "/package.json", &pjson_url);
+ pjson_path = pjson_url.ToFilePath();
+ continue;
+ }
+
+ // Package match.
+ Maybe<const PackageConfig*> pcfg = GetPackageConfig(env, pjson_path, base);
+ // Invalid package configuration error.
+ if (pcfg.IsNothing()) return Nothing<URL>();
+ if (!pkg_subpath.length()) {
+ return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base);
+ } else {
+ return FinalizeResolution(env, URL(pkg_subpath, pjson_url), base);
+ }
+ CHECK(false);
+ // Cross-platform root check.
+ } while (pjson_path.length() != last_path.length());
+
+ std::string msg = "Cannot find package '" + pkg_name +
+ "' imported from " + base.ToFilePath();
+ node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
+ return Nothing<URL>();
}
} // anonymous namespace
Maybe<URL> Resolve(Environment* env,
const std::string& specifier,
- const URL& base,
- PackageMainCheck check_pjson_main) {
- URL pure_url(specifier);
- if (!(pure_url.flags() & URL_FLAGS_FAILED)) {
- // just check existence, without altering
- Maybe<uv_file> check = CheckFile(pure_url.ToFilePath());
- if (check.IsNothing()) {
- return Nothing<URL>();
- }
- return Just(pure_url);
- }
- if (specifier.length() == 0) {
- return Nothing<URL>();
- }
+ const URL& base) {
+ // Order swapped from spec for minor perf gain.
+ // Ok since relative URLs cannot parse as URLs.
+ URL resolved;
if (ShouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
- URL resolved(specifier, base);
- Maybe<URL> file = ResolveExtensions<TRY_EXACT_NAME>(resolved);
- if (!file.IsNothing())
- return file;
- if (specifier.back() != '/') {
- resolved = URL(specifier + "/", base);
- }
- return ResolveDirectory(env, resolved, check_pjson_main);
+ resolved = URL(specifier, base);
} else {
- return ResolveModule(env, specifier, base);
+ URL pure_url(specifier);
+ if (!(pure_url.flags() & URL_FLAGS_FAILED)) {
+ resolved = pure_url;
+ } else {
+ return PackageResolve(env, specifier, base);
+ }
}
+ return FinalizeResolution(env, resolved, base);
}
void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) {
@@ -705,15 +897,40 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) {
env, "second argument is not a URL string");
}
- Maybe<URL> result = node::loader::Resolve(env, specifier_std, url);
- if (result.IsNothing() || (result.FromJust().flags() & URL_FLAGS_FAILED)) {
- std::string msg = "Cannot find module " + specifier_std;
- return node::THROW_ERR_MISSING_MODULE(env, msg.c_str());
+ Maybe<URL> result =
+ node::loader::Resolve(env,
+ specifier_std,
+ url);
+ if (result.IsNothing()) {
+ return;
+ }
+
+ URL resolution = result.FromJust();
+ CHECK(!(resolution.flags() & URL_FLAGS_FAILED));
+
+ Local<Value> resolution_obj;
+ if (resolution.ToObject(env).ToLocal(&resolution_obj))
+ args.GetReturnValue().Set(resolution_obj);
+}
+
+void ModuleWrap::GetPackageType(const FunctionCallbackInfo<Value>& args) {
+ Environment* env = Environment::GetCurrent(args);
+
+ // module.getPackageType(url)
+ CHECK_EQ(args.Length(), 1);
+
+ CHECK(args[0]->IsString());
+ Utf8Value url_utf8(env->isolate(), args[0]);
+ URL url(*url_utf8, url_utf8.length());
+
+ PackageType pkg_type = PackageType::None;
+ Maybe<const PackageConfig*> pcfg =
+ GetPackageScopeConfig(env, url, url);
+ if (!pcfg.IsNothing()) {
+ pkg_type = pcfg.FromJust()->type;
}
- MaybeLocal<Value> obj = result.FromJust().ToObject(env);
- if (!obj.IsEmpty())
- args.GetReturnValue().Set(obj.ToLocalChecked());
+ args.GetReturnValue().Set(Integer::New(env->isolate(), pkg_type));
}
static MaybeLocal<Promise> ImportModuleDynamically(
@@ -849,6 +1066,7 @@ void ModuleWrap::Initialize(Local<Object> target,
target->Set(env->context(), FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"),
tpl->GetFunction(context).ToLocalChecked()).FromJust();
env->SetMethod(target, "resolve", Resolve);
+ env->SetMethod(target, "getPackageType", GetPackageType);
env->SetMethod(target,
"setImportModuleDynamicallyCallback",
SetImportModuleDynamicallyCallback);
diff --git a/src/module_wrap.h b/src/module_wrap.h
index dc34685fed..6b7025f8a9 100644
--- a/src/module_wrap.h
+++ b/src/module_wrap.h
@@ -12,11 +12,6 @@
namespace node {
namespace loader {
-enum PackageMainCheck : bool {
- CheckMain = true,
- IgnoreMain = false
-};
-
enum ScriptType : int {
kScript,
kModule,
@@ -29,11 +24,6 @@ enum HostDefinedOptions : int {
kLength = 10,
};
-v8::Maybe<url::URL> Resolve(Environment* env,
- const std::string& specifier,
- const url::URL& base,
- PackageMainCheck read_pkg_json = CheckMain);
-
class ModuleWrap : public BaseObject {
public:
static const std::string EXTENSIONS[];
@@ -75,6 +65,7 @@ class ModuleWrap : public BaseObject {
const v8::FunctionCallbackInfo<v8::Value>& args);
static void Resolve(const v8::FunctionCallbackInfo<v8::Value>& args);
+ static void GetPackageType(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetImportModuleDynamicallyCallback(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetInitializeImportMetaObjectCallback(
diff --git a/src/node_errors.h b/src/node_errors.h
index 835794b178..9d3f2ead71 100644
--- a/src/node_errors.h
+++ b/src/node_errors.h
@@ -45,12 +45,14 @@ void FatalException(v8::Isolate* isolate,
V(ERR_CONSTRUCT_CALL_REQUIRED, Error) \
V(ERR_INVALID_ARG_VALUE, TypeError) \
V(ERR_INVALID_ARG_TYPE, TypeError) \
+ V(ERR_INVALID_MODULE_SPECIFIER, TypeError) \
+ V(ERR_INVALID_PACKAGE_CONFIG, SyntaxError) \
V(ERR_INVALID_TRANSFER_OBJECT, TypeError) \
V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
V(ERR_MISSING_ARGS, TypeError) \
V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, TypeError) \
- V(ERR_MISSING_MODULE, Error) \
V(ERR_MISSING_PLATFORM_FOR_WORKER, Error) \
+ V(ERR_MODULE_NOT_FOUND, Error) \
V(ERR_OUT_OF_RANGE, RangeError) \
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \
V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \
diff --git a/src/node_options.cc b/src/node_options.cc
index 5687fb327b..bd20b6385d 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -107,6 +107,32 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
errors->push_back("--loader requires --experimental-modules be enabled");
}
+ if (!module_type.empty()) {
+ if (!experimental_modules) {
+ errors->push_back("--entry-type requires "
+ "--experimental-modules to be enabled");
+ }
+ if (module_type != "commonjs" && module_type != "module") {
+ errors->push_back("--entry-type must \"module\" or \"commonjs\"");
+ }
+ }
+
+ if (experimental_json_modules && !experimental_modules) {
+ errors->push_back("--experimental-json-modules requires "
+ "--experimental-modules be enabled");
+ }
+
+ if (!es_module_specifier_resolution.empty()) {
+ if (!experimental_modules) {
+ errors->push_back("--es-module-specifier-resolution requires "
+ "--experimental-modules be enabled");
+ }
+ if (es_module_specifier_resolution != "node" &&
+ es_module_specifier_resolution != "explicit") {
+ errors->push_back("invalid value for --es-module-specifier-resolution");
+ }
+ }
+
if (syntax_check_only && has_eval_string) {
errors->push_back("either --check or --eval can be used, not both");
}
@@ -214,6 +240,10 @@ DebugOptionsParser::DebugOptionsParser() {
}
EnvironmentOptionsParser::EnvironmentOptionsParser() {
+ AddOption("--experimental-json-modules",
+ "experimental JSON interop support for the ES Module loader",
+ &EnvironmentOptions::experimental_json_modules,
+ kAllowedInEnvironment);
AddOption("--experimental-modules",
"experimental ES Module support and caching modules",
&EnvironmentOptions::experimental_modules,
@@ -253,6 +283,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"custom loader",
&EnvironmentOptions::userland_loader,
kAllowedInEnvironment);
+ AddOption("--es-module-specifier-resolution",
+ "Select extension resolution algorithm for es modules; "
+ "either 'explicit' (default) or 'node'",
+ &EnvironmentOptions::es_module_specifier_resolution,
+ kAllowedInEnvironment);
AddOption("--no-deprecation",
"silence deprecation warnings",
&EnvironmentOptions::no_deprecation,
@@ -271,10 +306,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvironment);
AddOption("--preserve-symlinks",
"preserve symbolic links when resolving",
- &EnvironmentOptions::preserve_symlinks);
+ &EnvironmentOptions::preserve_symlinks,
+ kAllowedInEnvironment);
AddOption("--preserve-symlinks-main",
"preserve symbolic links when resolving the main module",
- &EnvironmentOptions::preserve_symlinks_main);
+ &EnvironmentOptions::preserve_symlinks_main,
+ kAllowedInEnvironment);
AddOption("--prof-process",
"process V8 profiler output generated using --prof",
&EnvironmentOptions::prof_process);
@@ -301,6 +338,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"show stack traces on process warnings",
&EnvironmentOptions::trace_warnings,
kAllowedInEnvironment);
+ AddOption("--entry-type",
+ "set module type name of the entry point",
+ &EnvironmentOptions::module_type,
+ kAllowedInEnvironment);
AddOption("--check",
"syntax check script without executing",
diff --git a/src/node_options.h b/src/node_options.h
index bcd6d2457d..e07ee7fb35 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -91,7 +91,10 @@ class DebugOptions : public Options {
class EnvironmentOptions : public Options {
public:
bool abort_on_uncaught_exception = false;
+ bool experimental_json_modules = false;
bool experimental_modules = false;
+ std::string es_module_specifier_resolution;
+ std::string module_type;
std::string experimental_policy;
bool experimental_repl_await = false;
bool experimental_vm_modules = false;