Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/api/cli.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -1177,11 +1177,14 @@
added:
- v22.0.0
- v20.17.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64154

Check warning on line 1182 in doc/api/cli.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Print the top-level awaits without evaluating the modules.
-->

If the ES module being `require()`'d contains top-level `await`, this flag
allows Node.js to evaluate the module, try to locate the
top-level awaits, and print their location to help users find them.
If the ES module graph cannot be `require()`'d because it contains any top-level `await`,
this flag allows Node.js to locate and print their locations.

### `--experimental-quic`

Expand Down
24 changes: 21 additions & 3 deletions doc/api/errors.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -2765,12 +2765,30 @@

### `ERR_REQUIRE_ASYNC_MODULE`

<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64260

Check warning on line 2771 in doc/api/errors.md

View workflow job for this annotation

GitHub Actions/ lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Added the `requireStack` and `topLevelAwaitLocations` properties.
-->

When trying to `require()` an [ES Module][], the module turns out to be asynchronous.
That is, it contains top-level await.

To see where the top-level await is, use
`--experimental-print-required-tla` (this would execute the modules
before looking for the top-level awaits).
When uncaught, the flag `--experimental-print-required-tla` prints
the locations of the top-level awaits in the graph to stderr.

This error has the following additional non-enumerable properties:

* `requireStack` {string\[]} The chain of modules that led to the failing
`require()`, starting with the module that required the asynchronous module.
* `topLevelAwaitLocations` {Object\[]} The locations of the top-level awaits in
the graph. Only populated when `--experimental-print-required-tla` is enabled.
Each entry has the following properties:
* `url` {string} The URL of the module containing the top-level await.
* `line` {number} The 1-based line number of the top-level await.
* `column` {number} The 1-based column number of the top-level await.
* `sourceLine` {string} The source line containing the top-level await.

<a id="ERR_REQUIRE_CYCLE_MODULE"></a>

Expand Down
7 changes: 3 additions & 4 deletions doc/api/modules.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -317,10 +317,9 @@ graph it `import`s contains top-level `await`,
[`ERR_REQUIRE_ASYNC_MODULE`][] will be thrown. In this case, users should
load the asynchronous module using [`import()`][].

If `--experimental-print-required-tla` is enabled, instead of throwing
`ERR_REQUIRE_ASYNC_MODULE` before evaluation, Node.js will evaluate the
module, try to locate the top-level awaits, and print their location to
help users fix them.
If `--experimental-print-required-tla` is enabled and the error is uncaught,
Node.js will try to locate the top-level `await`s in the `require()`'d module graph
and print the locations in the stderr.

If support for loading ES modules using `require()` results in unexpected
breakage, it can be disabled using `--no-require-module`.
Expand Down
42 changes: 35 additions & 7 deletions lib/internal/errors.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,6 +48,7 @@ const {
StringPrototypeEndsWith,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
Expand DownExpand Up@@ -1692,15 +1693,42 @@ E('ERR_QUIC_STREAM_ABORTED', '%s', Error);
E('ERR_QUIC_STREAM_RESET',
'The QUIC stream was reset by the peer with error code %d', Error);
E('ERR_QUIC_VERSION_NEGOTIATION_ERROR', 'The QUIC session requires version negotiation', Error);
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parentFilename) {
let message = 'require() cannot be used on an ESM ' +
'graph with top-level await. Use import() instead. To see where the' +
' top-level await comes from, use --experimental-print-required-tla.';
if (parentFilename) {
message += `\n From ${parentFilename} `;
E('ERR_REQUIRE_ASYNC_MODULE', function(filename, parent, locations) {
let message = 'require() cannot be used on an ESM graph with top-level await. Use import() instead.';
const { getOptionValue } = require('internal/options');
if (!getOptionValue('--experimental-print-required-tla')) {
message += ' To see where the top-level await comes from, use --experimental-print-required-tla.';
}
if (filename) {
message += `\n Requiring ${filename} `;
message += `\nRequired module: ${filename}`;
}
if (parent) {
const { getRequireStack } = require('internal/modules/helpers');
const requireStack = getRequireStack(parent);
if (requireStack.length > 0) {
message += '\nRequire stack:\n- ' +
ArrayPrototypeJoin(requireStack, '\n- ');
}
ObjectDefineProperty(this, 'requireStack', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: requireStack,
});
}
if (locations && locations.length > 0) {
const { urlToFilename } = require('internal/modules/helpers');
const frames = ArrayPrototypeMap(locations, ({ url, line, column, sourceLine }) =>
`${urlToFilename(url)}:${line}\n\n${sourceLine}\n${StringPrototypeRepeat(' ', column - 1)}^\n`);
setArrowMessage(this, ArrayPrototypeJoin(frames, '\n'));
ObjectDefineProperty(this, 'topLevelAwaitLocations', {
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: locations,
});
}
return message;
}, Error);
Expand Down
12 changes: 1 addition & 11 deletions lib/internal/modules/cjs/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,6 +168,7 @@ const {
setHasStartedUserCJSExecution,
stripBOM,
toRealPath,
getRequireStack,
} = require('internal/modules/helpers');
const {
convertCJSFilenameToURL,
Expand DownExpand Up@@ -1567,17 +1568,6 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};

function getRequireStack(parent) {
const requireStack = [];
for (let cursor = parent;
cursor;
// TODO(joyeecheung): it makes more sense to use kLastModuleParent here.
cursor = cursor[kFirstModuleParent]) {
ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
}
return requireStack;
}

function getRequireStackMessage(request, requireStack) {
let message = `Cannot find module '${request}'`;
if (requireStack.length > 0) {
Expand Down
14 changes: 9 additions & 5 deletions lib/internal/modules/esm/loader.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,7 +25,6 @@ const { imported_cjs_symbol } = internalBinding('symbols');

const assert = require('internal/assert');
const {
ERR_REQUIRE_ASYNC_MODULE,
ERR_REQUIRE_CYCLE_MODULE,
ERR_REQUIRE_ESM,
ERR_REQUIRE_ESM_RACE_CONDITION,
Expand DownExpand Up@@ -289,8 +288,8 @@ class ModuleLoader {
const status = job.module.getStatus();
debug('Module status', job, status);
// hasAsyncGraph is available after module been instantiated.
if (status >= kInstantiated && job.module.hasAsyncGraph) {
throw new ERR_REQUIRE_ASYNC_MODULE(filename, parentFilename);
if (status >= kInstantiated) {
job.throwIfAsyncGraph(parent);
}
if (status === kEvaluated) {
return { wrap: job.module, namespace: job.module.getNamespace() };
Expand DownExpand Up@@ -318,6 +317,11 @@ class ModuleLoader {
}
if (status !== kEvaluating) {
assert(status === kUninstantiated, `Unexpected module status ${status}`);
// If we get here, either there's a race where the job is still being instantiated
// by an in-flight import(), or the cached module previously encountered an
// instantiation error during a prior load (e.g. due to a mismatched import).
// TODO(joyeecheung): the current check is too broad. We should attempt to
// get the potential instantiation error and throw it.
throw new ERR_REQUIRE_ESM_RACE_CONDITION(filename, parentFilename, false);
}
let message = `Cannot require() ES Module ${filename} in a cycle.`;
Expand DownExpand Up@@ -368,8 +372,8 @@ class ModuleLoader {

// Otherwise the module could be imported before but the evaluation may be already
// completed (e.g. the require call is lazy) so it's okay. We will return the
// job and check asynchronicity of the entire graph later, after the
// graph is instantiated.
// job and check asynchronicity of the entire graph later, before the
// graph is evaluated.
}

/**
Expand Down
Loading