fix(esm): >= Node.js 21 globalPreload -> initialize (#28526)
https://github.com/microsoft/playwright/issues/28524 https://github.com/microsoft/playwright/issues/28732 https://nodejs.org/en/blog/announcements/v21-release-announce#module-customization-hook-globalpreload-removed-use-register-and-initialize-instead
This commit is contained in:
parent
37634df0c4
commit
7670fd21e2
|
|
@ -21,7 +21,7 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import { Runner } from './runner/runner';
|
||||
import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
|
||||
import { execArgvWithoutExperimentalLoaderOptions, execArgvWithExperimentalLoaderOptions, fileIsModule, serializeError } from './util';
|
||||
import { fileIsModule, serializeError } from './util';
|
||||
import { showHTMLReport } from './reporters/html';
|
||||
import { createMergedReport } from './reporters/merge';
|
||||
import { ConfigLoader, resolveConfigFile } from './common/configLoader';
|
||||
|
|
@ -33,6 +33,8 @@ import type { FullConfigInternal } from './common/config';
|
|||
import program from 'playwright-core/lib/cli/program';
|
||||
import type { ReporterDescription } from '../types/test';
|
||||
import { prepareErrorStack } from './reporters/base';
|
||||
import { registerESMLoader } from './common/esmLoaderHost';
|
||||
import { execArgvWithExperimentalLoaderOptions, execArgvWithoutExperimentalLoaderOptions } from './transform/esmUtils';
|
||||
|
||||
function addTestCommand(program: Command) {
|
||||
const command = program.command('test [test-filter...]');
|
||||
|
|
@ -277,26 +279,33 @@ function restartWithExperimentalTsEsm(configFile: string | null): boolean {
|
|||
return false;
|
||||
if (process.env.PW_DISABLE_TS_ESM)
|
||||
return false;
|
||||
if (process.env.PW_TS_ESM_ON) {
|
||||
// Node.js < 20
|
||||
if ((globalThis as any).__esmLoaderPortPreV20) {
|
||||
// clear execArgv after restart, so that childProcess.fork in user code does not inherit our loader.
|
||||
process.execArgv = execArgvWithoutExperimentalLoaderOptions();
|
||||
return false;
|
||||
}
|
||||
if (!fileIsModule(configFile))
|
||||
return false;
|
||||
const innerProcess = (require('child_process') as typeof import('child_process')).fork(require.resolve('./cli'), process.argv.slice(2), {
|
||||
env: {
|
||||
...process.env,
|
||||
PW_TS_ESM_ON: '1',
|
||||
},
|
||||
execArgv: execArgvWithExperimentalLoaderOptions(),
|
||||
});
|
||||
// Node.js < 20
|
||||
if (!require('node:module').register) {
|
||||
const innerProcess = (require('child_process') as typeof import('child_process')).fork(require.resolve('./cli'), process.argv.slice(2), {
|
||||
env: {
|
||||
...process.env,
|
||||
PW_TS_ESM_LEGACY_LOADER_ON: '1',
|
||||
},
|
||||
execArgv: execArgvWithExperimentalLoaderOptions(),
|
||||
});
|
||||
|
||||
innerProcess.on('close', (code: number | null) => {
|
||||
if (code !== 0 && code !== null)
|
||||
gracefullyProcessExitDoNotHang(code);
|
||||
});
|
||||
return true;
|
||||
innerProcess.on('close', (code: number | null) => {
|
||||
if (code !== 0 && code !== null)
|
||||
gracefullyProcessExitDoNotHang(code);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
// Nodejs >= 21
|
||||
registerESMLoader();
|
||||
return false;
|
||||
}
|
||||
|
||||
const kTraceModes: TraceMode[] = ['on', 'off', 'on-first-retry', 'on-all-retries', 'retain-on-failure'];
|
||||
|
|
|
|||
|
|
@ -14,16 +14,36 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import url from 'url';
|
||||
import { addToCompilationCache, serializeCompilationCache } from '../transform/compilationCache';
|
||||
import { transformConfig } from '../transform/transform';
|
||||
import { PortTransport } from '../transform/portTransport';
|
||||
|
||||
const port = (globalThis as any).__esmLoaderPort;
|
||||
let loaderChannel: PortTransport | undefined;
|
||||
// Node.js < 20
|
||||
if ((globalThis as any).__esmLoaderPortPreV20)
|
||||
loaderChannel = createPortTransport((globalThis as any).__esmLoaderPortPreV20);
|
||||
|
||||
const loaderChannel = port ? new PortTransport(port, async (method, params) => {
|
||||
if (method === 'pushToCompilationCache')
|
||||
addToCompilationCache(params.cache);
|
||||
}) : undefined;
|
||||
// Node.js >= 20
|
||||
export let esmLoaderRegistered = false;
|
||||
export function registerESMLoader() {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
// register will wait until the loader is initialized.
|
||||
require('node:module').register(url.pathToFileURL(require.resolve('../transform/esmLoader')), {
|
||||
parentURL: url.pathToFileURL(__filename),
|
||||
data: { port: port2 },
|
||||
transferList: [port2],
|
||||
});
|
||||
loaderChannel = createPortTransport(port1);
|
||||
esmLoaderRegistered = true;
|
||||
}
|
||||
|
||||
function createPortTransport(port: MessagePort) {
|
||||
return new PortTransport(port, async (method, params) => {
|
||||
if (method === 'pushToCompilationCache')
|
||||
addToCompilationCache(params.cache);
|
||||
});
|
||||
}
|
||||
|
||||
export async function startCollectingFileDeps() {
|
||||
if (!loaderChannel)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ import type { WriteStream } from 'tty';
|
|||
import type { EnvProducedPayload, ProcessInitParams, TtyParams } from './ipc';
|
||||
import { startProfiling, stopProfiling } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoError } from '../../types/test';
|
||||
import { execArgvWithoutExperimentalLoaderOptions, serializeError } from '../util';
|
||||
import { serializeError } from '../util';
|
||||
import { registerESMLoader } from './esmLoaderHost';
|
||||
import { execArgvWithoutExperimentalLoaderOptions } from '../transform/esmUtils';
|
||||
|
||||
export type ProtocolRequest = {
|
||||
id: number;
|
||||
|
|
@ -54,6 +56,10 @@ process.on('SIGTERM', () => {});
|
|||
// Clear execArgv immediately, so that the user-code does not inherit our loader.
|
||||
process.execArgv = execArgvWithoutExperimentalLoaderOptions();
|
||||
|
||||
// Node.js >= 20
|
||||
if (process.env.PW_TS_ESM_LOADER_ON)
|
||||
registerESMLoader();
|
||||
|
||||
let processRunner: ProcessRunner | undefined;
|
||||
let processName: string | undefined;
|
||||
const startingEnv = { ...process.env };
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ import { EventEmitter } from 'events';
|
|||
import { debug } from 'playwright-core/lib/utilsBundle';
|
||||
import type { EnvProducedPayload, ProcessInitParams } from '../common/ipc';
|
||||
import type { ProtocolResponse } from '../common/process';
|
||||
import { execArgvWithExperimentalLoaderOptions } from '../util';
|
||||
import { execArgvWithExperimentalLoaderOptions } from '../transform/esmUtils';
|
||||
import { assert } from 'playwright-core/lib/utils';
|
||||
import { esmLoaderRegistered } from '../common/esmLoaderHost';
|
||||
|
||||
export type ProcessExitData = {
|
||||
unexpectedly: boolean;
|
||||
|
|
@ -51,14 +52,18 @@ export class ProcessHost extends EventEmitter {
|
|||
assert(!this.process, 'Internal error: starting the same process twice');
|
||||
this.process = child_process.fork(require.resolve('../common/process'), {
|
||||
detached: false,
|
||||
env: { ...process.env, ...this._extraEnv },
|
||||
env: {
|
||||
...process.env,
|
||||
...this._extraEnv,
|
||||
...(esmLoaderRegistered ? { PW_TS_ESM_LOADER_ON: '1' } : {}),
|
||||
},
|
||||
stdio: [
|
||||
'ignore',
|
||||
options.onStdOut ? 'pipe' : 'inherit',
|
||||
(options.onStdErr && !process.env.PW_RUNNER_DEBUG) ? 'pipe' : 'inherit',
|
||||
'ipc',
|
||||
],
|
||||
...(process.env.PW_TS_ESM_ON ? { execArgv: execArgvWithExperimentalLoaderOptions() } : {}),
|
||||
...(process.env.PW_TS_ESM_LEGACY_LOADER_ON ? { execArgv: execArgvWithExperimentalLoaderOptions() } : {}),
|
||||
});
|
||||
this.process.on('exit', async (code, signal) => {
|
||||
this._processDidExit = true;
|
||||
|
|
|
|||
|
|
@ -66,8 +66,21 @@ async function load(moduleUrl: string, context: { format?: string }, defaultLoad
|
|||
|
||||
let transport: PortTransport | undefined;
|
||||
|
||||
// Node.js < 20
|
||||
function globalPreload(context: { port: MessagePort }) {
|
||||
transport = new PortTransport(context.port, async (method, params) => {
|
||||
transport = createTransport(context.port);
|
||||
return `
|
||||
globalThis.__esmLoaderPortPreV20 = port;
|
||||
`;
|
||||
}
|
||||
|
||||
// Node.js >= 20
|
||||
function initialize(data: { port: MessagePort }) {
|
||||
transport = createTransport(data?.port);
|
||||
}
|
||||
|
||||
function createTransport(port: MessagePort) {
|
||||
return new PortTransport(port, async (method, params) => {
|
||||
if (method === 'setTransformConfig') {
|
||||
setTransformConfig(params.config);
|
||||
return;
|
||||
|
|
@ -91,10 +104,7 @@ function globalPreload(context: { port: MessagePort }) {
|
|||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return `
|
||||
globalThis.__esmLoaderPort = port;
|
||||
`;
|
||||
}
|
||||
|
||||
module.exports = { resolve, load, globalPreload };
|
||||
|
||||
module.exports = { resolve, load, globalPreload, initialize };
|
||||
|
|
|
|||
33
packages/playwright/src/transform/esmUtils.ts
Normal file
33
packages/playwright/src/transform/esmUtils.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import url from 'url';
|
||||
|
||||
const kExperimentalLoaderOptions = [
|
||||
'--no-warnings',
|
||||
`--experimental-loader=${url.pathToFileURL(require.resolve('playwright/lib/transform/esmLoader')).toString()}`,
|
||||
];
|
||||
|
||||
export function execArgvWithExperimentalLoaderOptions() {
|
||||
return [
|
||||
...process.execArgv,
|
||||
...kExperimentalLoaderOptions,
|
||||
];
|
||||
}
|
||||
|
||||
export function execArgvWithoutExperimentalLoaderOptions() {
|
||||
return process.execArgv.filter(arg => !kExperimentalLoaderOptions.includes(arg));
|
||||
}
|
||||
|
|
@ -286,22 +286,6 @@ function folderIsModule(folder: string): boolean {
|
|||
return require(packageJsonPath).type === 'module';
|
||||
}
|
||||
|
||||
const kExperimentalLoaderOptions = [
|
||||
'--no-warnings',
|
||||
`--experimental-loader=${url.pathToFileURL(require.resolve('playwright/lib/transform/esmLoader')).toString()}`,
|
||||
];
|
||||
|
||||
export function execArgvWithExperimentalLoaderOptions() {
|
||||
return [
|
||||
...process.execArgv,
|
||||
...kExperimentalLoaderOptions,
|
||||
];
|
||||
}
|
||||
|
||||
export function execArgvWithoutExperimentalLoaderOptions() {
|
||||
return process.execArgv.filter(arg => !kExperimentalLoaderOptions.includes(arg));
|
||||
}
|
||||
|
||||
// This follows the --moduleResolution=bundler strategy from tsc.
|
||||
// https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#moduleresolution-bundler
|
||||
const kExtLookups = new Map([
|
||||
|
|
|
|||
Loading…
Reference in a new issue