| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,7 +24,7 @@ markBootstrapComplete(); | |||
| 24 | 24 | ||
| 25 | 25 | const source = getOptionValue('--eval'); | |
| 26 | 26 | const print = getOptionValue('--print'); | |
| 27 | - const loadESM = getOptionValue('--import').length > 0; | ||
| 27 | + const loadESM = getOptionValue('--import').length > 0 || getOptionValue('--experimental-loader').length > 0; | ||
| 28 | 28 | if (getOptionValue('--input-type') === 'module') | |
| 29 | 29 | evalModule(source, print); | |
| 30 | 30 | else { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,6 +10,7 @@ const { | |||
| 10 | 10 | Promise, | |
| 11 | 11 | SafeSet, | |
| 12 | 12 | StringPrototypeSlice, | |
| 13 | + StringPrototypeStartsWith, | ||
| 13 | 14 | StringPrototypeToUpperCase, | |
| 14 | 15 | globalThis, | |
| 15 | 16 | } = primordials; | |
@@ -30,6 +31,7 @@ const { | |||
| 30 | 31 | ERR_INVALID_RETURN_PROPERTY_VALUE, | |
| 31 | 32 | ERR_INVALID_RETURN_VALUE, | |
| 32 | 33 | ERR_LOADER_CHAIN_INCOMPLETE, | |
| 34 | + ERR_UNKNOWN_BUILTIN_MODULE, | ||
| 33 | 35 | ERR_WORKER_UNSERIALIZABLE_ERROR, | |
| 34 | 36 | } = require('internal/errors').codes; | |
| 35 | 37 | const { exitCodes: { kUnfinishedTopLevelAwait } } = internalBinding('errors'); | |
@@ -521,14 +523,14 @@ class HooksProxy { | |||
| 521 | 523 | this.#worker.on('exit', process.exit); | |
| 522 | 524 | } | |
| 523 | 525 | ||
| 524 | - #waitForWorker() { | ||
| 526 | + waitForWorker() { | ||
| 525 | 527 | if (!this.#isReady) { | |
| 526 | 528 | const { kIsOnline } = require('internal/worker'); | |
| 527 | 529 | if (!this.#worker[kIsOnline]) { | |
| 528 | 530 | debug('wait for signal from worker'); | |
| 529 | 531 | AtomicsWait(this.#lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 0); | |
| 530 | 532 | const response = this.#worker.receiveMessageSync(); | |
| 531 | - if (response.message.status === 'exit') { return; } | ||
| 533 | + if (response == null || response.message.status === 'exit') { return; } | ||
| 532 | 534 | const { preloadScripts } = this.#unwrapMessage(response); | |
| 533 | 535 | this.#executePreloadScripts(preloadScripts); | |
| 534 | 536 | } | |
@@ -538,7 +540,7 @@ class HooksProxy { | |||
| 538 | 540 | } | |
| 539 | 541 | ||
| 540 | 542 | async makeAsyncRequest(method, ...args) { | |
| 541 | - this.#waitForWorker(); | ||
| 543 | + this.waitForWorker(); | ||
| 542 | 544 | ||
| 543 | 545 | MessageChannel ??= require('internal/worker/io').MessageChannel; | |
| 544 | 546 | const asyncCommChannel = new MessageChannel(); | |
@@ -578,7 +580,7 @@ class HooksProxy { | |||
| 578 | 580 | } | |
| 579 | 581 | ||
| 580 | 582 | makeSyncRequest(method, ...args) { | |
| 581 | - this.#waitForWorker(); | ||
| 583 | + this.waitForWorker(); | ||
| 582 | 584 | ||
| 583 | 585 | // Pass work to the worker. | |
| 584 | 586 | debug('post sync message to worker', { method, args }); | |
@@ -620,35 +622,66 @@ class HooksProxy { | |||
| 620 | 622 | } | |
| 621 | 623 | } | |
| 622 | 624 | ||
| 625 | + #importMetaInitializer = require('internal/modules/esm/initialize_import_meta').initializeImportMeta; | ||
| 626 | + | ||
| 627 | + importMetaInitialize(meta, context, loader) { | ||
| 628 | + this.#importMetaInitializer(meta, context, loader); | ||
| 629 | + } | ||
| 630 | + | ||
| 623 | 631 | #executePreloadScripts(preloadScripts) { | |
| 624 | 632 | for (let i = 0; i < preloadScripts.length; i++) { | |
| 625 | 633 | const { code, port } = preloadScripts[i]; | |
| 626 | 634 | const { compileFunction } = require('vm'); | |
| 627 | 635 | const preloadInit = compileFunction( | |
| 628 | 636 | code, | |
| 629 | - ['getBuiltin', 'port'], | ||
| 637 | + ['getBuiltin', 'port', 'setImportMetaCallback'], | ||
| 630 | 638 | { | |
| 631 | 639 | filename: '<preload>', | |
| 632 | 640 | }, | |
| 633 | 641 | ); | |
| 642 | + let finished = false; | ||
| 643 | + let replacedImportMetaInitializer = false; | ||
| 644 | + let next = this.#importMetaInitializer; | ||
| 634 | 645 | const { BuiltinModule } = require('internal/bootstrap/realm'); | |
| 635 | 646 | // Calls the compiled preload source text gotten from the hook | |
| 636 | 647 | // Since the parameters are named we use positional parameters | |
| 637 | 648 | // see compileFunction above to cross reference the names | |
| 638 | - FunctionPrototypeCall( | ||
| 639 | - preloadInit, | ||
| 640 | - globalThis, | ||
| 641 | - // Param getBuiltin | ||
| 642 | - (builtinName) => { | ||
| 643 | - if (BuiltinModule.canBeRequiredByUsers(builtinName) && | ||
| 644 | - BuiltinModule.canBeRequiredWithoutScheme(builtinName)) { | ||
| 645 | - return require(builtinName); | ||
| 646 | - } | ||
| 647 | - throw new ERR_INVALID_ARG_VALUE('builtinName', builtinName); | ||
| 648 | - }, | ||
| 649 | - // Param port | ||
| 650 | - port, | ||
| 651 | - ); | ||
| 649 | + try { | ||
| 650 | + FunctionPrototypeCall( | ||
| 651 | + preloadInit, | ||
| 652 | + globalThis, | ||
| 653 | + // Param getBuiltin | ||
| 654 | + (builtinName) => { | ||
| 655 | + if (StringPrototypeStartsWith(builtinName, 'node:')) { | ||
| 656 | + builtinName = StringPrototypeSlice(builtinName, 5); | ||
| 657 | + } else if (!BuiltinModule.canBeRequiredWithoutScheme(builtinName)) { | ||
| 658 | + throw new ERR_UNKNOWN_BUILTIN_MODULE(builtinName); | ||
| 659 | + } | ||
| 660 | + if (BuiltinModule.canBeRequiredByUsers(builtinName)) { | ||
| 661 | + return require(builtinName); | ||
| 662 | + } | ||
| 663 | + throw new ERR_UNKNOWN_BUILTIN_MODULE(builtinName); | ||
| 664 | + }, | ||
| 665 | + // Param port | ||
| 666 | + port, | ||
| 667 | + // setImportMetaCallback | ||
| 668 | + (fn) => { | ||
| 669 | + if (finished || typeof fn !== 'function') { | ||
| 670 | + throw new ERR_INVALID_ARG_TYPE('fn', fn); | ||
| 671 | + } | ||
| 672 | + replacedImportMetaInitializer = true; | ||
| 673 | + const parent = next; | ||
| 674 | + next = (meta, context) => { | ||
| 675 | + return fn(meta, context, parent); | ||
| 676 | + }; | ||
| 677 | + }, | ||
| 678 | + ); | ||
| 679 | + } finally { | ||
| 680 | + finished = true; | ||
| 681 | + if (replacedImportMetaInitializer) { | ||
| 682 | + this.#importMetaInitializer = next; | ||
| 683 | + } | ||
| 684 | + } | ||
| 652 | 685 | } | |
| 653 | 686 | } | |
| 654 | 687 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -277,6 +277,11 @@ class DefaultModuleLoader { | |||
| 277 | 277 | meta = importMetaInitializer(meta, context, this); | |
| 278 | 278 | return meta; | |
| 279 | 279 | } | |
| 280 | + | ||
| 281 | + /** | ||
| 282 | + * No-op when no hooks have been supplied. | ||
| 283 | + */ | ||
| 284 | + forceLoadHooks() {} | ||
| 280 | 285 | } | |
| 281 | 286 | ObjectSetPrototypeOf(DefaultModuleLoader.prototype, null); | |
| 282 | 287 | ||
@@ -349,6 +354,14 @@ class CustomizedModuleLoader extends DefaultModuleLoader { | |||
| 349 | 354 | ||
| 350 | 355 | return result; | |
| 351 | 356 | } | |
| 357 | + | ||
| 358 | + importMetaInitialize(meta, context) { | ||
| 359 | + hooksProxy.importMetaInitialize(meta, context, this); | ||
| 360 | + } | ||
| 361 | + | ||
| 362 | + forceLoadHooks() { | ||
| 363 | + hooksProxy.waitForWorker(); | ||
| 364 | + } | ||
| 352 | 365 | } | |
| 353 | 366 | ||
| 354 | 367 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -36,6 +36,8 @@ module.exports = { | |||
| 36 | 36 | parentURL, | |
| 37 | 37 | kEmptyObject, | |
| 38 | 38 | )); | |
| 39 | + } else { | ||
| 40 | + esmLoader.forceLoadHooks(); | ||
| 39 | 41 | } | |
| 40 | 42 | await callback(esmLoader); | |
| 41 | 43 | } catch (err) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,7 @@ | |||
| 1 | 1 | import { spawnPromisified } from '../common/index.mjs'; | |
| 2 | 2 | import * as fixtures from '../common/fixtures.mjs'; | |
| 3 | 3 | import assert from 'node:assert'; | |
| 4 | + import os from 'node:os'; | ||
| 4 | 5 | import { execPath } from 'node:process'; | |
| 5 | 6 | import { describe, it } from 'node:test'; | |
| 6 | 7 | ||
@@ -422,18 +423,119 @@ describe('Loader hooks', { concurrency: true }, () => { | |||
| 422 | 423 | }); | |
| 423 | 424 | }); | |
| 424 | 425 | ||
| 425 | - it('should handle globalPreload returning undefined', async () => { | ||
| 426 | - const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 427 | - '--no-warnings', | ||
| 428 | - '--experimental-loader', | ||
| 429 | - 'data:text/javascript,export function globalPreload(){}', | ||
| 430 | - fixtures.path('empty.js'), | ||
| 431 | - ]); | ||
| 426 | + describe('globalPreload', () => { | ||
| 427 | + it('should handle globalPreload returning undefined', async () => { | ||
| 428 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 429 | + '--no-warnings', | ||
| 430 | + '--experimental-loader', | ||
| 431 | + 'data:text/javascript,export function globalPreload(){}', | ||
| 432 | + fixtures.path('empty.js'), | ||
| 433 | + ]); | ||
| 432 | 434 | ||
| 433 | - assert.strictEqual(stderr, ''); | ||
| 434 | - assert.strictEqual(stdout, ''); | ||
| 435 | - assert.strictEqual(code, 0); | ||
| 436 | - assert.strictEqual(signal, null); | ||
| 435 | + assert.strictEqual(stderr, ''); | ||
| 436 | + assert.strictEqual(stdout, ''); | ||
| 437 | + assert.strictEqual(code, 0); | ||
| 438 | + assert.strictEqual(signal, null); | ||
| 439 | + }); | ||
| 440 | + | ||
| 441 | + it('should handle loading node:test', async () => { | ||
| 442 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 443 | + '--no-warnings', | ||
| 444 | + '--experimental-loader', | ||
| 445 | + 'data:text/javascript,export function globalPreload(){return `getBuiltin("node:test")()`}', | ||
| 446 | + fixtures.path('empty.js'), | ||
| 447 | + ]); | ||
| 448 | + | ||
| 449 | + assert.strictEqual(stderr, ''); | ||
| 450 | + assert.match(stdout, /\n# pass 1\r?\n/); | ||
| 451 | + assert.strictEqual(code, 0); | ||
| 452 | + assert.strictEqual(signal, null); | ||
| 453 | + }); | ||
| 454 | + | ||
| 455 | + it('should handle loading node:os with node: prefix', async () => { | ||
| 456 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 457 | + '--no-warnings', | ||
| 458 | + '--experimental-loader', | ||
| 459 | + 'data:text/javascript,export function globalPreload(){return `console.log(getBuiltin("node:os").arch())`}', | ||
| 460 | + fixtures.path('empty.js'), | ||
| 461 | + ]); | ||
| 462 | + | ||
| 463 | + assert.strictEqual(stderr, ''); | ||
| 464 | + assert.strictEqual(stdout.trim(), os.arch()); | ||
| 465 | + assert.strictEqual(code, 0); | ||
| 466 | + assert.strictEqual(signal, null); | ||
| 467 | + }); | ||
| 468 | + | ||
| 469 | + // `os` is used here because it's simple and not mocked (the builtin module otherwise doesn't matter). | ||
| 470 | + it('should handle loading builtin module without node: prefix', async () => { | ||
| 471 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 472 | + '--no-warnings', | ||
| 473 | + '--experimental-loader', | ||
| 474 | + 'data:text/javascript,export function globalPreload(){return `console.log(getBuiltin("os").arch())`}', | ||
| 475 | + fixtures.path('empty.js'), | ||
| 476 | + ]); | ||
| 477 | + | ||
| 478 | + assert.strictEqual(stderr, ''); | ||
| 479 | + assert.strictEqual(stdout.trim(), os.arch()); | ||
| 480 | + assert.strictEqual(code, 0); | ||
| 481 | + assert.strictEqual(signal, null); | ||
| 482 | + }); | ||
| 483 | + | ||
| 484 | + it('should throw when loading node:test without node: prefix', async () => { | ||
| 485 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 486 | + '--no-warnings', | ||
| 487 | + '--experimental-loader', | ||
| 488 | + 'data:text/javascript,export function globalPreload(){return `getBuiltin("test")()`}', | ||
| 489 | + fixtures.path('empty.js'), | ||
| 490 | + ]); | ||
| 491 | + | ||
| 492 | + assert.match(stderr, /ERR_UNKNOWN_BUILTIN_MODULE/); | ||
| 493 | + assert.strictEqual(stdout, ''); | ||
| 494 | + assert.strictEqual(code, 1); | ||
| 495 | + assert.strictEqual(signal, null); | ||
| 496 | + }); | ||
| 497 | + | ||
| 498 | + it('should register globals set from globalPreload', async () => { | ||
| 499 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 500 | + '--no-warnings', | ||
| 501 | + '--experimental-loader', | ||
| 502 | + 'data:text/javascript,export function globalPreload(){return "this.myGlobal=4"}', | ||
| 503 | + '--print', 'myGlobal', | ||
| 504 | + ]); | ||
| 505 | + | ||
| 506 | + assert.strictEqual(stderr, ''); | ||
| 507 | + assert.strictEqual(stdout.trim(), '4'); | ||
| 508 | + assert.strictEqual(code, 0); | ||
| 509 | + assert.strictEqual(signal, null); | ||
| 510 | + }); | ||
| 511 | + | ||
| 512 | + it('should log console.log calls returned from globalPreload', async () => { | ||
| 513 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 514 | + '--no-warnings', | ||
| 515 | + '--experimental-loader', | ||
| 516 | + 'data:text/javascript,export function globalPreload(){return `console.log("Hello from globalPreload")`}', | ||
| 517 | + fixtures.path('empty.js'), | ||
| 518 | + ]); | ||
| 519 | + | ||
| 520 | + assert.strictEqual(stderr, ''); | ||
| 521 | + assert.strictEqual(stdout.trim(), 'Hello from globalPreload'); | ||
| 522 | + assert.strictEqual(code, 0); | ||
| 523 | + assert.strictEqual(signal, null); | ||
| 524 | + }); | ||
| 525 | + | ||
| 526 | + it('should crash if globalPreload returns code that throws', async () => { | ||
| 527 | + const { code, signal, stdout, stderr } = await spawnPromisified(execPath, [ | ||
| 528 | + '--no-warnings', | ||
| 529 | + '--experimental-loader', | ||
| 530 | + 'data:text/javascript,export function globalPreload(){return `throw new Error("error from globalPreload")`}', | ||
| 531 | + fixtures.path('empty.js'), | ||
| 532 | + ]); | ||
| 533 | + | ||
| 534 | + assert.match(stderr, /error from globalPreload/); | ||
| 535 | + assert.strictEqual(stdout, ''); | ||
| 536 | + assert.strictEqual(code, 1); | ||
| 537 | + assert.strictEqual(signal, null); | ||
| 538 | + }); | ||
| 437 | 539 | }); | |
| 438 | 540 | ||
| 439 | 541 | it('should be fine to call `process.removeAllListeners("beforeExit")` from the main thread', async () => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,45 @@ | |||
| 1 | + // Flags: --loader ./test/fixtures/es-module-loaders/mock-loader.mjs | ||
| 2 | + import '../common/index.mjs'; | ||
| 3 | + import assert from 'assert/strict'; | ||
| 4 | + | ||
| 5 | + // This is provided by test/fixtures/es-module-loaders/mock-loader.mjs | ||
| 6 | + import mock from 'node:mock'; | ||
| 7 | + | ||
| 8 | + mock('node:events', { | ||
| 9 | + EventEmitter: 'This is mocked!' | ||
| 10 | + }); | ||
| 11 | + | ||
| 12 | + // This resolves to node:events | ||
| 13 | + // It is intercepted by mock-loader and doesn't return the normal value | ||
| 14 | + assert.deepStrictEqual(await import('events'), Object.defineProperty({ | ||
| 15 | + __proto__: null, | ||
| 16 | + EventEmitter: 'This is mocked!' | ||
| 17 | + }, Symbol.toStringTag, { | ||
| 18 | + enumerable: false, | ||
| 19 | + value: 'Module' | ||
| 20 | + })); | ||
| 21 | + | ||
| 22 | + const mutator = mock('node:events', { | ||
| 23 | + EventEmitter: 'This is mocked v2!' | ||
| 24 | + }); | ||
| 25 | + | ||
| 26 | + // It is intercepted by mock-loader and doesn't return the normal value. | ||
| 27 | + // This is resolved separately from the import above since the specifiers | ||
| 28 | + // are different. | ||
| 29 | + const mockedV2 = await import('node:events'); | ||
| 30 | + assert.deepStrictEqual(mockedV2, Object.defineProperty({ | ||
| 31 | + __proto__: null, | ||
| 32 | + EventEmitter: 'This is mocked v2!' | ||
| 33 | + }, Symbol.toStringTag, { | ||
| 34 | + enumerable: false, | ||
| 35 | + value: 'Module' | ||
| 36 | + })); | ||
| 37 | + | ||
| 38 | + mutator.EventEmitter = 'This is mocked v3!'; | ||
| 39 | + assert.deepStrictEqual(mockedV2, Object.defineProperty({ | ||
| 40 | + __proto__: null, | ||
| 41 | + EventEmitter: 'This is mocked v3!' | ||
| 42 | + }, Symbol.toStringTag, { | ||
| 43 | + enumerable: false, | ||
| 44 | + value: 'Module' | ||
| 45 | + })); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments