From c31bb8e7f2a445aa639e092d277c41dd6b059573 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 24 Jan 2019 19:33:28 +0200 Subject: [PATCH] irp type implementation --- lib/internal/errors.js | 11 +- lib/internal/modules/esm/default_resolve.js | 56 ++- lib/internal/modules/esm/loader.js | 5 +- lib/internal/modules/esm/module_job.js | 2 +- lib/internal/modules/esm/translators.js | 13 +- lib/internal/process/esm_loader.js | 4 +- src/env.h | 27 +- src/module_wrap.cc | 395 ++++++++++++++++++-- src/module_wrap.h | 11 +- src/node_errors.h | 2 + test/message/esm_display_syntax_error.out | 2 +- 11 files changed, 423 insertions(+), 105 deletions(-) diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 113ddfea93..7f2c73b642 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -864,13 +864,6 @@ E('ERR_MISSING_ARGS', E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK', 'The ES Module loader may not return a format of \'dynamic\' when no ' + 'dynamicInstantiate function was provided', Error); -E('ERR_MODULE_NOT_FOUND', (module, base, legacyResolution) => { - let msg = `Cannot find module '${module}' imported from ${base}.`; - if (legacyResolution) - msg += ' Legacy behavior in require() would have found it at ' + - legacyResolution; - return msg; -}, Error); E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error); E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError); E('ERR_NAPI_INVALID_DATAVIEW_ARGS', @@ -973,10 +966,10 @@ E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error); E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError); // This should probably be a `TypeError`. -E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: \'%s\' imported ' + - 'from %s', Error); E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError); E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError); +E('ERR_UNSUPPORTED_FILE_EXTENSION', 'Unsupported file extension: \'%s\' ' + + 'imported from %s', Error); E('ERR_V8BREAKITERATOR', 'Full ICU data not installed. See https://github.com/nodejs/node/wiki/Intl', diff --git a/lib/internal/modules/esm/default_resolve.js b/lib/internal/modules/esm/default_resolve.js index 188313f304..1e784e710a 100644 --- a/lib/internal/modules/esm/default_resolve.js +++ b/lib/internal/modules/esm/default_resolve.js @@ -1,7 +1,5 @@ 'use strict'; -const { URL } = require('url'); -const CJSmodule = require('internal/modules/cjs/loader'); const internalFS = require('internal/fs/utils'); const { NativeModule } = require('internal/bootstrap/loaders'); const { extname } = require('path'); @@ -9,38 +7,24 @@ const { realpathSync } = require('fs'); const { getOptionValue } = require('internal/options'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); -const { - ERR_MODULE_NOT_FOUND, - ERR_UNKNOWN_FILE_EXTENSION -} = require('internal/errors').codes; +const { ERR_UNSUPPORTED_FILE_EXTENSION } = require('internal/errors').codes; const { resolve: moduleWrapResolve } = internalBinding('module_wrap'); const { pathToFileURL, fileURLToPath } = require('internal/url'); const realpathCache = new Map(); -function search(target, base) { - try { - return moduleWrapResolve(target, base); - } catch (e) { - e.stack; // cause V8 to generate stack before rethrow - let error = e; - try { - const questionedBase = new URL(base); - const tmpMod = new CJSmodule(questionedBase.pathname, null); - tmpMod.paths = CJSmodule._nodeModulePaths( - new URL('./', questionedBase).pathname); - const found = CJSmodule._resolveFilename(target, tmpMod); - error = new ERR_MODULE_NOT_FOUND(target, fileURLToPath(base), found); - } catch { - // ignore - } - throw error; - } -} - const extensionFormatMap = { '__proto__': null, - '.mjs': 'esm' + '.mjs': 'esm', + '.js': 'esm' +}; + +const legacyExtensionFormatMap = { + '__proto__': null, + '.js': 'cjs', + '.json': 'cjs', + '.mjs': 'esm', + '.node': 'cjs' }; function resolve(specifier, parentURL) { @@ -51,10 +35,14 @@ function resolve(specifier, parentURL) { }; } - let url = search(specifier, - parentURL || pathToFileURL(`${process.cwd()}/`).href); - const isMain = parentURL === undefined; + if (isMain) + parentURL = pathToFileURL(`${process.cwd()}/`).href; + + const resolved = moduleWrapResolve(specifier, parentURL, isMain); + + let url = resolved.url; + const legacy = resolved.legacy; if (isMain ? !preserveSymlinksMain : !preserveSymlinks) { const real = realpathSync(fileURLToPath(url), { @@ -67,14 +55,14 @@ function resolve(specifier, parentURL) { } const ext = extname(url.pathname); + let format = (legacy ? legacyExtensionFormatMap : extensionFormatMap)[ext]; - let format = extensionFormatMap[ext]; if (!format) { if (isMain) - format = 'cjs'; + format = legacy ? 'cjs' : 'esm'; else - throw new ERR_UNKNOWN_FILE_EXTENSION(url.pathname, - fileURLToPath(parentURL)); + throw new ERR_UNSUPPORTED_FILE_EXTENSION(fileURLToPath(url), + fileURLToPath(parentURL)); } return { url: `${url}`, format }; diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index a1a1621909..1776d3da48 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -14,7 +14,7 @@ const ModuleJob = require('internal/modules/esm/module_job'); const defaultResolve = require('internal/modules/esm/default_resolve'); const createDynamicModule = require( 'internal/modules/esm/create_dynamic_module'); -const translators = require('internal/modules/esm/translators'); +const { translators } = require('internal/modules/esm/translators'); const FunctionBind = Function.call.bind(Function.prototype.bind); @@ -32,6 +32,9 @@ class Loader { // Registry of loaded modules, akin to `require.cache` this.moduleMap = new ModuleMap(); + // map of already-loaded CJS modules to use + this.cjsCache = new Map(); + // The resolver has the signature // (specifier : string, parentURL : string, defaultResolve) // -> Promise<{ url : string, format: string }> diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js index fbb29aef78..50cc7a9b46 100644 --- a/lib/internal/modules/esm/module_job.js +++ b/lib/internal/modules/esm/module_job.js @@ -17,7 +17,7 @@ class ModuleJob { // This is a Promise<{ module, reflect }>, whose fields will be copied // onto `this` by `link()` below once it has been resolved. - this.modulePromise = moduleProvider(url, isMain); + this.modulePromise = moduleProvider.call(loader, url, isMain); this.module = undefined; this.reflect = undefined; diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 9e0b878838..71b613a6ff 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -21,7 +21,7 @@ const StringReplace = Function.call.bind(String.prototype.replace); const debug = debuglog('esm'); const translators = new SafeMap(); -module.exports = translators; +exports.translators = translators; function initializeImportMeta(meta, { url }) { meta.url = url; @@ -33,7 +33,7 @@ async function importModuleDynamically(specifier, { url }) { } // Strategy for loading a standard JavaScript module -translators.set('esm', async (url) => { +translators.set('esm', async function(url) { const source = `${await readFileAsync(new URL(url))}`; debug(`Translating StandardModule ${url}`); const module = new ModuleWrap(stripShebang(source), url); @@ -50,9 +50,14 @@ translators.set('esm', async (url) => { // Strategy for loading a node-style CommonJS module const isWindows = process.platform === 'win32'; const winSepRegEx = /\//g; -translators.set('cjs', async (url, isMain) => { +translators.set('cjs', async function(url, isMain) { debug(`Translating CJSModule ${url}`); const pathname = internalURLModule.fileURLToPath(new URL(url)); + const cached = this.cjsCache.get(url); + if (cached) { + this.cjsCache.delete(url); + return cached; + } const module = CJSModule._cache[ isWindows ? StringReplace(pathname, winSepRegEx, '\\') : pathname]; if (module && module.loaded) { @@ -72,7 +77,7 @@ translators.set('cjs', async (url, isMain) => { // Strategy for loading a node builtin CommonJS module that isn't // through normal resolution -translators.set('builtin', async (url) => { +translators.set('builtin', async function(url) { debug(`Translating BuiltinModule ${url}`); // slice 'node:' scheme const id = url.slice(5); diff --git a/lib/internal/process/esm_loader.js b/lib/internal/process/esm_loader.js index 9d8df07c05..20e6bceba9 100644 --- a/lib/internal/process/esm_loader.js +++ b/lib/internal/process/esm_loader.js @@ -38,9 +38,7 @@ setInitializeImportMetaObjectCallback(initializeImportMetaObject); setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); let loaderResolve; -exports.loaderPromise = new Promise((resolve, reject) => { - loaderResolve = resolve; -}); +exports.loaderPromise = new Promise((resolve) => loaderResolve = resolve); exports.ESMLoader = undefined; diff --git a/src/env.h b/src/env.h index 711d07963a..a6e3426411 100644 --- a/src/env.h +++ b/src/env.h @@ -73,14 +73,23 @@ namespace loader { class ModuleWrap; struct PackageConfig { - enum class Exists { Yes, No }; - enum class IsValid { Yes, No }; - enum class HasMain { Yes, No }; - - Exists exists; - IsValid is_valid; - HasMain has_main; - std::string main; + struct Exists { + enum Bool { No, Yes }; + }; + struct IsValid { + enum Bool { No, Yes }; + }; + struct HasMain { + enum Bool { No, Yes }; + }; + struct IsESM { + enum Bool { No, Yes }; + }; + const Exists::Bool exists; + const IsValid::Bool is_valid; + const HasMain::Bool has_main; + const std::string main; + const IsESM::Bool esm; }; } // namespace loader @@ -168,6 +177,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(env_var_settings_string, "envVarSettings") \ V(errno_string, "errno") \ V(error_string, "error") \ + V(esm_string, "esm") \ V(exchange_string, "exchange") \ V(exit_code_string, "exitCode") \ V(expire_string, "expire") \ @@ -206,6 +216,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2; V(kill_signal_string, "killSignal") \ V(kind_string, "kind") \ V(library_string, "library") \ + V(legacy_string, "legacy") \ V(mac_string, "mac") \ V(main_string, "main") \ V(max_buffer_string, "maxBuffer") \ diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 4d2ffe838e..bf3cc0c52b 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -43,8 +43,6 @@ using v8::String; using v8::Undefined; using v8::Value; -static const char* const EXTENSIONS[] = {".mjs"}; - ModuleWrap::ModuleWrap(Environment* env, Local object, Local module, @@ -467,14 +465,30 @@ std::string ReadFile(uv_file file) { } enum DescriptorType { - NONE, FILE, - DIRECTORY + DIRECTORY, + NONE }; -DescriptorType CheckDescriptor(const std::string& path) { +// When DescriptorType cache is added, this can also return +// Nothing for the "null" cache entries. +inline Maybe OpenDescriptor(const std::string& path) { + uv_fs_t fs_req; + 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(); + return Just(fd); +} + +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); +} + +inline DescriptorType CheckDescriptorAtFile(uv_file fd) { uv_fs_t fs_req; - int rc = uv_fs_stat(nullptr, &fs_req, path.c_str(), nullptr); + 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); @@ -484,27 +498,320 @@ DescriptorType CheckDescriptor(const std::string& path) { return NONE; } -Maybe PackageResolve(Environment* env, +// 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 fd = OpenDescriptor(path); + if (fd.IsNothing()) return NONE; + DescriptorType type = CheckDescriptorAtFile(fd.FromJust()); + CloseDescriptor(fd.FromJust()); + return type; +} + +Maybe ReadIfFile(const std::string& path) { + Maybe fd = OpenDescriptor(path); + if (fd.IsNothing()) return Nothing(); + DescriptorType type = CheckDescriptorAtFile(fd.FromJust()); + if (type != FILE) return Nothing(); + 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 IsESM = PackageConfig::IsESM; + +Maybe 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 Just(&existing->second); + } + + Maybe source = ReadIfFile(path); + + if (source.IsNothing()) { + auto entry = env->package_json_cache.emplace(path, + PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", + IsESM::No }); + return Just(&entry.first->second); + } + + std::string pkg_src = source.FromJust(); + + Isolate* isolate = env->isolate(); + v8::HandleScope handle_scope(isolate); + + bool parsed = false; + Local pkg_json; + { + Local src; + Local pkg_json_v; + if (String::NewFromUtf8(isolate, + pkg_src.c_str(), + v8::NewStringType::kNormal, + pkg_src.length()).ToLocal(&src) && + v8::JSON::Parse(env->context(), src).ToLocal(&pkg_json_v) && + pkg_json_v->ToObject(env->context()).ToLocal(&pkg_json)) { + parsed = true; + } + } + + if (!parsed) { + (void)env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "", + IsESM::No }); + std::string msg = "Invalid JSON in '" + path + + "' imported from " + base.ToFilePath(); + node::THROW_ERR_INVALID_PACKAGE_CONFIG(env, msg.c_str()); + return Nothing(); + } + + Local pkg_main; + HasMain::Bool has_main = HasMain::No; + std::string main_std; + if (pkg_json->Get(env->context(), env->main_string()).ToLocal(&pkg_main)) { + if (pkg_main->IsString()) { + has_main = HasMain::Yes; + } + Utf8Value main_utf8(isolate, pkg_main); + main_std.assign(std::string(*main_utf8, main_utf8.length())); + } + + IsESM::Bool esm = IsESM::No; + Local type_v; + if (pkg_json->Get(env->context(), env->type_string()).ToLocal(&type_v)) { + if (type_v->StrictEquals(env->esm_string())) { + esm = IsESM::Yes; + } + } + + Local exports_v; + if (pkg_json->Get(env->context(), + env->exports_string()).ToLocal(&exports_v) && + (exports_v->IsObject() || exports_v->IsString() || + exports_v->IsBoolean())) { + Persistent exports; + // esm = IsESM::Yes; + exports.Reset(env->isolate(), exports_v); + + auto entry = env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + esm }); + return Just(&entry.first->second); + } + + auto entry = env->package_json_cache.emplace(path, + PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + esm }); + return Just(&entry.first->second); +} + +Maybe GetPackageBoundaryConfig(Environment* env, + const URL& search, + const URL& base) { + URL pjson_url("package.json", &search); + while (true) { + Maybe 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, "", + IsESM::No }); + return Just(&entry.first->second); + } + } +} + +/* + * 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 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(); +} + +Maybe FinalizeResolution(Environment* env, + const URL& resolved, + const URL& base, + bool check_exists, + bool is_main) { + const std::string& path = resolved.ToFilePath(); + + if (check_exists && 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(); + } + + Maybe pcfg = + GetPackageBoundaryConfig(env, resolved, base); + if (pcfg.IsNothing()) return Nothing(); + + if (pcfg.FromJust()->exists == Exists::No) { + return Just(ModuleResolution { resolved, true }); + } + + return Just(ModuleResolution { + resolved, pcfg.FromJust()->esm == IsESM::No }); +} + +Maybe PackageMainResolve(Environment* env, + const URL& pjson_url, + const PackageConfig& pcfg, + const URL& base) { + if (pcfg.exists == Exists::No || ( + pcfg.esm == IsESM::Yes && pcfg.has_main == HasMain::No)) { + 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(); + } + if (pcfg.has_main == HasMain::Yes && + pcfg.main.substr(pcfg.main.length() - 4, 4) == ".mjs") { + return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true, + true); + } + if (pcfg.esm == IsESM::Yes && + pcfg.main.substr(pcfg.main.length() - 3, 3) == ".js") { + return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true, + true); + } + + Maybe resolved = LegacyMainResolve(pjson_url, pcfg); + // Legacy main resolution error + if (resolved.IsNothing()) { + return Nothing(); + } + return FinalizeResolution(env, resolved.FromJust(), base, false, true); +} + +Maybe PackageResolve(Environment* env, const std::string& specifier, const URL& base) { - URL parent(".", 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(); + } + bool scope = false; + if (specifier[0] == '@') { + scope = true; + sep_index = specifier.find('/', sep_index + 1); + } + 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 { - URL pkg_url("./node_modules/" + specifier, &parent); - DescriptorType check = CheckDescriptor(pkg_url.ToFilePath()); - if (check == FILE) return Just(pkg_url); - last_path = parent.path(); - parent = URL("..", &parent); - // cross-platform root check - } while (parent.path() != last_path); - return Nothing(); + 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 pcfg = GetPackageConfig(env, pjson_path, base); + // Invalid package configuration error. + if (pcfg.IsNothing()) return Nothing(); + if (!pkg_subpath.length()) { + return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base); + } else { + return FinalizeResolution(env, URL(pkg_subpath, pjson_url), + base, true, false); + } + CHECK(false); + // Cross-platform root check. + } while (pjson_url.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(); } } // anonymous namespace -Maybe Resolve(Environment* env, - const std::string& specifier, - const URL& base) { +Maybe Resolve(Environment* env, + const std::string& specifier, + const URL& base, + bool is_main) { // Order swapped from spec for minor perf gain. // Ok since relative URLs cannot parse as URLs. URL resolved; @@ -518,21 +825,14 @@ Maybe Resolve(Environment* env, return PackageResolve(env, specifier, base); } } - DescriptorType check = CheckDescriptor(resolved.ToFilePath()); - if (check != FILE) { - std::string msg = "Cannot find module '" + resolved.ToFilePath() + - "' imported from " + base.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); - return Nothing(); - } - return Just(resolved); + return FinalizeResolution(env, resolved, base, true, is_main); } void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - // module.resolve(specifier, url) - CHECK_EQ(args.Length(), 2); + // module.resolve(specifier, url, is_main) + CHECK_EQ(args.Length(), 3); CHECK(args[0]->IsString()); Utf8Value specifier_utf8(env->isolate(), args[0]); @@ -542,28 +842,41 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo& args) { Utf8Value url_utf8(env->isolate(), args[1]); URL url(*url_utf8, url_utf8.length()); + CHECK(args[2]->IsBoolean()); + if (url.flags() & URL_FLAGS_FAILED) { return node::THROW_ERR_INVALID_ARG_TYPE( env, "second argument is not a URL string"); } TryCatchScope try_catch(env); - Maybe result = node::loader::Resolve(env, specifier_std, url); - if (try_catch.HasCaught()) { - try_catch.ReThrow(); - return; - } else if (result.IsNothing() || - (result.FromJust().flags() & URL_FLAGS_FAILED)) { - std::string msg = "Cannot find module '" + specifier_std + - "' imported from " + url.ToFilePath(); - node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); + Maybe result = + node::loader::Resolve(env, specifier_std, url, args[2]->IsTrue()); + if (result.IsNothing()) { + CHECK(try_catch.HasCaught()); try_catch.ReThrow(); return; } + CHECK(!try_catch.HasCaught()); + + ModuleResolution resolution = result.FromJust(); + CHECK(!(resolution.url.flags() & URL_FLAGS_FAILED)); + + Local resolved = Object::New(env->isolate()); + + resolved->DefineOwnProperty( + env->context(), + env->url_string(), + resolution.url.ToObject(env).ToLocalChecked(), + v8::ReadOnly).FromJust(); + + resolved->DefineOwnProperty( + env->context(), + env->legacy_string(), + v8::Boolean::New(env->isolate(), resolution.legacy), + v8::ReadOnly).FromJust(); - MaybeLocal obj = result.FromJust().ToObject(env); - if (!obj.IsEmpty()) - args.GetReturnValue().Set(obj.ToLocalChecked()); + args.GetReturnValue().Set(resolved); } static MaybeLocal ImportModuleDynamically( diff --git a/src/module_wrap.h b/src/module_wrap.h index 8a5592d3f2..f5e6eef94e 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -23,9 +23,14 @@ enum HostDefinedOptions : int { kLength = 10, }; -v8::Maybe Resolve(Environment* env, - const std::string& specifier, - const url::URL& base); +struct ModuleResolution { + url::URL url; + bool legacy; +}; + +v8::Maybe Resolve(Environment* env, + const std::string& specifier, + const url::URL& base); class ModuleWrap : public BaseObject { public: diff --git a/src/node_errors.h b/src/node_errors.h index 3010ddf594..3b849abed4 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -59,6 +59,8 @@ void FatalException(const v8::FunctionCallbackInfo& args); 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) \ diff --git a/test/message/esm_display_syntax_error.out b/test/message/esm_display_syntax_error.out index ed2e928eb1..2700fd894c 100644 --- a/test/message/esm_display_syntax_error.out +++ b/test/message/esm_display_syntax_error.out @@ -3,4 +3,4 @@ file:///*/test/message/esm_display_syntax_error.mjs:3 await async () => 0; ^^^^^ SyntaxError: Unexpected reserved word - at translators.set (internal/modules/esm/translators.js:*:*) + at Loader. (internal/modules/esm/translators.js:*:*)