Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9c68320

Browse filesBrowse files
GeoffreyBoothLiviaMedeiros
authored andcommitted
esm: unflag extensionless javascript and wasm in module scope
PR-URL: #49974 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com>
1 parent 23cb478 commit 9c68320
Copy full SHA for 9c68320

File tree

Expand file treeCollapse file tree

14 files changed

+174
-47
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

14 files changed

+174
-47
lines changed
Open diff view settings
Collapse file

‎doc/api/esm.md‎

Copy file name to clipboardExpand all lines: doc/api/esm.md
+6-2Lines changed: 6 additions & 2 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1009,8 +1009,12 @@ _isImports_, _conditions_)
10091009
> 5. Let _packageURL_ be the result of **LOOKUP\_PACKAGE\_SCOPE**(_url_).
10101010
> 6. Let _pjson_ be the result of **READ\_PACKAGE\_JSON**(_packageURL_).
10111011
> 7. If _pjson?.type_ exists and is _"module"_, then
1012-
> 1. If _url_ ends in _".js"_, then
1013-
> 1. Return _"module"_.
1012+
> 1. If _url_ ends in _".js"_ or has no file extension, then
1013+
> 1. If `--experimental-wasm-modules` is enabled and the file at _url_
1014+
> contains the header for a WebAssembly module, then
1015+
> 1. Return _"wasm"_.
1016+
> 2. Otherwise,
1017+
> 1. Return _"module"_.
10141018
> 2. Return **undefined**.
10151019
> 8. Otherwise,
10161020
> 1. Return **undefined**.
Collapse file

‎lib/internal/errors.js‎

Copy file name to clipboardExpand all lines: lib/internal/errors.js
+1-7Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,13 +1791,7 @@ E('ERR_UNHANDLED_ERROR',
17911791
E('ERR_UNKNOWN_BUILTIN_MODULE', 'No such built-in module: %s', Error);
17921792
E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error);
17931793
E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError);
1794-
E('ERR_UNKNOWN_FILE_EXTENSION', (ext, path, suggestion) => {
1795-
let msg = `Unknown file extension "${ext}" for ${path}`;
1796-
if (suggestion) {
1797-
msg += `. ${suggestion}`;
1798-
}
1799-
return msg;
1800-
}, TypeError);
1794+
E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension "%s" for %s', TypeError);
18011795
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s for URL %s',
18021796
RangeError);
18031797
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);
Collapse file

‎lib/internal/modules/esm/get_format.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/get_format.js
+11-23Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const {
99
StringPrototypeCharCodeAt,
1010
StringPrototypeSlice,
1111
} = primordials;
12-
const { basename, relative } = require('path');
1312
const { getOptionValue } = require('internal/options');
1413
const {
1514
extensionFormatMap,
@@ -22,7 +21,7 @@ const experimentalNetworkImports =
2221
const defaultTypeFlag = getOptionValue('--experimental-default-type');
2322
// The next line is where we flip the default to ES modules someday.
2423
const defaultType = defaultTypeFlag === 'module' ? 'module' : 'commonjs';
25-
const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve');
24+
const { getPackageType } = require('internal/modules/esm/resolve');
2625
const { fileURLToPath } = require('internal/url');
2726
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
2827

@@ -112,17 +111,16 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) {
112111
if (defaultType === 'commonjs') { // Legacy behavior
113112
if (packageType === 'none' || packageType === 'commonjs') {
114113
return 'commonjs';
115-
}
116-
// If package type is `module`, fall through to the error case below
117-
} else { // Else defaultType === 'module'
118-
if (underNodeModules(url)) { // Exception for package scopes under `node_modules`
119-
return 'commonjs';
120-
}
121-
if (packageType === 'none' || packageType === 'module') {
122-
return getFormatOfExtensionlessFile(url);
123-
} // Else packageType === 'commonjs'
124-
return 'commonjs';
114+
} // Else packageType === 'module'
115+
return getFormatOfExtensionlessFile(url);
116+
} // Else defaultType === 'module'
117+
if (underNodeModules(url)) { // Exception for package scopes under `node_modules`
118+
return packageType === 'module' ? getFormatOfExtensionlessFile(url) : 'commonjs';
125119
}
120+
if (packageType === 'none' || packageType === 'module') {
121+
return getFormatOfExtensionlessFile(url);
122+
} // Else packageType === 'commonjs'
123+
return 'commonjs';
126124
}
127125

128126
const format = extensionFormatMap[ext];
@@ -131,17 +129,7 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) {
131129
// Explicit undefined return indicates load hook should rerun format check
132130
if (ignoreErrors) { return undefined; }
133131
const filepath = fileURLToPath(url);
134-
let suggestion = '';
135-
if (getPackageType(url) === 'module' && ext === '') {
136-
const config = getPackageScopeConfig(url);
137-
const fileBasename = basename(filepath);
138-
const relativePath = StringPrototypeSlice(relative(config.pjsonPath, filepath), 1);
139-
suggestion = 'Loading extensionless files is not supported inside of "type":"module" package.json contexts ' +
140-
`without --experimental-default-type=module. The package.json file ${config.pjsonPath} caused this "type":"module" ` +
141-
`context. Try changing ${filepath} to have a file extension. Note the "bin" field of package.json can point ` +
142-
`to a file with an extension, for example {"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`;
143-
}
144-
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath, suggestion);
132+
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath);
145133
}
146134

147135
/**
Collapse file
+106Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Flags: --experimental-wasm-modules
2+
import { mustNotCall, spawnPromisified } from '../common/index.mjs';
3+
import * as fixtures from '../common/fixtures.mjs';
4+
import { describe, it } from 'node:test';
5+
import { match, ok, strictEqual } from 'node:assert';
6+
7+
describe('extensionless ES modules within a "type": "module" package scope', { concurrency: true }, () => {
8+
it('should run as the entry point', async () => {
9+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
10+
fixtures.path('es-modules/package-type-module/noext-esm'),
11+
]);
12+
13+
strictEqual(stderr, '');
14+
strictEqual(stdout, 'executed\n');
15+
strictEqual(code, 0);
16+
strictEqual(signal, null);
17+
});
18+
19+
it('should be importable', async () => {
20+
const { default: defaultExport } =
21+
await import(fixtures.fileURL('es-modules/package-type-module/noext-esm'));
22+
strictEqual(defaultExport, 'module');
23+
});
24+
25+
it('should be importable from a module scope under node_modules', async () => {
26+
const { default: defaultExport } =
27+
await import(fixtures.fileURL(
28+
'es-modules/package-type-module/node_modules/dep-with-package-json-type-module/noext-esm'));
29+
strictEqual(defaultExport, 'module');
30+
});
31+
});
32+
describe('extensionless Wasm modules within a "type": "module" package scope', { concurrency: true }, () => {
33+
it('should run as the entry point', async () => {
34+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
35+
'--experimental-wasm-modules',
36+
'--no-warnings',
37+
fixtures.path('es-modules/package-type-module/noext-wasm'),
38+
]);
39+
40+
strictEqual(stderr, '');
41+
strictEqual(stdout, 'executed\n');
42+
strictEqual(code, 0);
43+
strictEqual(signal, null);
44+
});
45+
46+
it('should be importable', async () => {
47+
const { add } = await import(fixtures.fileURL('es-modules/package-type-module/noext-wasm'));
48+
strictEqual(add(1, 2), 3);
49+
});
50+
51+
it('should be importable from a module scope under node_modules', async () => {
52+
const { add } = await import(fixtures.fileURL(
53+
'es-modules/package-type-module/node_modules/dep-with-package-json-type-module/noext-wasm'));
54+
strictEqual(add(1, 2), 3);
55+
});
56+
});
57+
58+
describe('extensionless ES modules within no package scope', { concurrency: true }, () => {
59+
// This succeeds with `--experimental-default-type=module`
60+
it('should error as the entry point', async () => {
61+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
62+
fixtures.path('es-modules/noext-esm'),
63+
]);
64+
65+
match(stderr, /SyntaxError/);
66+
strictEqual(stdout, '');
67+
strictEqual(code, 1);
68+
strictEqual(signal, null);
69+
});
70+
71+
// This succeeds with `--experimental-default-type=module`
72+
it('should error on import', async () => {
73+
try {
74+
await import(fixtures.fileURL('es-modules/noext-esm'));
75+
mustNotCall();
76+
} catch (err) {
77+
ok(err instanceof SyntaxError);
78+
}
79+
});
80+
});
81+
82+
describe('extensionless Wasm within no package scope', { concurrency: true }, () => {
83+
// This succeeds with `--experimental-default-type=module`
84+
it('should error as the entry point', async () => {
85+
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
86+
'--experimental-wasm-modules',
87+
'--no-warnings',
88+
fixtures.path('es-modules/noext-wasm'),
89+
]);
90+
91+
match(stderr, /SyntaxError/);
92+
strictEqual(stdout, '');
93+
strictEqual(code, 1);
94+
strictEqual(signal, null);
95+
});
96+
97+
// This succeeds with `--experimental-default-type=module`
98+
it('should error on import', async () => {
99+
try {
100+
await import(fixtures.fileURL('es-modules/noext-wasm'));
101+
mustNotCall();
102+
} catch (err) {
103+
ok(err instanceof SyntaxError);
104+
}
105+
});
106+
});
Collapse file

‎test/es-module/test-esm-type-flag-package-scopes.mjs‎

Copy file name to clipboardExpand all lines: test/es-module/test-esm-type-flag-package-scopes.mjs
+21-4Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ describe('the type flag should change the interpretation of certain files within
2424
strictEqual(defaultExport, 'module');
2525
});
2626

27+
it('should import an extensionless JavaScript file within a "type": "module" scope under node_modules',
28+
async () => {
29+
const { default: defaultExport } =
30+
await import(fixtures.fileURL(
31+
'es-modules/package-type-module/node_modules/dep-with-package-json-type-module/noext-esm'));
32+
strictEqual(defaultExport, 'module');
33+
});
34+
2735
it('should run as Wasm an extensionless Wasm file within a "type": "module" scope', async () => {
2836
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
2937
'--experimental-default-type=module',
@@ -42,6 +50,13 @@ describe('the type flag should change the interpretation of certain files within
4250
const { add } = await import(fixtures.fileURL('es-modules/package-type-module/noext-wasm'));
4351
strictEqual(add(1, 2), 3);
4452
});
53+
54+
it('should import an extensionless Wasm file within a "type": "module" scope under node_modules',
55+
async () => {
56+
const { add } = await import(fixtures.fileURL(
57+
'es-modules/package-type-module/node_modules/dep-with-package-json-type-module/noext-wasm'));
58+
strictEqual(add(1, 2), 3);
59+
});
4560
});
4661

4762
describe(`the type flag should change the interpretation of certain files within a package scope that lacks a
@@ -112,7 +127,7 @@ describe(`the type flag should NOT change the interpretation of certain files wi
112127
async () => {
113128
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
114129
'--experimental-default-type=module',
115-
fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'),
130+
fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json-without-type/run.js'),
116131
]);
117132

118133
strictEqual(stderr, '');
@@ -124,15 +139,16 @@ describe(`the type flag should NOT change the interpretation of certain files wi
124139
it(`should import as CommonJS a .js file within a package scope that has no defined "type" and is under
125140
node_modules`, async () => {
126141
const { default: defaultExport } =
127-
await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/run.js'));
142+
await import(fixtures.fileURL(
143+
'es-modules/package-type-module/node_modules/dep-with-package-json-without-type/run.js'));
128144
strictEqual(defaultExport, 42);
129145
});
130146

131147
it(`should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is
132148
under node_modules`, async () => {
133149
const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [
134150
'--experimental-default-type=module',
135-
fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs'),
151+
fixtures.path('es-modules/package-type-module/node_modules/dep-with-package-json-without-type/noext-cjs'),
136152
]);
137153

138154
strictEqual(stderr, '');
@@ -144,7 +160,8 @@ describe(`the type flag should NOT change the interpretation of certain files wi
144160
it(`should import as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and
145161
is under node_modules`, async () => {
146162
const { default: defaultExport } =
147-
await import(fixtures.fileURL('es-modules/package-type-module/node_modules/dep-with-package-json/noext-cjs'));
163+
await import(fixtures.fileURL(
164+
'es-modules/package-type-module/node_modules/dep-with-package-json-without-type/noext-cjs'));
148165
strictEqual(defaultExport, 42);
149166
});
150167
});
Collapse file

‎…dule/test-esm-unknown-or-no-extension.js‎ ‎…/es-module/test-esm-unknown-extension.js‎test/es-module/test-esm-unknown-or-no-extension.js renamed to test/es-module/test-esm-unknown-extension.js test/es-module/test-esm-unknown-or-no-extension.js renamed to test/es-module/test-esm-unknown-extension.js

Copy file name to clipboardExpand all lines: test/es-module/test-esm-unknown-extension.js
+3-10Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ const { execPath } = require('node:process');
77
const { describe, it } = require('node:test');
88

99

10-
// In a "type": "module" package scope, files with unknown extensions or no
11-
// extensions should throw; both when used as a main entry point and also when
12-
// referenced via `import`.
13-
describe('ESM: extensionless and unknown specifiers', { concurrency: true }, () => {
10+
// In a "type": "module" package scope, files with unknown extensions should throw;
11+
// both when used as a main entry point and also when referenced via `import`.
12+
describe('ESM: unknown specifiers', { concurrency: true }, () => {
1413
for (
1514
const fixturePath of [
16-
'/es-modules/package-type-module/noext-esm',
17-
'/es-modules/package-type-module/imports-noext.mjs',
1815
'/es-modules/package-type-module/extension.unknown',
1916
'/es-modules/package-type-module/imports-unknownext.mjs',
2017
]
@@ -27,10 +24,6 @@ describe('ESM: extensionless and unknown specifiers', { concurrency: true }, ()
2724
assert.strictEqual(signal, null);
2825
assert.strictEqual(stdout, '');
2926
assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/);
30-
if (fixturePath.includes('noext')) {
31-
// Check for explanation to users
32-
assert.match(stderr, /extensionless/);
33-
}
3427
});
3528
}
3629
});
Collapse file

‎test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json-type-module/noext-esm‎

Copy file name to clipboardExpand all lines: test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json-type-module/noext-esm
+2Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Collapse file

‎test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json-type-module/package.json‎

Copy file name to clipboardExpand all lines: test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json-type-module/package.json
+8Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Collapse file

‎test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json-type-module/wasm-dep.mjs‎

Copy file name to clipboardExpand all lines: test/fixtures/es-modules/package-type-module/node_modules/dep-with-package-json-type-module/wasm-dep.mjs
+15Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.