Max Schmitt 2024-01-12 18:51:45 +01:00 committed by GitHub
parent 37634df0c4
commit 7670fd21e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 45 deletions

View file

@ -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,17 +279,20 @@ 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;
// 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_ON: '1',
PW_TS_ESM_LEGACY_LOADER_ON: '1',
},
execArgv: execArgvWithExperimentalLoaderOptions(),
});
@ -298,6 +303,10 @@ function restartWithExperimentalTsEsm(configFile: string | null): boolean {
});
return true;
}
// Nodejs >= 21
registerESMLoader();
return false;
}
const kTraceModes: TraceMode[] = ['on', 'off', 'on-first-retry', 'on-all-retries', 'retain-on-failure'];

View file

@ -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) => {
// 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);
}) : undefined;
});
}
export async function startCollectingFileDeps() {
if (!loaderChannel)

View file

@ -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 };

View file

@ -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;

View file

@ -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 };

View 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));
}

View file

@ -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([