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

Commit 6ab6680

Browse files
MylesBorinsdevsnek
authored andcommitted
esm: remove node specifier resolution algorithm
Refs: nodejs/modules#180 PR-URL: #6 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: John-David Dalton <john.david.dalton@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
1 parent eb251aa commit 6ab6680

37 files changed

Lines changed: 120 additions & 173 deletions

doc/api/esm.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ property:
5858

5959
ESM must have the `.mjs` extension.
6060

61+
### Mandatory file extensions
62+
63+
You must provide a file extension when using the `import` keyword.
64+
65+
### No importing directories
66+
67+
There is no support for importing directories.
68+
6169
### No NODE_PATH
6270

6371
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
@@ -82,21 +90,15 @@ Modules will be loaded multiple times if the `import` specifier used to resolve
8290
them have a different query or fragment.
8391

8492
```js
85-
import './foo?query=1'; // loads ./foo with query of "?query=1"
86-
import './foo?query=2'; // loads ./foo with query of "?query=2"
93+
import './foo.mjs?query=1'; // loads ./foo.mjs with query of "?query=1"
94+
import './foo.mjs?query=2'; // loads ./foo.mjs with query of "?query=2"
8795
```
8896

8997
For now, only modules using the `file:` protocol can be loaded.
9098

91-
## Interop with existing modules
99+
## CommonJS, JSON, and Native Modules
92100

93-
CommonJS and C++ modules can be used with `import`.
94-
95-
Modules loaded this way will only be loaded once, even if their query
96-
or fragment string differs between `import` statements.
97-
98-
When loaded via `import` these modules will provide a single `default` export
99-
representing the value of `module.exports` at the time they finished evaluating.
101+
CommonJS, JSON, and Native modules can be used with [`module.createRequireFromPath()`][`module.createRequireFromPath()`].
100102

101103
```js
102104
// cjs.js
@@ -112,6 +114,8 @@ const cjs = require('./cjs');
112114
cjs === 'cjs'; // true
113115
```
114116
117+
## Builtin modules
118+
115119
Builtin modules will provide named exports of their public API, as well as a
116120
default export which can be used for, among other things, modifying the named
117121
exports. Named exports of builtin modules are updated when the corresponding
@@ -261,3 +265,4 @@ in the import tree.
261265
262266
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
263267
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
268+
[`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename

lib/internal/modules/cjs/loader.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const {
4444
const preserveSymlinks = !!process.binding('config').preserveSymlinks;
4545
const preserveSymlinksMain = !!process.binding('config').preserveSymlinksMain;
4646
const experimentalModules = !!process.binding('config').experimentalModules;
47+
const hasLoader = !!process.binding('config').userLoader;
4748

4849
const {
4950
ERR_INVALID_ARG_TYPE,
@@ -755,7 +756,11 @@ if (experimentalModules) {
755756
// bootstrap main module.
756757
Module.runMain = function() {
757758
// Load the main module--the command line argument.
758-
if (experimentalModules) {
759+
const base = path.basename(process.argv[1]);
760+
const ext = path.extname(base);
761+
const isESM = ext === '.mjs';
762+
763+
if (experimentalModules && (isESM || hasLoader)) {
759764
if (asyncESM === undefined) lazyLoadESM();
760765
asyncESM.loaderPromise.then((loader) => {
761766
return loader.import(pathToFileURL(process.argv[1]).pathname);

src/module_wrap.cc

Lines changed: 11 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ using v8::HandleScope;
2626
using v8::Integer;
2727
using v8::IntegrityLevel;
2828
using v8::Isolate;
29-
using v8::JSON;
3029
using v8::Just;
3130
using v8::Local;
3231
using v8::Maybe;
@@ -505,70 +504,17 @@ Maybe<uv_file> CheckFile(const std::string& path,
505504
return Just(fd);
506505
}
507506

508-
using Exists = PackageConfig::Exists;
509-
using IsValid = PackageConfig::IsValid;
510-
using HasMain = PackageConfig::HasMain;
511-
512-
const PackageConfig& GetPackageConfig(Environment* env,
513-
const std::string& path) {
514-
auto existing = env->package_json_cache.find(path);
515-
if (existing != env->package_json_cache.end()) {
516-
return existing->second;
517-
}
518-
Maybe<uv_file> check = CheckFile(path, LEAVE_OPEN_AFTER_CHECK);
519-
if (check.IsNothing()) {
520-
auto entry = env->package_json_cache.emplace(path,
521-
PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "" });
522-
return entry.first->second;
523-
}
524-
525-
Isolate* isolate = env->isolate();
526-
v8::HandleScope handle_scope(isolate);
527-
528-
std::string pkg_src = ReadFile(check.FromJust());
529-
uv_fs_t fs_req;
530-
CHECK_EQ(0, uv_fs_close(nullptr, &fs_req, check.FromJust(), nullptr));
531-
uv_fs_req_cleanup(&fs_req);
532-
533-
Local<String> src;
534-
if (!String::NewFromUtf8(isolate,
535-
pkg_src.c_str(),
536-
v8::NewStringType::kNormal,
537-
pkg_src.length()).ToLocal(&src)) {
538-
auto entry = env->package_json_cache.emplace(path,
539-
PackageConfig { Exists::No, IsValid::Yes, HasMain::No, "" });
540-
return entry.first->second;
541-
}
542-
543-
Local<Value> pkg_json_v;
544-
Local<Object> pkg_json;
545-
546-
if (!JSON::Parse(env->context(), src).ToLocal(&pkg_json_v) ||
547-
!pkg_json_v->ToObject(env->context()).ToLocal(&pkg_json)) {
548-
auto entry = env->package_json_cache.emplace(path,
549-
PackageConfig { Exists::Yes, IsValid::No, HasMain::No, "" });
550-
return entry.first->second;
551-
}
552-
553-
Local<Value> pkg_main;
554-
HasMain has_main = HasMain::No;
555-
std::string main_std;
556-
if (pkg_json->Get(env->context(), env->main_string()).ToLocal(&pkg_main)) {
557-
has_main = HasMain::Yes;
558-
Utf8Value main_utf8(isolate, pkg_main);
559-
main_std.assign(std::string(*main_utf8, main_utf8.length()));
560-
}
561-
562-
auto entry = env->package_json_cache.emplace(path,
563-
PackageConfig { Exists::Yes, IsValid::Yes, has_main, main_std });
564-
return entry.first->second;
565-
}
566-
567507
enum ResolveExtensionsOptions {
568508
TRY_EXACT_NAME,
569509
ONLY_VIA_EXTENSIONS
570510
};
571511

512+
inline bool ResolvesToFile(const URL& search) {
513+
std::string filePath = search.ToFilePath();
514+
Maybe<uv_file> check = CheckFile(filePath);
515+
return !check.IsNothing();
516+
}
517+
572518
template <ResolveExtensionsOptions options>
573519
Maybe<URL> ResolveExtensions(const URL& search) {
574520
if (options == TRY_EXACT_NAME) {
@@ -594,24 +540,6 @@ inline Maybe<URL> ResolveIndex(const URL& search) {
594540
return ResolveExtensions<ONLY_VIA_EXTENSIONS>(URL("index", search));
595541
}
596542

597-
Maybe<URL> ResolveMain(Environment* env, const URL& search) {
598-
URL pkg("package.json", &search);
599-
600-
const PackageConfig& pjson =
601-
GetPackageConfig(env, pkg.ToFilePath());
602-
// Note invalid package.json should throw in resolver
603-
// currently we silently ignore which is incorrect
604-
if (pjson.exists == Exists::No ||
605-
pjson.is_valid == IsValid::No ||
606-
pjson.has_main == HasMain::No) {
607-
return Nothing<URL>();
608-
}
609-
if (!ShouldBeTreatedAsRelativeOrAbsolutePath(pjson.main)) {
610-
return Resolve(env, "./" + pjson.main, search, IgnoreMain);
611-
}
612-
return Resolve(env, pjson.main, search, IgnoreMain);
613-
}
614-
615543
Maybe<URL> ResolveModule(Environment* env,
616544
const std::string& specifier,
617545
const URL& base) {
@@ -620,7 +548,7 @@ Maybe<URL> ResolveModule(Environment* env,
620548
do {
621549
dir = parent;
622550
Maybe<URL> check =
623-
Resolve(env, "./node_modules/" + specifier, dir, CheckMain);
551+
Resolve(env, "./node_modules/" + specifier, dir);
624552
if (!check.IsNothing()) {
625553
const size_t limit = specifier.find('/');
626554
const size_t spec_len =
@@ -640,23 +568,11 @@ Maybe<URL> ResolveModule(Environment* env,
640568
return Nothing<URL>();
641569
}
642570

643-
Maybe<URL> ResolveDirectory(Environment* env,
644-
const URL& search,
645-
PackageMainCheck check_pjson_main) {
646-
if (check_pjson_main) {
647-
Maybe<URL> main = ResolveMain(env, search);
648-
if (!main.IsNothing())
649-
return main;
650-
}
651-
return ResolveIndex(search);
652-
}
653-
654571
} // anonymous namespace
655572

656573
Maybe<URL> Resolve(Environment* env,
657574
const std::string& specifier,
658-
const URL& base,
659-
PackageMainCheck check_pjson_main) {
575+
const URL& base) {
660576
URL pure_url(specifier);
661577
if (!(pure_url.flags() & URL_FLAGS_FAILED)) {
662578
// just check existence, without altering
@@ -671,13 +587,9 @@ Maybe<URL> Resolve(Environment* env,
671587
}
672588
if (ShouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
673589
URL resolved(specifier, base);
674-
Maybe<URL> file = ResolveExtensions<TRY_EXACT_NAME>(resolved);
675-
if (!file.IsNothing())
676-
return file;
677-
if (specifier.back() != '/') {
678-
resolved = URL(specifier + "/", base);
679-
}
680-
return ResolveDirectory(env, resolved, check_pjson_main);
590+
if (ResolvesToFile(resolved))
591+
return Just(resolved);
592+
return Nothing<URL>();
681593
} else {
682594
return ResolveModule(env, specifier, base);
683595
}

src/module_wrap.h

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,9 @@
1212
namespace node {
1313
namespace loader {
1414

15-
enum PackageMainCheck : bool {
16-
CheckMain = true,
17-
IgnoreMain = false
18-
};
19-
20-
enum ScriptType : int {
21-
kScript,
22-
kModule,
23-
};
24-
25-
enum HostDefinedOptions : int {
26-
kType = 8,
27-
kID = 9,
28-
kLength = 10,
29-
};
30-
3115
v8::Maybe<url::URL> Resolve(Environment* env,
3216
const std::string& specifier,
33-
const url::URL& base,
34-
PackageMainCheck read_pkg_json = CheckMain);
17+
const url::URL& base);
3518

3619
class ModuleWrap : public BaseObject {
3720
public:

test/es-module/test-esm-basic-imports.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Flags: --experimental-modules
2-
import '../common';
2+
/* eslint-disable node-core/required-modules */
3+
import '../common/index.mjs';
34
import assert from 'assert';
45
import ok from '../fixtures/es-modules/test-esm-ok.mjs';
56
import okShebang from './test-esm-shebang.mjs';
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
// Flags: --experimental-modules
2-
import '../common';
3-
import('./test-esm-cyclic-dynamic-import');
2+
/* eslint-disable node-core/required-modules */
3+
import '../common/index.mjs';
4+
import('./test-esm-cyclic-dynamic-import.mjs');

test/es-module/test-esm-double-encoding.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Flags: --experimental-modules
2-
import '../common';
2+
/* eslint-disable node-core/required-modules */
3+
import '../common/index.mjs';
34

45
// Assert we can import files with `%` in their pathname.
56

test/es-module/test-esm-encoded-path.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Flags: --experimental-modules
2-
import '../common';
2+
/* eslint-disable node-core/required-modules */
3+
import '../common/index.mjs';
34
import assert from 'assert';
45
// ./test-esm-ok.mjs
56
import ok from '../fixtures/es-modules/test-%65%73%6d-ok.mjs';

test/es-module/test-esm-forbidden-globals.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Flags: --experimental-modules
2-
import '../common';
2+
/* eslint-disable node-core/required-modules */
3+
import '../common/index.mjs';
34

45
// eslint-disable-next-line no-undef
56
if (typeof arguments !== 'undefined') {

test/es-module/test-esm-import-meta.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Flags: --experimental-modules
2+
/* eslint-disable node-core/required-modules */
23

3-
import '../common';
4+
import '../common/index.mjs';
45
import assert from 'assert';
56

67
assert.strictEqual(Object.getPrototypeOf(import.meta), null);

0 commit comments

Comments
 (0)