GeezerVison is showing github.com — esm: unflag extensionless javascript and wasm in module scope · nodejs/node@fe69198
File treeExpand file treeCollapse file tree Open diff view settings fixtures/es-modules/package-type-module/node_modules dep-with-package-json-type-module dep-with-package-json-without-type
Expand file treeCollapse file tree Open diff view settings Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -1008,8 +1008,12 @@ _isImports_, _conditions_) | 1008 | 1008 | > 5. Let _packageURL_ be the result of **LOOKUP\_PACKAGE\_SCOPE**(_url_). | 1009 | 1009 | > 6. Let _pjson_ be the result of **READ\_PACKAGE\_JSON**(_packageURL_). | 1010 | 1010 | > 7. If _pjson?.type_ exists and is _"module"_, then | 1011 | | -> 1. If _url_ ends in _".js"_, then | 1012 | | -> 1. Return _"module"_. | | 1011 | +> 1. If _url_ ends in _".js"_ or has no file extension, then | | 1012 | +> 1. If `--experimental-wasm-modules` is enabled and the file at _url_ | | 1013 | +> contains the header for a WebAssembly module, then | | 1014 | +> 1. Return _"wasm"_. | | 1015 | +> 2. Otherwise, | | 1016 | +> 1. Return _"module"_. | 1013 | 1017 | > 2. Return **undefined**. | 1014 | 1018 | > 8. Otherwise, | 1015 | 1019 | > 1. Return **undefined**. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/errors.js +1-7Lines changed: 1 addition & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1686,13 +1686,7 @@ E('ERR_UNHANDLED_ERROR', | 1686 | 1686 | E('ERR_UNKNOWN_BUILTIN_MODULE', 'No such built-in module: %s', Error); | 1687 | 1687 | E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error); | 1688 | 1688 | E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError); | 1689 | | -E('ERR_UNKNOWN_FILE_EXTENSION', (ext, path, suggestion) => { | 1690 | | - let msg = `Unknown file extension "${ext}" for ${path}`; | 1691 | | - if (suggestion) { | 1692 | | - msg += `. ${suggestion}`; | 1693 | | - } | 1694 | | - return msg; | 1695 | | -}, TypeError); | | 1689 | +E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension "%s" for %s', TypeError); | 1696 | 1690 | E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s for URL %s', | 1697 | 1691 | RangeError); | 1698 | 1692 | E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError); | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/modules/esm/get_format.js +11-23Lines changed: 11 additions & 23 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 9 | 9 | StringPrototypeCharCodeAt, | 10 | 10 | StringPrototypeSlice, | 11 | 11 | } = primordials; | 12 | | -const { basename, relative } = require('path'); | 13 | 12 | const { getOptionValue } = require('internal/options'); | 14 | 13 | const { | 15 | 14 | extensionFormatMap, | @@ -25,7 +24,7 @@ const experimentalSpecifierResolution = | 25 | 24 | const defaultTypeFlag = getOptionValue('--experimental-default-type'); | 26 | 25 | // The next line is where we flip the default to ES modules someday. | 27 | 26 | const defaultType = defaultTypeFlag === 'module' ? 'module' : 'commonjs'; | 28 | | -const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); | | 27 | +const { getPackageType } = require('internal/modules/esm/resolve'); | 29 | 28 | const { fileURLToPath } = require('internal/url'); | 30 | 29 | const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; | 31 | 30 |
| @@ -115,17 +114,16 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { | 115 | 114 | if (defaultType === 'commonjs') { // Legacy behavior | 116 | 115 | if (packageType === 'none' || packageType === 'commonjs') { | 117 | 116 | return 'commonjs'; | 118 | | - } | 119 | | - // If package type is `module`, fall through to the error case below | 120 | | - } else { // Else defaultType === 'module' | 121 | | - if (underNodeModules(url)) { // Exception for package scopes under `node_modules` | 122 | | - return 'commonjs'; | 123 | | - } | 124 | | - if (packageType === 'none' || packageType === 'module') { | 125 | | - return getFormatOfExtensionlessFile(url); | 126 | | - } // Else packageType === 'commonjs' | 127 | | - return 'commonjs'; | | 117 | + } // Else packageType === 'module' | | 118 | + return getFormatOfExtensionlessFile(url); | | 119 | + } // Else defaultType === 'module' | | 120 | + if (underNodeModules(url)) { // Exception for package scopes under `node_modules` | | 121 | + return packageType === 'module' ? getFormatOfExtensionlessFile(url) : 'commonjs'; | 128 | 122 | } | | 123 | + if (packageType === 'none' || packageType === 'module') { | | 124 | + return getFormatOfExtensionlessFile(url); | | 125 | + } // Else packageType === 'commonjs' | | 126 | + return 'commonjs'; | 129 | 127 | } | 130 | 128 |
| 131 | 129 | const format = extensionFormatMap[ext]; | @@ -135,17 +133,7 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { | 135 | 133 | // Explicit undefined return indicates load hook should rerun format check | 136 | 134 | if (ignoreErrors) { return undefined; } | 137 | 135 | const filepath = fileURLToPath(url); | 138 | | - let suggestion = ''; | 139 | | - if (getPackageType(url) === 'module' && ext === '') { | 140 | | - const config = getPackageScopeConfig(url); | 141 | | - const fileBasename = basename(filepath); | 142 | | - const relativePath = StringPrototypeSlice(relative(config.pjsonPath, filepath), 1); | 143 | | - suggestion = 'Loading extensionless files is not supported inside of "type":"module" package.json contexts ' + | 144 | | - `without --experimental-default-type=module. The package.json file ${config.pjsonPath} caused this "type":"module" ` + | 145 | | - `context. Try changing ${filepath} to have a file extension. Note the "bin" field of package.json can point ` + | 146 | | - `to a file with an extension, for example {"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`; | 147 | | - } | 148 | | - throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath, suggestion); | | 136 | + throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath); | 149 | 137 | } | 150 | 138 |
| 151 | 139 | return getLegacyExtensionFormat(ext) ?? null; | |
Collapse file Copy file name to clipboard +106Lines changed: 106 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 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 number | Diff line number | Diff line change |
|---|
@@ -24,6 +24,14 @@ describe('the type flag should change the interpretation of certain files within | 24 | 24 | strictEqual(defaultExport, 'module'); | 25 | 25 | }); | 26 | 26 |
| | 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 | + | 27 | 35 | it('should run as Wasm an extensionless Wasm file within a "type": "module" scope', async () => { | 28 | 36 | const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | 29 | 37 | '--experimental-default-type=module', | @@ -42,6 +50,13 @@ describe('the type flag should change the interpretation of certain files within | 42 | 50 | const { add } = await import(fixtures.fileURL('es-modules/package-type-module/noext-wasm')); | 43 | 51 | strictEqual(add(1, 2), 3); | 44 | 52 | }); | | 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 | + }); | 45 | 60 | }); | 46 | 61 |
| 47 | 62 | 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 | 112 | 127 | async () => { | 113 | 128 | const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | 114 | 129 | '--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'), | 116 | 131 | ]); | 117 | 132 |
| 118 | 133 | strictEqual(stderr, ''); | @@ -124,15 +139,16 @@ describe(`the type flag should NOT change the interpretation of certain files wi | 124 | 139 | it(`should import as CommonJS a .js file within a package scope that has no defined "type" and is under | 125 | 140 | node_modules`, async () => { | 126 | 141 | 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')); | 128 | 144 | strictEqual(defaultExport, 42); | 129 | 145 | }); | 130 | 146 |
| 131 | 147 | it(`should run as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and is | 132 | 148 | under node_modules`, async () => { | 133 | 149 | const { code, signal, stdout, stderr } = await spawnPromisified(process.execPath, [ | 134 | 150 | '--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'), | 136 | 152 | ]); | 137 | 153 |
| 138 | 154 | strictEqual(stderr, ''); | @@ -144,7 +160,8 @@ describe(`the type flag should NOT change the interpretation of certain files wi | 144 | 160 | it(`should import as CommonJS an extensionless JavaScript file within a package scope that has no defined "type" and | 145 | 161 | is under node_modules`, async () => { | 146 | 162 | 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')); | 148 | 165 | strictEqual(defaultExport, 42); | 149 | 166 | }); | 150 | 167 | }); |
Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -7,14 +7,11 @@ const { execPath } = require('node:process'); | 7 | 7 | const { describe, it } = require('node:test'); | 8 | 8 |
| 9 | 9 |
| 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 }, () => { | 14 | 13 | for ( | 15 | 14 | const fixturePath of [ | 16 | | - '/es-modules/package-type-module/noext-esm', | 17 | | - '/es-modules/package-type-module/imports-noext.mjs', | 18 | 15 | '/es-modules/package-type-module/extension.unknown', | 19 | 16 | '/es-modules/package-type-module/imports-unknownext.mjs', | 20 | 17 | ] | @@ -27,10 +24,6 @@ describe('ESM: extensionless and unknown specifiers', { concurrency: true }, () | 27 | 24 | assert.strictEqual(signal, null); | 28 | 25 | assert.strictEqual(stdout, ''); | 29 | 26 | assert.match(stderr, /ERR_UNKNOWN_FILE_EXTENSION/); | 30 | | - if (fixturePath.includes('noext')) { | 31 | | - // Check for explanation to users | 32 | | - assert.match(stderr, /extensionless/); | 33 | | - } | 34 | 27 | }); | 35 | 28 | } | 36 | 29 | }); |
Collapse file 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 Collapse file Copy file name to clipboard Collapse file 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 Collapse file 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
|