| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 72644d6 commit 56bd9a8
42 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -366,6 +366,36 @@ added: v17.6.0 | |||
| 366 | 366 | ||
| 367 | 367 | Expose the [Web Crypto API][] on the global scope. | |
| 368 | 368 | ||
| 369 | + ### `--experimental-default-type=type` | ||
| 370 | + | ||
| 371 | + <!-- YAML | ||
| 372 | + added: | ||
| 373 | + - REPLACEME | ||
| 374 | + --> | ||
| 375 | + | ||
| 376 | + > Stability: 1.0 - Early development | ||
| 377 | + | ||
| 378 | + Define which module system, `module` or `commonjs`, to use for the following: | ||
| 379 | + | ||
| 380 | + * String input provided via `--eval` or STDIN, if `--input-type` is unspecified. | ||
| 381 | + | ||
| 382 | + * Files ending in `.js` or with no extension, if there is no `package.json` file | ||
| 383 | + present in the same folder or any parent folder. | ||
| 384 | + | ||
| 385 | + * Files ending in `.js` or with no extension, if the nearest parent | ||
| 386 | + `package.json` field lacks a `"type"` field; unless the `package.json` folder | ||
| 387 | + or any parent folder is inside a `node_modules` folder. | ||
| 388 | + | ||
| 389 | + In other words, `--experimental-default-type=module` flips all the places where | ||
| 390 | + Node.js currently defaults to CommonJS to instead default to ECMAScript modules, | ||
| 391 | + with the exception of folders and subfolders below `node_modules`, for backward | ||
| 392 | + compatibility. | ||
| 393 | + | ||
| 394 | + Under `--experimental-default-type=module` and `--experimental-wasm-modules`, | ||
| 395 | + files with no extension will be treated as WebAssembly if they begin with the | ||
| 396 | + WebAssembly magic number (`\0asm`); otherwise they will be treated as ES module | ||
| 397 | + JavaScript. | ||
| 398 | + | ||
| 369 | 399 | ### `--experimental-import-meta-resolve` | |
| 370 | 400 | ||
| 371 | 401 | <!-- YAML | |
@@ -1923,6 +1953,7 @@ Node.js options that are allowed are: | |||
| 1923 | 1953 | * `--enable-network-family-autoselection` | |
| 1924 | 1954 | * `--enable-source-maps` | |
| 1925 | 1955 | * `--experimental-abortcontroller` | |
| 1956 | + * `--experimental-default-type` | ||
| 1926 | 1957 | * `--experimental-global-customevent` | |
| 1927 | 1958 | * `--experimental-global-webcrypto` | |
| 1928 | 1959 | * `--experimental-import-meta-resolve` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -105,10 +105,11 @@ provides interoperability between them and its original module format, | |||
| 105 | 105 | ||
| 106 | 106 | Node.js has two module systems: [CommonJS][] modules and ECMAScript modules. | |
| 107 | 107 | ||
| 108 | - Authors can tell Node.js to use the ECMAScript modules loader | ||
| 109 | - via the `.mjs` file extension, the `package.json` [`"type"`][] field, or the | ||
| 110 | - [`--input-type`][] flag. Outside of those cases, Node.js will use the CommonJS | ||
| 111 | - module loader. See [Determining module system][] for more details. | ||
| 108 | + Authors can tell Node.js to use the ECMAScript modules loader via the `.mjs` | ||
| 109 | + file extension, the `package.json` [`"type"`][] field, the [`--input-type`][] | ||
| 110 | + flag, or the [`--experimental-default-type`][] flag. Outside of those cases, | ||
| 111 | + Node.js will use the CommonJS module loader. See [Determining module system][] | ||
| 112 | + for more details. | ||
| 112 | 113 | ||
| 113 | 114 | <!-- Anchors to make sure old links find a target --> | |
| 114 | 115 | ||
@@ -1080,6 +1081,7 @@ success! | |||
| 1080 | 1081 | [URL]: https://url.spec.whatwg.org/ | |
| 1081 | 1082 | [`"exports"`]: packages.md#exports | |
| 1082 | 1083 | [`"type"`]: packages.md#type | |
| 1084 | + [`--experimental-default-type`]: cli.md#--experimental-default-typetype | ||
| 1083 | 1085 | [`--input-type`]: cli.md#--input-typetype | |
| 1084 | 1086 | [`data:` URLs]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs | |
| 1085 | 1087 | [`export`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -55,6 +55,8 @@ along with a reference for the [`package.json`][] fields defined by Node.js. | |||
| 55 | 55 | ||
| 56 | 56 | ## Determining module system | |
| 57 | 57 | ||
| 58 | + ### Introduction | ||
| 59 | + | ||
| 58 | 60 | Node.js will treat the following as [ES modules][] when passed to `node` as the | |
| 59 | 61 | initial input, or when referenced by `import` statements or `import()` | |
| 60 | 62 | expressions: | |
@@ -67,14 +69,9 @@ expressions: | |||
| 67 | 69 | * Strings passed in as an argument to `--eval`, or piped to `node` via `STDIN`, | |
| 68 | 70 | with the flag `--input-type=module`. | |
| 69 | 71 | ||
| 70 | - Node.js will treat as [CommonJS][] all other forms of input, such as `.js` files | ||
| 71 | - where the nearest parent `package.json` file contains no top-level `"type"` | ||
| 72 | - field, or string input without the flag `--input-type`. This behavior is to | ||
| 73 | - preserve backward compatibility. However, now that Node.js supports both | ||
| 74 | - CommonJS and ES modules, it is best to be explicit whenever possible. Node.js | ||
| 75 | - will treat the following as CommonJS when passed to `node` as the initial input, | ||
| 76 | - or when referenced by `import` statements, `import()` expressions, or | ||
| 77 | - `require()` expressions: | ||
| 72 | + Node.js will treat the following as [CommonJS][] when passed to `node` as the | ||
| 73 | + initial input, or when referenced by `import` statements or `import()` | ||
| 74 | + expressions: | ||
| 78 | 75 | ||
| 79 | 76 | * Files with a `.cjs` extension. | |
| 80 | 77 | ||
@@ -84,11 +81,30 @@ or when referenced by `import` statements, `import()` expressions, or | |||
| 84 | 81 | * Strings passed in as an argument to `--eval` or `--print`, or piped to `node` | |
| 85 | 82 | via `STDIN`, with the flag `--input-type=commonjs`. | |
| 86 | 83 | ||
| 87 | - Package authors should include the [`"type"`][] field, even in packages where | ||
| 88 | - all sources are CommonJS. Being explicit about the `type` of the package will | ||
| 89 | - future-proof the package in case the default type of Node.js ever changes, and | ||
| 90 | - it will also make things easier for build tools and loaders to determine how the | ||
| 91 | - files in the package should be interpreted. | ||
| 84 | + Aside from these explicit cases, there are other cases where Node.js defaults to | ||
| 85 | + one module system or the other based on the value of the | ||
| 86 | + [`--experimental-default-type`][] flag: | ||
| 87 | + | ||
| 88 | + * Files ending in `.js` or with no extension, if there is no `package.json` file | ||
| 89 | + present in the same folder or any parent folder. | ||
| 90 | + | ||
| 91 | + * Files ending in `.js` or with no extension, if the nearest parent | ||
| 92 | + `package.json` field lacks a `"type"` field; unless the folder is inside a | ||
| 93 | + `node_modules` folder. (Package scopes under `node_modules` are always treated | ||
| 94 | + as CommonJS when the `package.json` file lacks a `"type"` field, regardless | ||
| 95 | + of `--experimental-default-type`, for backward compatibility.) | ||
| 96 | + | ||
| 97 | + * Strings passed in as an argument to `--eval` or piped to `node` via `STDIN`, | ||
| 98 | + when `--input-type` is unspecified. | ||
| 99 | + | ||
| 100 | + This flag currently defaults to `"commonjs"`, but it may change in the future to | ||
| 101 | + default to `"module"`. For this reason it is best to be explicit wherever | ||
| 102 | + possible; in particular, package authors should always include the [`"type"`][] | ||
| 103 | + field in their `package.json` files, even in packages where all sources are | ||
| 104 | + CommonJS. Being explicit about the `type` of the package will future-proof the | ||
| 105 | + package in case the default type of Node.js ever changes, and it will also make | ||
| 106 | + things easier for build tools and loaders to determine how the files in the | ||
| 107 | + package should be interpreted. | ||
| 92 | 108 | ||
| 93 | 109 | ### Modules loaders | |
| 94 | 110 | ||
@@ -1337,6 +1353,7 @@ This field defines [subpath imports][] for the current package. | |||
| 1337 | 1353 | [`"packageManager"`]: #packagemanager | |
| 1338 | 1354 | [`"type"`]: #type | |
| 1339 | 1355 | [`--conditions` / `-C` flag]: #resolving-user-conditions | |
| 1356 | + [`--experimental-default-type`]: cli.md#--experimental-default-typetype | ||
| 1340 | 1357 | [`--no-addons` flag]: cli.md#--no-addons | |
| 1341 | 1358 | [`ERR_PACKAGE_PATH_NOT_EXPORTED`]: errors.md#err_package_path_not_exported | |
| 1342 | 1359 | [`esm`]: https://github.com/standard-things/esm#readme | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -140,6 +140,11 @@ Requires Node.js to be built with | |||
| 140 | 140 | .It Fl -enable-source-maps | |
| 141 | 141 | Enable Source Map V3 support for stack traces. | |
| 142 | 142 | . | |
| 143 | + .It Fl -experimental-default-type Ns = Ns Ar type | ||
| 144 | + Interpret as either ES modules or CommonJS modules input via --eval or STDIN, when --input-type is unspecified; | ||
| 145 | + .js or extensionless files with no sibling or parent package.json; | ||
| 146 | + .js or extensionless files whose nearest parent package.json lacks a "type" field, unless under node_modules. | ||
| 147 | + . | ||
| 143 | 148 | .It Fl -experimental-global-customevent | |
| 144 | 149 | Expose the CustomEvent on the global scope. | |
| 145 | 150 | . | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,7 +63,8 @@ function loadESMIfNeeded(cb) { | |||
| 63 | 63 | async function checkSyntax(source, filename) { | |
| 64 | 64 | let isModule = true; | |
| 65 | 65 | if (filename === '[stdin]' || filename === '[eval]') { | |
| 66 | - isModule = getOptionValue('--input-type') === 'module'; | ||
| 66 | + isModule = getOptionValue('--input-type') === 'module' || | ||
| 67 | + (getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs'); | ||
| 67 | 68 | } else { | |
| 68 | 69 | const { defaultResolve } = require('internal/modules/esm/resolve'); | |
| 69 | 70 | const { defaultGetFormat } = require('internal/modules/esm/get_format'); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,12 +25,14 @@ readStdin((code) => { | |||
| 25 | 25 | ||
| 26 | 26 | const print = getOptionValue('--print'); | |
| 27 | 27 | const loadESM = getOptionValue('--import').length > 0; | |
| 28 | - if (getOptionValue('--input-type') === 'module') | ||
| 28 | + if (getOptionValue('--input-type') === 'module' || | ||
| 29 | + (getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { | ||
| 29 | 30 | evalModule(code, print); | |
| 30 | - else | ||
| 31 | + } else { | ||
| 31 | 32 | evalScript('[stdin]', | |
| 32 | 33 | code, | |
| 33 | 34 | getOptionValue('--inspect-brk'), | |
| 34 | 35 | print, | |
| 35 | 36 | loadESM); | |
| 37 | + } | ||
| 36 | 38 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,11 +23,13 @@ markBootstrapComplete(); | |||
| 23 | 23 | const source = getOptionValue('--eval'); | |
| 24 | 24 | const print = getOptionValue('--print'); | |
| 25 | 25 | const loadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; | |
| 26 | - if (getOptionValue('--input-type') === 'module') | ||
| 26 | + if (getOptionValue('--input-type') === 'module' || | ||
| 27 | + (getOptionValue('--experimental-default-type') === 'module' && getOptionValue('--input-type') !== 'commonjs')) { | ||
| 27 | 28 | evalModule(source, print); | |
| 28 | - else | ||
| 29 | + } else { | ||
| 29 | 30 | evalScript('[eval]', | |
| 30 | 31 | source, | |
| 31 | 32 | getOptionValue('--inspect-brk'), | |
| 32 | 33 | print, | |
| 33 | 34 | loadESM); | |
| 35 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,9 +2,11 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | 4 | RegExpPrototypeExec, | |
| 5 | + Uint8Array, | ||
| 5 | 6 | } = primordials; | |
| 6 | 7 | const { getOptionValue } = require('internal/options'); | |
| 7 | 8 | ||
| 9 | + const { closeSync, openSync, readSync } = require('fs'); | ||
| 8 | 10 | ||
| 9 | 11 | const experimentalWasmModules = getOptionValue('--experimental-wasm-modules'); | |
| 10 | 12 | ||
@@ -49,8 +51,34 @@ function getLegacyExtensionFormat(ext) { | |||
| 49 | 51 | return legacyExtensionFormatMap[ext]; | |
| 50 | 52 | } | |
| 51 | 53 | ||
| 54 | + /** | ||
| 55 | + * For extensionless files in a `module` package scope, or a default `module` scope enabled by the | ||
| 56 | + * `--experimental-default-type` flag, we check the file contents to disambiguate between ES module JavaScript and Wasm. | ||
| 57 | + * We do this by taking advantage of the fact that all Wasm files start with the header `0x00 0x61 0x73 0x6d` (`_asm`). | ||
| 58 | + * @param {URL} url | ||
| 59 | + */ | ||
| 60 | + function getFormatOfExtensionlessFile(url) { | ||
| 61 | + if (!experimentalWasmModules) { return 'module'; } | ||
| 62 | + | ||
| 63 | + const magic = new Uint8Array(4); | ||
| 64 | + let fd; | ||
| 65 | + try { | ||
| 66 | + // TODO(@anonrig): Optimize the following by having a single C++ call | ||
| 67 | + fd = openSync(url); | ||
| 68 | + readSync(fd, magic, 0, 4); // Only read the first four bytes | ||
| 69 | + if (magic[0] === 0x00 && magic[1] === 0x61 && magic[2] === 0x73 && magic[3] === 0x6d) { | ||
| 70 | + return 'wasm'; | ||
| 71 | + } | ||
| 72 | + } finally { | ||
| 73 | + if (fd !== undefined) { closeSync(fd); } | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + return 'module'; | ||
| 77 | + } | ||
| 78 | + | ||
| 52 | 79 | module.exports = { | |
| 53 | 80 | extensionFormatMap, | |
| 81 | + getFormatOfExtensionlessFile, | ||
| 54 | 82 | getLegacyExtensionFormat, | |
| 55 | 83 | legacyExtensionFormatMap, | |
| 56 | 84 | mimeToFormat, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,16 +1,19 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | + | ||
| 2 | 3 | const { | |
| 3 | 4 | RegExpPrototypeExec, | |
| 4 | 5 | ObjectPrototypeHasOwnProperty, | |
| 5 | 6 | PromisePrototypeThen, | |
| 6 | 7 | PromiseResolve, | |
| 8 | + StringPrototypeIncludes, | ||
| 7 | 9 | StringPrototypeCharCodeAt, | |
| 8 | 10 | StringPrototypeSlice, | |
| 9 | 11 | } = primordials; | |
| 10 | 12 | const { basename, relative } = require('path'); | |
| 11 | 13 | const { getOptionValue } = require('internal/options'); | |
| 12 | 14 | const { | |
| 13 | 15 | extensionFormatMap, | |
| 16 | + getFormatOfExtensionlessFile, | ||
| 14 | 17 | getLegacyExtensionFormat, | |
| 15 | 18 | mimeToFormat, | |
| 16 | 19 | } = require('internal/modules/esm/formats'); | |
@@ -19,6 +22,9 @@ const experimentalNetworkImports = | |||
| 19 | 22 | getOptionValue('--experimental-network-imports'); | |
| 20 | 23 | const experimentalSpecifierResolution = | |
| 21 | 24 | getOptionValue('--experimental-specifier-resolution'); | |
| 25 | + const defaultTypeFlag = getOptionValue('--experimental-default-type'); | ||
| 26 | + // The next line is where we flip the default to ES modules someday. | ||
| 27 | + const defaultType = defaultTypeFlag === 'module' ? 'module' : 'commonjs'; | ||
| 22 | 28 | const { getPackageType, getPackageScopeConfig } = require('internal/modules/esm/resolve'); | |
| 23 | 29 | const { fileURLToPath } = require('internal/url'); | |
| 24 | 30 | const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes; | |
@@ -69,6 +75,18 @@ function extname(url) { | |||
| 69 | 75 | return ''; | |
| 70 | 76 | } | |
| 71 | 77 | ||
| 78 | + /** | ||
| 79 | + * Determine whether the given file URL is under a `node_modules` folder. | ||
| 80 | + * This function assumes that the input has already been verified to be a `file:` URL, | ||
| 81 | + * and is a file rather than a folder. | ||
| 82 | + * @param {URL} url | ||
| 83 | + */ | ||
| 84 | + function underNodeModules(url) { | ||
| 85 | + if (url.protocol !== 'file:') { return false; } // We determine module types for other protocols based on MIME header | ||
| 86 | + | ||
| 87 | + return StringPrototypeIncludes(url.pathname, '/node_modules/'); | ||
| 88 | + } | ||
| 89 | + | ||
| 72 | 90 | /** | |
| 73 | 91 | * @param {URL} url | |
| 74 | 92 | * @param {{parentURL: string}} context | |
@@ -77,8 +95,37 @@ function extname(url) { | |||
| 77 | 95 | */ | |
| 78 | 96 | function getFileProtocolModuleFormat(url, context, ignoreErrors) { | |
| 79 | 97 | const ext = extname(url); | |
| 98 | + | ||
| 80 | 99 | if (ext === '.js') { | |
| 81 | - return getPackageType(url) === 'module' ? 'module' : 'commonjs'; | ||
| 100 | + const packageType = getPackageType(url); | ||
| 101 | + if (packageType !== 'none') { | ||
| 102 | + return packageType; | ||
| 103 | + } | ||
| 104 | + // The controlling `package.json` file has no `type` field. | ||
| 105 | + if (defaultType === 'module') { | ||
| 106 | + // An exception to the type flag making ESM the default everywhere is that package scopes under `node_modules` | ||
| 107 | + // should retain the assumption that a lack of a `type` field means CommonJS. | ||
| 108 | + return underNodeModules(url) ? 'commonjs' : 'module'; | ||
| 109 | + } | ||
| 110 | + return 'commonjs'; | ||
| 111 | + } | ||
| 112 | + | ||
| 113 | + if (ext === '') { | ||
| 114 | + const packageType = getPackageType(url); | ||
| 115 | + if (defaultType === 'commonjs') { // Legacy behavior | ||
| 116 | + if (packageType === 'none' || packageType === 'commonjs') { | ||
| 117 | + 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'; | ||
| 128 | + } | ||
| 82 | 129 | } | |
| 83 | 130 | ||
| 84 | 131 | const format = extensionFormatMap[ext]; | |
@@ -93,12 +140,10 @@ function getFileProtocolModuleFormat(url, context, ignoreErrors) { | |||
| 93 | 140 | const config = getPackageScopeConfig(url); | |
| 94 | 141 | const fileBasename = basename(filepath); | |
| 95 | 142 | const relativePath = StringPrototypeSlice(relative(config.pjsonPath, filepath), 1); | |
| 96 | - suggestion = 'Loading extensionless files is not supported inside of ' + | ||
| 97 | - '"type":"module" package.json contexts. The package.json file ' + | ||
| 98 | - `${config.pjsonPath} caused this "type":"module" context. Try ` + | ||
| 99 | - `changing ${filepath} to have a file extension. Note the "bin" ` + | ||
| 100 | - 'field of package.json can point to a file with an extension, for example ' + | ||
| 101 | - `{"type":"module","bin":{"${fileBasename}":"${relativePath}.js"}}`; | ||
| 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"}}`; | ||
| 102 | 147 | } | |
| 103 | 148 | throw new ERR_UNKNOWN_FILE_EXTENSION(ext, filepath, suggestion); | |
| 104 | 149 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,7 +36,7 @@ const preserveSymlinks = getOptionValue('--preserve-symlinks'); | |||
| 36 | 36 | const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); | |
| 37 | 37 | const experimentalNetworkImports = | |
| 38 | 38 | getOptionValue('--experimental-network-imports'); | |
| 39 | - const typeFlag = getOptionValue('--input-type'); | ||
| 39 | + const inputTypeFlag = getOptionValue('--input-type'); | ||
| 40 | 40 | const { URL, pathToFileURL, fileURLToPath, isURL, toPathIfFileURL } = require('internal/url'); | |
| 41 | 41 | const { canParse: URLCanParse } = internalBinding('url'); | |
| 42 | 42 | const { | |
@@ -1183,7 +1183,7 @@ function defaultResolve(specifier, context = {}) { | |||
| 1183 | 1183 | // input, to avoid user confusion over how expansive the effect of the | |
| 1184 | 1184 | // flag should be (i.e. entry point only, package scope surrounding the | |
| 1185 | 1185 | // entry point, etc.). | |
| 1186 | - if (typeFlag) { throw new ERR_INPUT_TYPE_NOT_ALLOWED(); } | ||
| 1186 | + if (inputTypeFlag) { throw new ERR_INPUT_TYPE_NOT_ALLOWED(); } | ||
| 1187 | 1187 | } | |
| 1188 | 1188 | ||
| 1189 | 1189 | conditions = getConditionsSet(conditions); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments