feat(core): add support for loading .ts files using ESM (#21268)
This commit is contained in:
parent
f0d93d0e43
commit
7a83e12234
@ -13,10 +13,32 @@ import * as Mod from 'module';
|
||||
*/
|
||||
|
||||
export function initLocal(workspace: WorkspaceTypeAndRoot) {
|
||||
// If module.register is not available, we need to restart the process with the experimental ESM loader.
|
||||
// Otherwise, usage of `registerTsProject` will not work for `.ts` files using ESM.
|
||||
// TODO: Remove this once Node 18 is out of LTS (March 2024).
|
||||
if (shouldRestartWithExperimentalTsEsmLoader()) {
|
||||
const child = require('child_process').fork(
|
||||
require.resolve('./nx'),
|
||||
process.argv.slice(2),
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
RESTARTED_WITH_EXPERIMENTAL_TS_ESM_LOADER: '1',
|
||||
},
|
||||
execArgv: execArgvWithExperimentalLoaderOptions(),
|
||||
}
|
||||
);
|
||||
child.on('close', (code: number | null) => {
|
||||
if (code !== 0 && code !== null) process.exit(code);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
process.env.NX_CLI_SET = 'true';
|
||||
|
||||
try {
|
||||
performance.mark('init-local');
|
||||
|
||||
monkeyPatchRequire();
|
||||
|
||||
if (workspace.type !== 'nx' && shouldDelegateToAngularCLI()) {
|
||||
@ -229,3 +251,36 @@ function monkeyPatchRequire() {
|
||||
// do some side-effect of your own
|
||||
};
|
||||
}
|
||||
|
||||
function shouldRestartWithExperimentalTsEsmLoader(): boolean {
|
||||
// Already restarted with experimental loader
|
||||
if (process.env.RESTARTED_WITH_EXPERIMENTAL_TS_ESM_LOADER === '1')
|
||||
return false;
|
||||
const nodeVersion = parseInt(process.versions.node.split('.')[0]);
|
||||
// `--experimental-loader` is only supported in Nodejs >= 16 so there is no point restarting for older versions
|
||||
if (nodeVersion < 16) return false;
|
||||
// Node 20.6.0 adds `module.register`, otherwise we need to restart process with "--experimental-loader ts-node/esm".
|
||||
return (
|
||||
!require('node:module').register &&
|
||||
moduleResolves('ts-node/esm') &&
|
||||
moduleResolves('typescript')
|
||||
);
|
||||
}
|
||||
|
||||
function execArgvWithExperimentalLoaderOptions() {
|
||||
return [
|
||||
...process.execArgv,
|
||||
'--no-warnings',
|
||||
'--experimental-loader',
|
||||
'ts-node/esm',
|
||||
];
|
||||
}
|
||||
|
||||
function moduleResolves(packageName: string) {
|
||||
try {
|
||||
require.resolve(packageName);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ const swcNodeInstalled = packageIsInstalled('@swc-node/register');
|
||||
const tsNodeInstalled = packageIsInstalled('ts-node/register');
|
||||
let ts: typeof import('typescript');
|
||||
|
||||
let isTsEsmLoaderRegistered = false;
|
||||
|
||||
/**
|
||||
* Optionally, if swc-node and tsconfig-paths are available in the current workspace, apply the require
|
||||
* register hooks so that .ts files can be used for writing custom workspace projects.
|
||||
@ -43,6 +45,19 @@ export function registerTsProject(
|
||||
registerTranspiler(compilerOptions),
|
||||
];
|
||||
|
||||
// Add ESM support for `.ts` files.
|
||||
// NOTE: There is no cleanup function for this, as it's not possible to unregister the loader.
|
||||
// Based on limited testing, it doesn't seem to matter if we register it multiple times, but just in
|
||||
// case let's keep a flag to prevent it.
|
||||
if (!isTsEsmLoaderRegistered) {
|
||||
const module = require('node:module');
|
||||
if (module.register && packageIsInstalled('ts-node/esm')) {
|
||||
const url = require('node:url');
|
||||
module.register(url.pathToFileURL(require.resolve('ts-node/esm')));
|
||||
}
|
||||
isTsEsmLoaderRegistered = true;
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const fn of cleanupFunctions) {
|
||||
fn();
|
||||
|
||||
@ -8,7 +8,7 @@ import { PlaywrightTestConfig } from '@playwright/test';
|
||||
// we overwrite the dynamic import function to use the regular syntax, which
|
||||
// jest does handle.
|
||||
import * as lcf from '../utils/load-config-file';
|
||||
(lcf as any).dynamicImport = (m) => import(m);
|
||||
(lcf as any).dynamicImport = (m) => require(m.split('?')[0]);
|
||||
|
||||
describe('@nx/playwright/plugin', () => {
|
||||
let createNodesFunction = createNodes[1];
|
||||
@ -34,7 +34,7 @@ describe('@nx/playwright/plugin', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tempFs.cleanup();
|
||||
// tempFs.cleanup();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
|
||||
@ -14,43 +14,25 @@ export async function loadPlaywrightConfig(
|
||||
): Promise<PlaywrightTestConfig> {
|
||||
{
|
||||
let module: any;
|
||||
const configPathWithTimestamp = `${configFilePath}?t=${Date.now()}`;
|
||||
if (extname(configFilePath) === '.ts') {
|
||||
const tsConfigPath = getRootTsConfigPath();
|
||||
|
||||
if (tsConfigPath) {
|
||||
const unregisterTsProject = registerTsProject(tsConfigPath);
|
||||
try {
|
||||
// Require's cache doesn't notice when the file is updated, and
|
||||
// this function is ran during daemon operation. If the config file
|
||||
// is updated, we need to read its new contents, so we need to clear the cache.
|
||||
// We can't just delete the cache entry for the config file, because
|
||||
// it might have imports that need to be updated as well.
|
||||
clearRequireCache();
|
||||
// ts-node doesn't support dynamic import, so we need to use require
|
||||
module = require(configFilePath);
|
||||
module = await dynamicImport(configPathWithTimestamp);
|
||||
} finally {
|
||||
unregisterTsProject();
|
||||
}
|
||||
} else {
|
||||
module = await dynamicImport(configFilePath);
|
||||
module = await dynamicImport(configPathWithTimestamp);
|
||||
}
|
||||
} else {
|
||||
module = await dynamicImport(configFilePath);
|
||||
module = await dynamicImport(configPathWithTimestamp);
|
||||
}
|
||||
return module.default ?? module;
|
||||
}
|
||||
}
|
||||
|
||||
const packageInstallationDirectories = ['node_modules', '.yarn'];
|
||||
|
||||
function clearRequireCache() {
|
||||
Object.keys(require.cache).forEach((key: string) => {
|
||||
// We don't want to clear the require cache of installed packages.
|
||||
// Clearing them can cause some issues when running Nx without the daemon
|
||||
// and may cause issues for other packages that use the module state
|
||||
// in some to store cached information.
|
||||
if (!packageInstallationDirectories.some((dir) => key.includes(dir))) {
|
||||
delete require.cache[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user