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

Commit ffa64db

Browse files
committed
esm: add --es-module-specifier-resolution
There are currently two supported values "explicit" and "node"
1 parent baec319 commit ffa64db

File tree

29 files changed

+170
-74
lines changed

29 files changed

+170
-74
lines changed

src/module_wrap.cc

Lines changed: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ using v8::String;
4545
using v8::Undefined;
4646
using v8::Value;
4747

48+
static const char* const EXTENSIONS[] = {
49+
".mjs",
50+
".cjs",
51+
".js",
52+
".json",
53+
".node"
54+
};
55+
4856
ModuleWrap::ModuleWrap(Environment* env,
4957
Local<Object> object,
5058
Local<Module> module,
@@ -669,13 +677,57 @@ Maybe<URL> LegacyMainResolve(const URL& pjson_url,
669677
return Nothing<URL>();
670678
}
671679

680+
enum ResolveExtensionsOptions {
681+
TRY_EXACT_NAME,
682+
ONLY_VIA_EXTENSIONS
683+
};
684+
685+
template <ResolveExtensionsOptions options>
686+
Maybe<URL> ResolveExtensions(const URL& search) {
687+
if (options == TRY_EXACT_NAME) {
688+
if (FileExists(search)) {
689+
return Just(search);
690+
}
691+
}
692+
693+
for (const char* extension : EXTENSIONS) {
694+
URL guess(search.path() + extension, &search);
695+
if (FileExists(guess)) {
696+
return Just(guess);
697+
}
698+
}
699+
700+
return Nothing<URL>();
701+
}
702+
703+
inline Maybe<URL> ResolveIndex(const URL& search) {
704+
return ResolveExtensions<ONLY_VIA_EXTENSIONS>(URL("index", search));
705+
}
706+
672707
Maybe<URL> FinalizeResolution(Environment* env,
673-
const URL& resolved,
674-
const URL& base,
675-
bool check_exists) {
676-
const std::string& path = resolved.ToFilePath();
708+
const URL& resolved,
709+
const URL& base) {
710+
if (env->options()->es_module_specifier_resolution == "node") {
711+
Maybe<URL> file = ResolveExtensions<TRY_EXACT_NAME>(resolved);
712+
if (!file.IsNothing()) {
713+
return file;
714+
}
715+
if (resolved.path().back() != '/') {
716+
file = ResolveIndex(URL(resolved.path() + "/", &base));
717+
} else {
718+
file = ResolveIndex(resolved);
719+
}
720+
if (!file.IsNothing()) {
721+
return file;
722+
}
723+
std::string msg = "Cannot find module '" + resolved.path() +
724+
"' imported from " + base.ToFilePath();
725+
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
726+
return Nothing<URL>();
727+
}
677728

678-
if (check_exists && CheckDescriptorAtPath(path) != FILE) {
729+
const std::string& path = resolved.ToFilePath();
730+
if (CheckDescriptorAtPath(path) != FILE) {
679731
std::string msg = "Cannot find module '" + path +
680732
"' imported from " + base.ToFilePath();
681733
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
@@ -686,32 +738,36 @@ Maybe<URL> FinalizeResolution(Environment* env,
686738
}
687739

688740
Maybe<URL> PackageMainResolve(Environment* env,
689-
const URL& pjson_url,
690-
const PackageConfig& pcfg,
691-
const URL& base) {
692-
if (pcfg.exists == Exists::No || (
693-
pcfg.esm == IsESM::Yes && pcfg.has_main == HasMain::No)) {
694-
std::string msg = "Cannot find main entry point for '" +
695-
URL(".", pjson_url).ToFilePath() + "' imported from " +
696-
base.ToFilePath();
697-
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
698-
return Nothing<URL>();
699-
}
700-
if (pcfg.has_main == HasMain::Yes &&
701-
pcfg.main.substr(pcfg.main.length() - 4, 4) == ".mjs") {
702-
return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true);
703-
}
704-
if (pcfg.esm == IsESM::Yes &&
705-
pcfg.main.substr(pcfg.main.length() - 3, 3) == ".js") {
706-
return FinalizeResolution(env, URL(pcfg.main, pjson_url), base, true);
707-
}
708-
709-
Maybe<URL> resolved = LegacyMainResolve(pjson_url, pcfg);
710-
// Legacy main resolution error
711-
if (resolved.IsNothing()) {
712-
return Nothing<URL>();
741+
const URL& pjson_url,
742+
const PackageConfig& pcfg,
743+
const URL& base) {
744+
if (pcfg.exists == Exists::Yes) {
745+
if (pcfg.has_main == HasMain::Yes) {
746+
URL resolved(pcfg.main, pjson_url);
747+
const std::string& path = resolved.ToFilePath();
748+
if (CheckDescriptorAtPath(path) == FILE) {
749+
return Just(resolved);
750+
}
751+
}
752+
if (env->options()->es_module_specifier_resolution == "node") {
753+
if (pcfg.has_main == HasMain::Yes) {
754+
return FinalizeResolution(env, URL(pcfg.main, pjson_url), base);
755+
} else {
756+
return FinalizeResolution(env, URL("index", pjson_url), base);
757+
}
758+
}
759+
if (pcfg.esm == IsESM::No) {
760+
Maybe<URL> resolved = LegacyMainResolve(pjson_url, pcfg);
761+
if (!resolved.IsNothing()) {
762+
return resolved;
763+
}
764+
}
713765
}
714-
return resolved;
766+
std::string msg = "Cannot find main entry point for '" +
767+
URL(".", pjson_url).ToFilePath() + "' imported from " +
768+
base.ToFilePath();
769+
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
770+
return Nothing<URL>();
715771
}
716772

717773
Maybe<URL> PackageResolve(Environment* env,
@@ -761,7 +817,7 @@ Maybe<URL> PackageResolve(Environment* env,
761817
if (!pkg_subpath.length()) {
762818
return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base);
763819
} else {
764-
return FinalizeResolution(env, URL(pkg_subpath, pjson_url), base, true);
820+
return FinalizeResolution(env, URL(pkg_subpath, pjson_url), base);
765821
}
766822
CHECK(false);
767823
// Cross-platform root check.
@@ -791,7 +847,7 @@ Maybe<URL> Resolve(Environment* env,
791847
return PackageResolve(env, specifier, base);
792848
}
793849
}
794-
return FinalizeResolution(env, resolved, base, true);
850+
return FinalizeResolution(env, resolved, base);
795851
}
796852

797853
void ModuleWrap::Resolve(const FunctionCallbackInfo<Value>& args) {

src/node_options.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
253253
"custom loader",
254254
&EnvironmentOptions::userland_loader,
255255
kAllowedInEnvironment);
256+
AddOption("--es-module-specifier-resolution",
257+
"Select extension resolution algorithm for es modules; "
258+
"either 'explicit' (default) or 'node'",
259+
&EnvironmentOptions::es_module_specifier_resolution,
260+
kAllowedInEnvironment);
256261
AddOption("--no-deprecation",
257262
"silence deprecation warnings",
258263
&EnvironmentOptions::no_deprecation,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class EnvironmentOptions : public Options {
9292
public:
9393
bool abort_on_uncaught_exception = false;
9494
bool experimental_modules = false;
95+
std::string es_module_specifier_resolution = "explicit";
9596
std::string module_type;
9697
std::string experimental_policy;
9798
bool experimental_repl_await = false;

test/es-module/test-esm-package-scope.mjs

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Flags: --experimental-modules --es-module-specifier-resolution=node
2+
import { mustNotCall } from '../common';
3+
import assert from 'assert';
4+
5+
// commonJS index.js
6+
import commonjs from '../fixtures/es-module-specifiers/package-type-commonjs';
7+
// esm index.js
8+
import module from '../fixtures/es-module-specifiers/package-type-module';
9+
// notice the trailing slash
10+
import success, { explicit, implicit, implicitModule, getImplicitCommonjs }
11+
from '../fixtures/es-module-specifiers/';
12+
13+
assert.strictEqual(commonjs, 'commonjs');
14+
assert.strictEqual(module, 'module');
15+
assert.strictEqual(success, 'success');
16+
assert.strictEqual(explicit, 'esm');
17+
assert.strictEqual(implicit, 'esm');
18+
assert.strictEqual(implicitModule, 'esm');
19+
20+
async function main() {
21+
try {
22+
await import('../fixtures/es-module-specifiers/do-not-exist.js');
23+
} catch (e) {
24+
// Files that do not exist should throw
25+
assert.strictEqual(e.name, 'Error');
26+
}
27+
try {
28+
await getImplicitCommonjs();
29+
} catch (e) {
30+
// Legacy loader cannot resolve .mjs automatically from main
31+
assert.strictEqual(e.name, 'Error');
32+
}
33+
}
34+
35+
main().catch(mustNotCall);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import explicit from 'explicit-main';
2+
import implicit from 'implicit-main';
3+
import implicitModule from 'implicit-main-type-module';
4+
5+
function getImplicitCommonjs () {
6+
return import('implicit-main-type-commonjs');
7+
}
8+
9+
export {explicit, implicit, implicitModule, getImplicitCommonjs};
10+
export default 'success';

test/fixtures/es-module-specifiers/node_modules/explicit-main/entry.mjs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/explicit-main/package.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main-type-commonjs/entry.mjs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main-type-commonjs/package.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main-type-module/entry.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main-type-module/entry.mjs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main-type-module/package.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main/entry.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main/entry.mjs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/es-module-specifiers/node_modules/implicit-main/package.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/esm-package-scope/legacy-loader/index.mjs renamed to test/fixtures/es-module-specifiers/package-type-commonjs/index.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {b} from './b.mjs';
55
// import 'c.cjs';
66
import cjs from './c.cjs';
77
// proves cross boundary fun bits
8-
import jsAsEsm from '../new-loader/a.js';
8+
import jsAsEsm from '../package-type-module/a.js';
99

1010
// named export from core
1111
import {strictEqual, deepStrictEqual} from 'assert';
@@ -18,4 +18,4 @@ deepStrictEqual(cjs, {
1818
three: 3
1919
});
2020

21-
export default 'legacy-loader';
21+
export default 'commonjs';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "commonjs"
3+
}

test/fixtures/esm-package-scope/new-loader/index.js renamed to test/fixtures/es-module-specifiers/package-type-module/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {b} from './b.mjs';
55
// import 'c.cjs';
66
import cjs from './c.cjs';
77
// import across boundaries
8-
import jsAsCjs from '../legacy-loader/a.js'
8+
import jsAsCjs from '../package-type-commonjs/a.js'
99

1010
// named export from core
1111
import {strictEqual, deepStrictEqual} from 'assert';
@@ -18,4 +18,4 @@ deepStrictEqual(cjs, {
1818
three: 3
1919
});
2020

21-
export default 'new-loader';
21+
export default 'module';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"type": "module"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

test/fixtures/esm-package-scope/legacy-loader/package.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

test/fixtures/esm-package-scope/new-loader/package.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)