feat: always enable ESM loader with the new API (#29991)

It does not require a process restart anymore, so safe to enable.

Fixes #29747.
This commit is contained in:
Dmitry Gozman 2024-03-18 21:54:25 -07:00 committed by GitHub
parent b41b802662
commit be1af15d57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 14 deletions

View file

@ -334,25 +334,33 @@ export async function loadEmptyConfigForMergeReports() {
}
export function restartWithExperimentalTsEsm(configFile: string | undefined, force: boolean = false): boolean {
const nodeVersion = +process.versions.node.split('.')[0];
// New experimental loader is only supported on Node 16+.
if (nodeVersion < 16)
return false;
if (!configFile && !force)
return false;
// Opt-out switch.
if (process.env.PW_DISABLE_TS_ESM)
return false;
// Node.js < 20
// 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;
}
if (!force && !fileIsModule(configFile!))
return false;
// Node.js < 20
// Now check for the newer API presence.
if (!require('node:module').register) {
// Older API is experimental, only supported on Node 16+.
const nodeVersion = +process.versions.node.split('.')[0];
if (nodeVersion < 16)
return false;
// 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,
@ -367,7 +375,8 @@ export function restartWithExperimentalTsEsm(configFile: string | undefined, for
});
return true;
}
// Nodejs >= 21
// With the newer API, always enable the ESM loader, because it does not need a restart.
registerESMLoader();
return false;
}

View file

@ -552,7 +552,9 @@ test('should load cjs config and test in non-ESM mode', async ({ runInlineTest }
expect(result.passed).toBe(2);
});
test('should disallow ESM when config is cjs', async ({ runInlineTest }) => {
test('should allow ESM when config is cjs', async ({ runInlineTest, nodeVersion }) => {
test.skip(nodeVersion.major < 18, 'ESM loader is enabled conditionally with older API');
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.cjs': `
@ -567,8 +569,24 @@ test('should disallow ESM when config is cjs', async ({ runInlineTest }) => {
`,
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain('Unknown file extension ".ts"');
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should load mts without config', async ({ runInlineTest, nodeVersion }) => {
test.skip(nodeVersion.major < 18, 'ESM loader is enabled conditionally with older API');
const result = await runInlineTest({
'a.test.mts': `
import { test, expect } from '@playwright/test';
test('check project name', ({}, testInfo) => {
expect(true).toBe(true);
});
`,
});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should be able to use use execSync with a Node.js file inside a spec', async ({ runInlineTest }) => {