diff options
author | Jan Krems <jan.krems@gmail.com> | 2019-08-19 20:59:25 -0700 |
---|---|---|
committer | Jan Krems <jan.krems@gmail.com> | 2019-10-24 15:14:38 -0700 |
commit | 71bcd05232b4fc21db20e5acf019f97780050568 (patch) | |
tree | ff58c1a5c27e3e28755395e1b07b0394583d96e1 /src/module_wrap.cc | |
parent | d53dd8b0a00d3e00e97f46ae4ae67afa31c10526 (diff) | |
download | android-node-v8-71bcd05232b4fc21db20e5acf019f97780050568.tar.gz android-node-v8-71bcd05232b4fc21db20e5acf019f97780050568.tar.bz2 android-node-v8-71bcd05232b4fc21db20e5acf019f97780050568.zip |
module: resolve self-references
Adds the ability to `import` or `require` a package from within its
own source code. This allows tests and examples to be written using
the package name, making them easier to reuse by consumers of the
package.
Assuming the `name` field in `package.json` is set to `my-pkg`, its
test could use `require('my-pkg')` or `import 'my-pkg'` even if
there's no `node_modules/my-pkg` while testing the package itself.
An important difference between this and relative specifiers like
`require('../')` is that self-references use the public interface
of the package as defined in the `exports` field while relative
specifiers don't.
This behavior is guarded by a new experimental flag
(`--experimental-resolve-self`).
PR-URL: https://github.com/nodejs/node/pull/29327
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michaƫl Zasso <targos@protonmail.com>
Diffstat (limited to 'src/module_wrap.cc')
-rw-r--r-- | src/module_wrap.cc | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/src/module_wrap.cc b/src/module_wrap.cc index e7df8688cb..46210520fc 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -566,6 +566,7 @@ Maybe<std::string> ReadIfFile(const std::string& path) { using Exists = PackageConfig::Exists; using IsValid = PackageConfig::IsValid; using HasMain = PackageConfig::HasMain; +using HasName = PackageConfig::HasName; using PackageType = PackageConfig::PackageType; Maybe<const PackageConfig*> GetPackageConfig(Environment* env, @@ -588,6 +589,7 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env, if (source.IsNothing()) { auto entry = env->package_json_cache.emplace(path, PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", + HasName::No, "", PackageType::None, Global<Value>() }); return Just(&entry.first->second); } @@ -608,6 +610,7 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env, !pkg_json_v->ToObject(context).ToLocal(&pkg_json)) { env->package_json_cache.emplace(path, PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "", + HasName::No, "", PackageType::None, Global<Value>() }); std::string msg = "Invalid JSON in " + path + " imported from " + base.ToFilePath(); @@ -627,6 +630,18 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env, main_std.assign(std::string(*main_utf8, main_utf8.length())); } + Local<Value> pkg_name; + HasName has_name = HasName::No; + std::string name_std; + if (pkg_json->Get(env->context(), env->name_string()).ToLocal(&pkg_name)) { + if (pkg_name->IsString()) { + has_name = HasName::Yes; + + Utf8Value name_utf8(isolate, pkg_name); + name_std.assign(std::string(*name_utf8, name_utf8.length())); + } + } + PackageType pkg_type = PackageType::None; Local<Value> type_v; if (pkg_json->Get(env->context(), env->type_string()).ToLocal(&type_v)) { @@ -647,12 +662,14 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env, auto entry = env->package_json_cache.emplace(path, PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + has_name, name_std, pkg_type, std::move(exports) }); return Just(&entry.first->second); } auto entry = env->package_json_cache.emplace(path, PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std, + has_name, name_std, pkg_type, Global<Value>() }); return Just(&entry.first->second); } @@ -682,6 +699,7 @@ Maybe<const PackageConfig*> GetPackageScopeConfig(Environment* env, } auto entry = env->package_json_cache.emplace(pjson_url.ToFilePath(), PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "", + HasName::No, "", PackageType::None, Global<Value>() }); const PackageConfig* pcfg = &entry.first->second; return Just(pcfg); @@ -1107,6 +1125,62 @@ Maybe<URL> PackageExportsResolve(Environment* env, return Nothing<URL>(); } +Maybe<URL> ResolveSelf(Environment* env, + const std::string& specifier, + const URL& base) { + if (!env->options()->experimental_resolve_self) { + return Nothing<URL>(); + } + + const PackageConfig* pcfg; + if (GetPackageScopeConfig(env, base, base).To(&pcfg) && + pcfg->exists == Exists::Yes) { + // TODO(jkrems): Find a way to forward the pair/iterator already generated + // while executing GetPackageScopeConfig + URL pjson_url(""); + bool found_pjson = false; + for (auto it = env->package_json_cache.begin(); + it != env->package_json_cache.end(); + ++it) { + if (&it->second == pcfg) { + pjson_url = URL::FromFilePath(it->first); + found_pjson = true; + } + } + + if (!found_pjson) { + return Nothing<URL>(); + } + + // "If specifier starts with pcfg name" + std::string subpath; + if (specifier.rfind(pcfg->name, 0)) { + // We know now: specifier is either equal to name or longer. + if (specifier == subpath) { + subpath = ""; + } else if (specifier[pcfg->name.length()] == '/') { + // Return everything after the slash + subpath = "." + specifier.substr(pcfg->name.length() + 1); + } else { + // The specifier is neither the name of the package nor a subpath of it + return Nothing<URL>(); + } + } + + if (found_pjson && !subpath.length()) { + return PackageMainResolve(env, pjson_url, *pcfg, base); + } else if (found_pjson) { + if (!pcfg->exports.IsEmpty()) { + return PackageExportsResolve(env, pjson_url, subpath, *pcfg, base); + } else { + return FinalizeResolution(env, URL(subpath, pjson_url), base); + } + } + } + + return Nothing<URL>(); +} + Maybe<URL> PackageResolve(Environment* env, const std::string& specifier, const URL& base) { @@ -1180,6 +1254,11 @@ Maybe<URL> PackageResolve(Environment* env, // Cross-platform root check. } while (pjson_path.length() != last_path.length()); + Maybe<URL> self_url = ResolveSelf(env, specifier, base); + if (self_url.IsJust()) { + return self_url; + } + std::string msg = "Cannot find package '" + pkg_name + "' imported from " + base.ToFilePath(); node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str()); |