chore: remove support for esm loader with process restart (#35732)

This commit is contained in:
Dmitry Gozman 2025-04-25 08:54:24 +00:00 committed by GitHub
parent 8abe6ac2b4
commit b536b38a78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 35 additions and 148 deletions

View File

@ -17,7 +17,6 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
import { isRegExp } from 'playwright-core/lib/utils'; import { isRegExp } from 'playwright-core/lib/utils';
import { requireOrImport, setSingleTSConfig, setTransformConfig } from '../transform/transform'; import { requireOrImport, setSingleTSConfig, setTransformConfig } from '../transform/transform';
@ -25,7 +24,6 @@ import { errorWithFile, fileIsModule } from '../util';
import { FullConfigInternal } from './config'; import { FullConfigInternal } from './config';
import { configureESMLoader, configureESMLoaderTransformConfig, registerESMLoader } from './esmLoaderHost'; import { configureESMLoader, configureESMLoaderTransformConfig, registerESMLoader } from './esmLoaderHost';
import { addToCompilationCache } from '../transform/compilationCache'; import { addToCompilationCache } from '../transform/compilationCache';
import { execArgvWithExperimentalLoaderOptions, execArgvWithoutExperimentalLoaderOptions } from '../transform/esmUtils';
import type { ConfigLocation } from './config'; import type { ConfigLocation } from './config';
import type { ConfigCLIOverrides, SerializedConfig } from './ipc'; import type { ConfigCLIOverrides, SerializedConfig } from './ipc';
@ -102,6 +100,14 @@ async function loadUserConfig(location: ConfigLocation): Promise<Config> {
} }
export async function loadConfig(location: ConfigLocation, overrides?: ConfigCLIOverrides, ignoreProjectDependencies = false, metadata?: Config['metadata']): Promise<FullConfigInternal> { export async function loadConfig(location: ConfigLocation, overrides?: ConfigCLIOverrides, ignoreProjectDependencies = false, metadata?: Config['metadata']): Promise<FullConfigInternal> {
// 0. Setup ESM loader if needed.
if (!registerESMLoader()) {
// In Node.js < 18, complain if the config file is ESM. Historically, we would restart
// the process with --loader, but now we require newer Node.js.
if (location.resolvedConfigFile && fileIsModule(location.resolvedConfigFile))
throw errorWithFile(location.resolvedConfigFile, `Playwright requires Node.js 18 or higher to load esm modules. Please update your version of Node.js.`);
}
// 1. Setup tsconfig; configure ESM loader with tsconfig and compilation cache. // 1. Setup tsconfig; configure ESM loader with tsconfig and compilation cache.
setSingleTSConfig(overrides?.tsconfig); setSingleTSConfig(overrides?.tsconfig);
await configureESMLoader(); await configureESMLoader();
@ -359,57 +365,11 @@ function resolveConfigFile(configFileOrDirectory: string): string | undefined {
return configFileOrDirectory!; return configFileOrDirectory!;
} }
export async function loadConfigFromFileRestartIfNeeded(configFile: string | undefined, overrides?: ConfigCLIOverrides, ignoreDeps?: boolean): Promise<FullConfigInternal | null> { export async function loadConfigFromFile(configFile: string | undefined, overrides?: ConfigCLIOverrides, ignoreDeps?: boolean): Promise<FullConfigInternal> {
const location = resolveConfigLocation(configFile); return await loadConfig(resolveConfigLocation(configFile), overrides, ignoreDeps);
if (restartWithExperimentalTsEsm(location.resolvedConfigFile))
return null;
return await loadConfig(location, overrides, ignoreDeps);
} }
export async function loadEmptyConfigForMergeReports() { export async function loadEmptyConfigForMergeReports() {
// Merge reports is "different" for no good reason. It should not pick up local config from the cwd. // Merge reports is "different" for no good reason. It should not pick up local config from the cwd.
return await loadConfig({ configDir: process.cwd() }); return await loadConfig({ configDir: process.cwd() });
} }
export function restartWithExperimentalTsEsm(configFile: string | undefined, force: boolean = false): boolean {
// Opt-out switch.
if (process.env.PW_DISABLE_TS_ESM)
return false;
// There are two esm loader APIs:
// - Older API that needs a process restart. Available in Node 16, 17, and non-latest 18, 19 and 20.
// - Newer API that works in-process. Available in Node 21+ and latest 18, 19 and 20.
// First check whether we have already restarted with the ESM loader from the older API.
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;
}
// Now check for the newer API presence.
if (!require('node:module').register) {
// With older API requiring a process restart, do so conditionally on the config.
const configIsModule = !!configFile && fileIsModule(configFile);
if (!force && !configIsModule)
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_LEGACY_LOADER_ON: '1',
},
execArgv: execArgvWithExperimentalLoaderOptions(),
});
innerProcess.on('close', (code: number | null) => {
if (code !== 0 && code !== null)
gracefullyProcessExitDoNotHang(code);
});
return true;
}
// With the newer API, always enable the ESM loader, because it does not need a restart.
registerESMLoader();
return false;
}

View File

@ -21,21 +21,27 @@ import { PortTransport } from '../transform/portTransport';
import { singleTSConfig, transformConfig } from '../transform/transform'; import { singleTSConfig, transformConfig } from '../transform/transform';
let loaderChannel: PortTransport | undefined; let loaderChannel: PortTransport | undefined;
// Node.js < 20
if ((globalThis as any).__esmLoaderPortPreV20)
loaderChannel = createPortTransport((globalThis as any).__esmLoaderPortPreV20);
// Node.js >= 20
export let esmLoaderRegistered = false;
export function registerESMLoader() { export function registerESMLoader() {
// Opt-out switch.
if (process.env.PW_DISABLE_TS_ESM)
return true;
if (loaderChannel)
return true;
const register = require('node:module').register;
if (!register)
return false;
const { port1, port2 } = new MessageChannel(); const { port1, port2 } = new MessageChannel();
// register will wait until the loader is initialized. // register will wait until the loader is initialized.
require('node:module').register(url.pathToFileURL(require.resolve('../transform/esmLoader')), { register(url.pathToFileURL(require.resolve('../transform/esmLoader')), {
data: { port: port2 }, data: { port: port2 },
transferList: [port2], transferList: [port2],
}); });
loaderChannel = createPortTransport(port1); loaderChannel = createPortTransport(port1);
esmLoaderRegistered = true; return true;
} }
function createPortTransport(port: MessagePort) { function createPortTransport(port: MessagePort) {

View File

@ -17,8 +17,6 @@
import { setTimeOrigin, startProfiling, stopProfiling } from 'playwright-core/lib/utils'; import { setTimeOrigin, startProfiling, stopProfiling } from 'playwright-core/lib/utils';
import { serializeError } from '../util'; import { serializeError } from '../util';
import { registerESMLoader } from './esmLoaderHost';
import { execArgvWithoutExperimentalLoaderOptions } from '../transform/esmUtils';
import type { EnvProducedPayload, ProcessInitParams, TestInfoErrorImpl } from './ipc'; import type { EnvProducedPayload, ProcessInitParams, TestInfoErrorImpl } from './ipc';
@ -54,13 +52,6 @@ process.on('disconnect', () => gracefullyCloseAndExit(true));
process.on('SIGINT', () => {}); process.on('SIGINT', () => {});
process.on('SIGTERM', () => {}); 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 processRunner: ProcessRunner | undefined;
let processName: string | undefined; let processName: string | undefined;
const startingEnv = { ...process.env }; const startingEnv = { ...process.env };

View File

@ -23,7 +23,7 @@ import { program } from 'playwright-core/lib/cli/program';
import { gracefullyProcessExitDoNotHang, startProfiling, stopProfiling } from 'playwright-core/lib/utils'; import { gracefullyProcessExitDoNotHang, startProfiling, stopProfiling } from 'playwright-core/lib/utils';
import { builtInReporters, defaultReporter, defaultTimeout } from './common/config'; import { builtInReporters, defaultReporter, defaultTimeout } from './common/config';
import { loadConfigFromFileRestartIfNeeded, loadEmptyConfigForMergeReports, resolveConfigLocation } from './common/configLoader'; import { loadConfigFromFile, loadEmptyConfigForMergeReports, resolveConfigLocation } from './common/configLoader';
export { program } from 'playwright-core/lib/cli/program'; export { program } from 'playwright-core/lib/cli/program';
import { prepareErrorStack } from './reporters/base'; import { prepareErrorStack } from './reporters/base';
import { showHTMLReport } from './reporters/html'; import { showHTMLReport } from './reporters/html';
@ -77,9 +77,7 @@ function addClearCacheCommand(program: Command) {
command.description('clears build and test caches'); command.description('clears build and test caches');
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.action(async opts => { command.action(async opts => {
const config = await loadConfigFromFileRestartIfNeeded(opts.config); const config = await loadConfigFromFile(opts.config);
if (!config)
return;
const runner = new Runner(config); const runner = new Runner(config);
const { status } = await runner.clearCache(); const { status } = await runner.clearCache();
const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1);
@ -102,9 +100,7 @@ function addDevServerCommand(program: Command) {
command.description('start dev server'); command.description('start dev server');
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`); command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.action(async options => { command.action(async options => {
const config = await loadConfigFromFileRestartIfNeeded(options.config); const config = await loadConfigFromFile(options.config);
if (!config)
return;
const runner = new Runner(config); const runner = new Runner(config);
const { status } = await runner.runDevServer(); const { status } = await runner.runDevServer();
const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1);
@ -161,10 +157,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
await startProfiling(); await startProfiling();
const cliOverrides = overridesFromOptions(opts); const cliOverrides = overridesFromOptions(opts);
const config = await loadConfigFromFileRestartIfNeeded(opts.config, cliOverrides, opts.deps === false); const config = await loadConfigFromFile(opts.config, cliOverrides, opts.deps === false);
if (!config)
return;
config.cliArgs = args; config.cliArgs = args;
config.cliGrep = opts.grep as string | undefined; config.cliGrep = opts.grep as string | undefined;
config.cliOnlyChanged = opts.onlyChanged === true ? 'HEAD' : opts.onlyChanged; config.cliOnlyChanged = opts.onlyChanged === true ? 'HEAD' : opts.onlyChanged;
@ -191,8 +184,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
reporter: Array.isArray(opts.reporter) ? opts.reporter : opts.reporter ? [opts.reporter] : undefined, reporter: Array.isArray(opts.reporter) ? opts.reporter : opts.reporter ? [opts.reporter] : undefined,
}); });
await stopProfiling('runner'); await stopProfiling('runner');
if (status === 'restarted')
return;
const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1);
gracefullyProcessExitDoNotHang(exitCode); gracefullyProcessExitDoNotHang(exitCode);
return; return;
@ -211,8 +202,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
} }
); );
await stopProfiling('runner'); await stopProfiling('runner');
if (status === 'restarted')
return;
const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1);
gracefullyProcessExitDoNotHang(exitCode); gracefullyProcessExitDoNotHang(exitCode);
return; return;
@ -229,8 +218,6 @@ async function runTestServer(opts: { [key: string]: any }) {
const host = opts.host || 'localhost'; const host = opts.host || 'localhost';
const port = opts.port ? +opts.port : 0; const port = opts.port ? +opts.port : 0;
const status = await testServer.runTestServer(opts.config, { }, { host, port }); const status = await testServer.runTestServer(opts.config, { }, { host, port });
if (status === 'restarted')
return;
const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1); const exitCode = status === 'interrupted' ? 130 : (status === 'passed' ? 0 : 1);
gracefullyProcessExitDoNotHang(exitCode); gracefullyProcessExitDoNotHang(exitCode);
} }
@ -240,9 +227,7 @@ export async function withRunnerAndMutedWrite(configFile: string | undefined, ca
const stdoutWrite = process.stdout.write.bind(process.stdout); const stdoutWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = ((a: any, b: any, c: any) => process.stderr.write(a, b, c)) as any; process.stdout.write = ((a: any, b: any, c: any) => process.stderr.write(a, b, c)) as any;
try { try {
const config = await loadConfigFromFileRestartIfNeeded(configFile); const config = await loadConfigFromFile(configFile);
if (!config)
return;
const runner = new Runner(config); const runner = new Runner(config);
const result = await callback(runner); const result = await callback(runner);
stdoutWrite(JSON.stringify(result, undefined, 2), () => { stdoutWrite(JSON.stringify(result, undefined, 2), () => {
@ -265,9 +250,7 @@ async function listTestFiles(opts: { [key: string]: any }) {
async function mergeReports(reportDir: string | undefined, opts: { [key: string]: any }) { async function mergeReports(reportDir: string | undefined, opts: { [key: string]: any }) {
const configFile = opts.config; const configFile = opts.config;
const config = configFile ? await loadConfigFromFileRestartIfNeeded(configFile) : await loadEmptyConfigForMergeReports(); const config = configFile ? await loadConfigFromFile(configFile) : await loadEmptyConfigForMergeReports();
if (!config)
return;
const dir = path.resolve(process.cwd(), reportDir || ''); const dir = path.resolve(process.cwd(), reportDir || '');
const dirStat = await fs.promises.stat(dir).catch(e => null); const dirStat = await fs.promises.stat(dir).catch(e => null);

View File

@ -20,9 +20,6 @@ import { EventEmitter } from 'events';
import { assert, timeOrigin } from 'playwright-core/lib/utils'; import { assert, timeOrigin } from 'playwright-core/lib/utils';
import { debug } from 'playwright-core/lib/utilsBundle'; import { debug } from 'playwright-core/lib/utilsBundle';
import { esmLoaderRegistered } from '../common/esmLoaderHost';
import { execArgvWithExperimentalLoaderOptions } from '../transform/esmUtils';
import type { EnvProducedPayload, ProcessInitParams } from '../common/ipc'; import type { EnvProducedPayload, ProcessInitParams } from '../common/ipc';
import type { ProtocolResponse } from '../common/process'; import type { ProtocolResponse } from '../common/process';
@ -58,7 +55,6 @@ export class ProcessHost extends EventEmitter {
env: { env: {
...process.env, ...process.env,
...this._extraEnv, ...this._extraEnv,
...(esmLoaderRegistered ? { PW_TS_ESM_LOADER_ON: '1' } : {}),
}, },
stdio: [ stdio: [
'ignore', 'ignore',
@ -66,7 +62,6 @@ export class ProcessHost extends EventEmitter {
(options.onStdErr && !process.env.PW_RUNNER_DEBUG) ? 'pipe' : 'inherit', (options.onStdErr && !process.env.PW_RUNNER_DEBUG) ? 'pipe' : 'inherit',
'ipc', 'ipc',
], ],
...(process.env.PW_TS_ESM_LEGACY_LOADER_ON ? { execArgv: execArgvWithExperimentalLoaderOptions() } : {}),
}); });
this.process.on('exit', async (code, signal) => { this.process.on('exit', async (code, signal) => {
this._processDidExit = true; this._processDidExit = true;

View File

@ -24,7 +24,7 @@ import { open } from 'playwright-core/lib/utilsBundle';
import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters'; import { createErrorCollectingReporter, createReporterForTestServer, createReporters } from './reporters';
import { SigIntWatcher } from './sigIntWatcher'; import { SigIntWatcher } from './sigIntWatcher';
import { TestRun, createApplyRebaselinesTask, createClearCacheTask, createGlobalSetupTasks, createListFilesTask, createLoadTask, createReportBeginTask, createRunTestsTasks, createStartDevServerTask, runTasks, runTasksDeferCleanup } from './tasks'; import { TestRun, createApplyRebaselinesTask, createClearCacheTask, createGlobalSetupTasks, createListFilesTask, createLoadTask, createReportBeginTask, createRunTestsTasks, createStartDevServerTask, runTasks, runTasksDeferCleanup } from './tasks';
import { loadConfig, resolveConfigLocation, restartWithExperimentalTsEsm } from '../common/configLoader'; import { loadConfig, resolveConfigLocation } from '../common/configLoader';
import { Watcher } from '../fsWatcher'; import { Watcher } from '../fsWatcher';
import { baseFullConfig } from '../isomorphic/teleReceiver'; import { baseFullConfig } from '../isomorphic/teleReceiver';
import { addGitCommitInfoPlugin } from '../plugins/gitCommitInfoPlugin'; import { addGitCommitInfoPlugin } from '../plugins/gitCommitInfoPlugin';
@ -435,7 +435,7 @@ export class TestServerDispatcher implements TestServerInterface {
} }
} }
export async function runUIMode(configFile: string | undefined, configCLIOverrides: ConfigCLIOverrides, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise<reporterTypes.FullResult['status'] | 'restarted'> { export async function runUIMode(configFile: string | undefined, configCLIOverrides: ConfigCLIOverrides, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise<reporterTypes.FullResult['status']> {
const configLocation = resolveConfigLocation(configFile); const configLocation = resolveConfigLocation(configFile);
return await innerRunTestServer(configLocation, configCLIOverrides, options, async (server: HttpServer, cancelPromise: ManualPromise<void>) => { return await innerRunTestServer(configLocation, configCLIOverrides, options, async (server: HttpServer, cancelPromise: ManualPromise<void>) => {
await installRootRedirect(server, [], { ...options, webApp: 'uiMode.html' }); await installRootRedirect(server, [], { ...options, webApp: 'uiMode.html' });
@ -469,7 +469,7 @@ async function installedChromiumChannelForUI(configLocation: ConfigLocation, con
return undefined; return undefined;
} }
export async function runTestServer(configFile: string | undefined, configCLIOverrides: ConfigCLIOverrides, options: { host?: string, port?: number }): Promise<reporterTypes.FullResult['status'] | 'restarted'> { export async function runTestServer(configFile: string | undefined, configCLIOverrides: ConfigCLIOverrides, options: { host?: string, port?: number }): Promise<reporterTypes.FullResult['status']> {
const configLocation = resolveConfigLocation(configFile); const configLocation = resolveConfigLocation(configFile);
return await innerRunTestServer(configLocation, configCLIOverrides, options, async server => { return await innerRunTestServer(configLocation, configCLIOverrides, options, async server => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -477,9 +477,7 @@ export async function runTestServer(configFile: string | undefined, configCLIOve
}); });
} }
async function innerRunTestServer(configLocation: ConfigLocation, configCLIOverrides: ConfigCLIOverrides, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise<void>) => Promise<void>): Promise<reporterTypes.FullResult['status'] | 'restarted'> { async function innerRunTestServer(configLocation: ConfigLocation, configCLIOverrides: ConfigCLIOverrides, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise<void>) => Promise<void>): Promise<reporterTypes.FullResult['status']> {
if (restartWithExperimentalTsEsm(undefined, true))
return 'restarted';
const testServer = new TestServer(configLocation, configCLIOverrides); const testServer = new TestServer(configLocation, configCLIOverrides);
const cancelPromise = new ManualPromise<void>(); const cancelPromise = new ManualPromise<void>();
const sigintWatcher = new SigIntWatcher(); const sigintWatcher = new SigIntWatcher();

View File

@ -25,7 +25,6 @@ import { colors } from 'playwright-core/lib/utils';
import { separator, terminalScreen } from '../reporters/base'; import { separator, terminalScreen } from '../reporters/base';
import { enquirer } from '../utilsBundle'; import { enquirer } from '../utilsBundle';
import { TestServerDispatcher } from './testServer'; import { TestServerDispatcher } from './testServer';
import { restartWithExperimentalTsEsm } from '../common/configLoader';
import { TeleSuiteUpdater } from '../isomorphic/teleSuiteUpdater'; import { TeleSuiteUpdater } from '../isomorphic/teleSuiteUpdater';
import { TestServerConnection } from '../isomorphic/testServerConnection'; import { TestServerConnection } from '../isomorphic/testServerConnection';
@ -73,10 +72,7 @@ interface WatchModeOptions {
grep?: string; grep?: string;
} }
export async function runWatchModeLoop(configLocation: ConfigLocation, initialOptions: WatchModeOptions): Promise<FullResult['status'] | 'restarted'> { export async function runWatchModeLoop(configLocation: ConfigLocation, initialOptions: WatchModeOptions): Promise<FullResult['status']> {
if (restartWithExperimentalTsEsm(undefined, true))
return 'restarted';
const options: WatchModeOptions = { ...initialOptions }; const options: WatchModeOptions = { ...initialOptions };
let bufferMode = false; let bufferMode = false;

View File

@ -86,15 +86,6 @@ async function load(moduleUrl: string, context: { format?: string }, defaultLoad
let transport: PortTransport | undefined; let transport: PortTransport | undefined;
// Node.js < 20
function globalPreload(context: { port: MessagePort }) {
transport = createTransport(context.port);
return `
globalThis.__esmLoaderPortPreV20 = port;
`;
}
// Node.js >= 20
function initialize(data: { port: MessagePort }) { function initialize(data: { port: MessagePort }) {
transport = createTransport(data?.port); transport = createTransport(data?.port);
} }
@ -132,4 +123,4 @@ function createTransport(port: MessagePort) {
} }
module.exports = { globalPreload, initialize, load, resolve }; module.exports = { initialize, load, resolve };

View File

@ -1,33 +0,0 @@
/**
* 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));
}