Skip to content
This repository was archived by the owner on Apr 16, 2020. It is now read-only.

Commit 2b6e1e5

Browse files
guybedfordGeoffreyBooth
authored andcommitted
esm: implement symlink handling in package scope
1 parent a3620e0 commit 2b6e1e5

File tree

5 files changed

+88
-122
lines changed

5 files changed

+88
-122
lines changed

doc/api/errors.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,11 @@ An invalid or unexpected value was passed in an options object.
12651265

12661266
An invalid or unknown file encoding was passed.
12671267

1268+
<a id="ERR_INVALID_PACKAGE_CONFIG"></a>
1269+
### ERR_INVALID_PACKAGE_CONFIG
1270+
1271+
An invalid `package.json` file was found which failed parsing.
1272+
12681273
<a id="ERR_INVALID_PERFORMANCE_MARK"></a>
12691274
### ERR_INVALID_PERFORMANCE_MARK
12701275

lib/internal/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,8 @@ E('ERR_INVALID_OPT_VALUE', (name, value) =>
773773
RangeError);
774774
E('ERR_INVALID_OPT_VALUE_ENCODING',
775775
'The value "%s" is invalid for option "encoding"', TypeError);
776+
E('ERR_INVALID_PACKAGE_CONFIG',
777+
'Invalid package config in \'%s\' imported from %s', Error);
776778
E('ERR_INVALID_PERFORMANCE_MARK',
777779
'The "%s" performance mark has not been set', Error);
778780
E('ERR_INVALID_PROTOCOL',

lib/internal/modules/esm/default_resolve.js

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
const internalFS = require('internal/fs/utils');
44
const { NativeModule } = require('internal/bootstrap/loaders');
55
const { extname } = require('path');
6-
const { realpathSync } = require('fs');
6+
const { realpathSync, readFileSync } = require('fs');
77
const { getOptionValue } = require('internal/options');
88
const preserveSymlinks = getOptionValue('--preserve-symlinks');
99
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
10-
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
10+
const { ERR_INVALID_PACKAGE_CONFIG,
11+
ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
1112
const { resolve: moduleWrapResolve } = internalBinding('module_wrap');
12-
const { pathToFileURL, fileURLToPath } = require('internal/url');
13+
const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
1314

1415
const realpathCache = new Map();
16+
// TOOD(@guybedford): Shared cache with C++
17+
const pjsonCache = new Map();
1518

1619
const extensionFormatMap = {
1720
'__proto__': null,
@@ -29,6 +32,57 @@ const legacyExtensionFormatMap = {
2932
'.node': 'commonjs'
3033
};
3134

35+
function readPackageConfig(path, parentURL) {
36+
const existing = pjsonCache.get(path);
37+
if (existing !== undefined)
38+
return existing;
39+
try {
40+
return JSON.parse(readFileSync(path).toString());
41+
} catch (e) {
42+
if (e.code === 'ENOENT') {
43+
pjsonCache.set(path, null);
44+
return null;
45+
} else if (e instanceof SyntaxError) {
46+
throw new ERR_INVALID_PACKAGE_CONFIG(path, fileURLToPath(parentURL));
47+
}
48+
throw e;
49+
}
50+
}
51+
52+
function getPackageBoundaryConfig(url, parentURL) {
53+
let pjsonURL = new URL('package.json', url);
54+
while (true) {
55+
const pcfg = readPackageConfig(fileURLToPath(pjsonURL), parentURL);
56+
if (pcfg)
57+
return pcfg;
58+
59+
const lastPjsonURL = pjsonURL;
60+
pjsonURL = new URL('../package.json', pjsonURL);
61+
62+
// Terminates at root where ../package.json equals ../../package.json
63+
// (can't just check "/package.json" for Windows support).
64+
if (pjsonURL.pathname === lastPjsonURL.pathname)
65+
return;
66+
}
67+
}
68+
69+
function getModuleFormat(url, isMain, parentURL) {
70+
const pcfg = getPackageBoundaryConfig(url, parentURL);
71+
const legacy = !pcfg || pcfg.type !== 'module';
72+
73+
const ext = extname(url.pathname);
74+
let format = (legacy ? legacyExtensionFormatMap : extensionFormatMap)[ext];
75+
76+
if (!format) {
77+
if (isMain)
78+
format = legacy ? 'commonjs' : 'module';
79+
else
80+
throw new ERR_UNKNOWN_FILE_EXTENSION(fileURLToPath(url),
81+
fileURLToPath(parentURL));
82+
}
83+
return format;
84+
}
85+
3286
function resolve(specifier, parentURL) {
3387
if (NativeModule.canBeRequiredByUsers(specifier)) {
3488
return {
@@ -41,12 +95,7 @@ function resolve(specifier, parentURL) {
4195
if (isMain)
4296
parentURL = pathToFileURL(`${process.cwd()}/`).href;
4397

44-
const resolved = moduleWrapResolve(specifier,
45-
parentURL,
46-
isMain);
47-
48-
let url = resolved.url;
49-
const legacy = resolved.legacy;
98+
let url = moduleWrapResolve(specifier, parentURL);
5099

51100
if (isMain ? !preserveSymlinksMain : !preserveSymlinks) {
52101
const real = realpathSync(fileURLToPath(url), {
@@ -58,16 +107,7 @@ function resolve(specifier, parentURL) {
58107
url.hash = old.hash;
59108
}
60109

61-
const ext = extname(url.pathname);
62-
let format = (legacy ? legacyExtensionFormatMap : extensionFormatMap)[ext];
63-
64-
if (!format) {
65-
if (isMain)
66-
format = legacy ? 'commonjs' : 'module';
67-
else
68-
throw new ERR_UNKNOWN_FILE_EXTENSION(fileURLToPath(url),
69-
fileURLToPath(parentURL));
70-
}
110+
const format = getModuleFormat(url, isMain, parentURL);
71111

72112
return { url: `${url}`, format };
73113
}

src/module_wrap.cc

Lines changed: 22 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -613,30 +613,6 @@ Maybe<const PackageConfig*> GetPackageConfig(Environment* env,
613613
return Just(&entry.first->second);
614614
}
615615

616-
Maybe<const PackageConfig*> GetPackageBoundaryConfig(Environment* env,
617-
const URL& search,
618-
const URL& base) {
619-
URL pjson_url("package.json", &search);
620-
while (true) {
621-
Maybe<const PackageConfig*> pkg_cfg =
622-
GetPackageConfig(env, pjson_url.ToFilePath(), base);
623-
if (pkg_cfg.IsNothing()) return pkg_cfg;
624-
if (pkg_cfg.FromJust()->exists == Exists::Yes) return pkg_cfg;
625-
626-
URL last_pjson_url = pjson_url;
627-
pjson_url = URL("../package.json", pjson_url);
628-
629-
// Terminates at root where ../package.json equals ../../package.json
630-
// (can't just check "/package.json" for Windows support).
631-
if (pjson_url.path() == last_pjson_url.path()) {
632-
auto entry = env->package_json_cache.emplace(pjson_url.ToFilePath(),
633-
PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "",
634-
IsESM::No });
635-
return Just(&entry.first->second);
636-
}
637-
}
638-
}
639-
640616
/*
641617
* Legacy CommonJS main resolution:
642618
* 1. let M = pkg_url + (json main field)
@@ -691,7 +667,7 @@ Maybe<URL> LegacyMainResolve(const URL& pjson_url,
691667
return Nothing<URL>();
692668
}
693669

694-
Maybe<ModuleResolution> FinalizeResolution(Environment* env,
670+
Maybe<URL> FinalizeResolution(Environment* env,
695671
const URL& resolved,
696672
const URL& base,
697673
bool check_exists) {
@@ -701,46 +677,13 @@ Maybe<ModuleResolution> FinalizeResolution(Environment* env,
701677
std::string msg = "Cannot find module '" + path +
702678
"' imported from " + base.ToFilePath();
703679
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
704-
return Nothing<ModuleResolution>();
705-
}
706-
707-
Maybe<const PackageConfig*> pcfg =
708-
GetPackageBoundaryConfig(env, resolved, base);
709-
if (pcfg.IsNothing()) return Nothing<ModuleResolution>();
710-
711-
if (pcfg.FromJust()->exists == Exists::No) {
712-
return Just(ModuleResolution { resolved, true });
713-
}
714-
715-
return Just(ModuleResolution {
716-
resolved, pcfg.FromJust()->esm == IsESM::No });
717-
}
718-
719-
Maybe<ModuleResolution> FinalizeResolutionMain(Environment* env,
720-
const URL& resolved,
721-
const URL& base) {
722-
const std::string& path = resolved.ToFilePath();
723-
724-
if (CheckDescriptorAtPath(path) != FILE) {
725-
std::string msg = "Cannot find module '" + path +
726-
"' imported from " + base.ToFilePath();
727-
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
728-
return Nothing<ModuleResolution>();
729-
}
730-
731-
Maybe<const PackageConfig*> pcfg =
732-
GetPackageBoundaryConfig(env, resolved, base);
733-
if (pcfg.IsNothing()) return Nothing<ModuleResolution>();
734-
735-
if (pcfg.FromJust()->exists == Exists::No) {
736-
return Just(ModuleResolution { resolved, true });
680+
return Nothing<URL>();
737681
}
738682

739-
return Just(ModuleResolution {
740-
resolved, pcfg.FromJust()->esm == IsESM::No });
683+
return Just(resolved);
741684
}
742685

743-
Maybe<ModuleResolution> PackageMainResolve(Environment* env,
686+
Maybe<URL> PackageMainResolve(Environment* env,
744687
const URL& pjson_url,
745688
const PackageConfig& pcfg,
746689
const URL& base) {
@@ -750,7 +693,7 @@ Maybe<ModuleResolution> PackageMainResolve(Environment* env,
750693
URL(".", pjson_url).ToFilePath() + "' imported from " +
751694
base.ToFilePath();
752695
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
753-
return Nothing<ModuleResolution>();
696+
return Nothing<URL>();
754697
}
755698
if (pcfg.has_main == HasMain::Yes &&
756699
pcfg.main.substr(pcfg.main.length() - 4, 4) == ".mjs") {
@@ -764,12 +707,12 @@ Maybe<ModuleResolution> PackageMainResolve(Environment* env,
764707
Maybe<URL> resolved = LegacyMainResolve(pjson_url, pcfg);
765708
// Legacy main resolution error
766709
if (resolved.IsNothing()) {
767-
return Nothing<ModuleResolution>();
710+
return Nothing<URL>();
768711
}
769-
return FinalizeResolution(env, resolved.FromJust(), base, false);
712+
return resolved;
770713
}
771714

772-
Maybe<ModuleResolution> PackageResolve(Environment* env,
715+
Maybe<URL> PackageResolve(Environment* env,
773716
const std::string& specifier,
774717
const URL& base) {
775718
size_t sep_index = specifier.find('/');
@@ -778,7 +721,7 @@ Maybe<ModuleResolution> PackageResolve(Environment* env,
778721
std::string msg = "Invalid package name '" + specifier +
779722
"' imported from " + base.ToFilePath();
780723
node::THROW_ERR_INVALID_MODULE_SPECIFIER(env, msg.c_str());
781-
return Nothing<ModuleResolution>();
724+
return Nothing<URL>();
782725
}
783726
bool scope = false;
784727
if (specifier[0] == '@') {
@@ -812,7 +755,7 @@ Maybe<ModuleResolution> PackageResolve(Environment* env,
812755
// Package match.
813756
Maybe<const PackageConfig*> pcfg = GetPackageConfig(env, pjson_path, base);
814757
// Invalid package configuration error.
815-
if (pcfg.IsNothing()) return Nothing<ModuleResolution>();
758+
if (pcfg.IsNothing()) return Nothing<URL>();
816759
if (!pkg_subpath.length()) {
817760
return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base);
818761
} else {
@@ -825,15 +768,14 @@ Maybe<ModuleResolution> PackageResolve(Environment* env,
825768
std::string msg = "Cannot find package '" + pkg_name +
826769
"' imported from " + base.ToFilePath();
827770
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
828-
return Nothing<ModuleResolution>();
771+
return Nothing<URL>();
829772
}
830773

831774
} // anonymous namespace
832775

833-
Maybe<ModuleResolution> Resolve(Environment* env,
776+
Maybe<URL> Resolve(Environment* env,
834777
const std::string& specifier,
835-
const URL& base,
836-
bool is_main) {
778+
const URL& base) {
837779
// Order swapped from spec for minor perf gain.
838780
// Ok since relative URLs cannot parse as URLs.
839781
URL resolved;
@@ -847,17 +789,14 @@ Maybe<ModuleResolution> Resolve(Environment* env,
847789
return PackageResolve(env, specifier, base);
848790
}
849791
}
850-
if (is_main)
851-
return FinalizeResolutionMain(env, resolved, base);
852-
else
853-
return FinalizeResolution(env, resolved, base, true);
792+
return FinalizeResolution(env, resolved, base, true);
854793
}
855794

856795
void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) {
857796
Environment* env = Environment::GetCurrent(args);
858797

859-
// module.resolve(specifier, url, is_main)
860-
CHECK_EQ(args.Length(), 3);
798+
// module.resolve(specifier, url)
799+
CHECK_EQ(args.Length(), 2);
861800

862801
CHECK(args[0]->IsString());
863802
Utf8Value specifier_utf8(env->isolate(), args[0]);
@@ -867,44 +806,29 @@ void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) {
867806
Utf8Value url_utf8(env->isolate(), args[1]);
868807
URL url(*url_utf8, url_utf8.length());
869808

870-
CHECK(args[2]->IsBoolean());
871-
872809
if (url.flags() & URL_FLAGS_FAILED) {
873810
return node::THROW_ERR_INVALID_ARG_TYPE(
874811
env, "second argument is not a URL string");
875812
}
876813

877814
TryCatchScope try_catch(env);
878-
Maybe<ModuleResolution> result =
815+
Maybe<URL> result =
879816
node::loader::Resolve(env,
880817
specifier_std,
881-
url,
882-
args[2]->IsTrue());
818+
url);
883819
if (result.IsNothing()) {
884820
CHECK(try_catch.HasCaught());
885821
try_catch.ReThrow();
886822
return;
887823
}
888824
CHECK(!try_catch.HasCaught());
889825

890-
ModuleResolution resolution = result.FromJust();
891-
CHECK(!(resolution.url.flags() & URL_FLAGS_FAILED));
892-
893-
Local<Object> resolved = Object::New(env->isolate());
894-
895-
resolved->DefineOwnProperty(
896-
env->context(),
897-
env->url_string(),
898-
resolution.url.ToObject(env).ToLocalChecked(),
899-
v8::ReadOnly).FromJust();
826+
URL resolution = result.FromJust();
827+
CHECK(!(resolution.flags() & URL_FLAGS_FAILED));
900828

901-
resolved->DefineOwnProperty(
902-
env->context(),
903-
env->legacy_string(),
904-
v8::Boolean::New(env->isolate(), resolution.legacy),
905-
v8::ReadOnly).FromJust();
829+
Local<Value> resolved = resolution.ToObject(env).ToLocalChecked();
906830

907-
args.GetReturnValue().Set(resolved);
831+
args.GetReturnValue().Set(resolved);
908832
}
909833

910834
static MaybeLocal<Promise> ImportModuleDynamically(

src/module_wrap.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@ enum HostDefinedOptions : int {
2424
kLength = 10,
2525
};
2626

27-
struct ModuleResolution {
28-
url::URL url;
29-
bool legacy;
30-
};
31-
3227
class ModuleWrap : public BaseObject {
3328
public:
3429
static const std::string EXTENSIONS[];

0 commit comments

Comments
 (0)