summaryrefslogtreecommitdiff
path: root/src/module_wrap.cc
diff options
context:
space:
mode:
authorJan Krems <jan.krems@gmail.com>2019-08-19 20:59:25 -0700
committerJan Krems <jan.krems@gmail.com>2019-10-24 15:14:38 -0700
commit71bcd05232b4fc21db20e5acf019f97780050568 (patch)
treeff58c1a5c27e3e28755395e1b07b0394583d96e1 /src/module_wrap.cc
parentd53dd8b0a00d3e00e97f46ae4ae67afa31c10526 (diff)
downloadandroid-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.cc79
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());