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

Commit bec588f

Browse files
committed
esm: add --es-module-specifier-resolution
There are currently two supported values "none" and "node"
1 parent f6e1a43 commit bec588f

File tree

30 files changed

+172
-76
lines changed

30 files changed

+172
-76
lines changed

doc/api/esm.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,6 @@ READ_PACKAGE_JSON(_packageURL_)
664664
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
665665
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
666666
[`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename
667-
[`import.meta.url`]: esm.html#importmeta
668-
[`import()`]: esm.html#import-expressions
667+
[`import.meta.url`]: #esm_import_meta
668+
[`import()`]: #esm_import-expressions
669669
[ecmascript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md

src/module_wrap.cc

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

46+
static const char* const EXTENSIONS[] = {
47+
".mjs",
48+
".cjs",
49+
".js",
50+
".json",
51+
".node"
52+
};
53+
4654
ModuleWrap::ModuleWrap(Environment* env,
4755
Local<Object> object,
4856
Local<Module> module,
@@ -667,13 +675,57 @@ Maybe<URL> LegacyMainResolve(const URL& pjson_url,
667675
return Nothing<URL>();
668676
}
669677

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

676-
if (check_exists && CheckDescriptorAtPath(path) != FILE) {
727+
const std::string& path = resolved.ToFilePath();
728+
if (CheckDescriptorAtPath(path) != FILE) {
677729
std::string msg = "Cannot find module '" + path +
678730
"' imported from " + base.ToFilePath();
679731
node::THROW_ERR_MODULE_NOT_FOUND(env, msg.c_str());
@@ -684,32 +736,36 @@ Maybe<URL> FinalizeResolution(Environment* env,
684736
}
685737

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

715771
Maybe<URL> PackageResolve(Environment* env,
@@ -759,7 +815,7 @@ Maybe<URL> PackageResolve(Environment* env,
759815
if (!pkg_subpath.length()) {
760816
return PackageMainResolve(env, pjson_url, *pcfg.FromJust(), base);
761817
} else {
762-
return FinalizeResolution(env, URL(pkg_subpath, pjson_url), base, true);
818+
return FinalizeResolution(env, URL(pkg_subpath, pjson_url), base);
763819
}
764820
CHECK(false);
765821
// Cross-platform root check.
@@ -789,7 +845,7 @@ Maybe<URL> Resolve(Environment* env,
789845
return PackageResolve(env, specifier, base);
790846
}
791847
}
792-
return FinalizeResolution(env, resolved, base, true);
848+
return FinalizeResolution(env, resolved, base);
793849
}
794850

795851
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
@@ -251,6 +251,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
251251
"custom loader",
252252
&EnvironmentOptions::userland_loader,
253253
kAllowedInEnvironment);
254+
AddOption("--es-module-specifier-resolution",
255+
"Select extension resolution algorithm for es modules; "
256+
"either 'explicit' (default) or 'node'",
257+
&EnvironmentOptions::es_module_specifier_resolution,
258+
kAllowedInEnvironment);
254259
AddOption("--no-deprecation",
255260
"silence deprecation warnings",
256261
&EnvironmentOptions::no_deprecation,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class EnvironmentOptions : public Options {
8686
public:
8787
bool abort_on_uncaught_exception = false;
8888
bool experimental_modules = false;
89+
std::string es_module_specifier_resolution = "explicit";
8990
std::string module_type;
9091
std::string experimental_policy;
9192
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)