mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 21:07:48 +00:00
[node] fix TypeScript + ESM in Windows (#9487)
ESM on Windows versions of Node.js require `--loader PATH` to be a `file://` protocol. This PR allows `vc dev` to be used with both TypeScript and ESM
This commit is contained in:
102
packages/node/src/fork-dev-server.ts
Normal file
102
packages/node/src/fork-dev-server.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import once from '@tootallnate/once';
|
||||
import { cloneEnv, Config, Meta } from '@vercel/build-utils';
|
||||
import { ChildProcess, fork, ForkOptions } from 'child_process';
|
||||
import { pathToFileURL } from 'url';
|
||||
import { join } from 'path';
|
||||
|
||||
export function forkDevServer(options: {
|
||||
tsConfig: any;
|
||||
config: Config;
|
||||
maybeTranspile: boolean;
|
||||
workPath: string | undefined;
|
||||
isTypeScript: boolean;
|
||||
isEsm: boolean;
|
||||
require_: NodeRequire;
|
||||
entrypoint: string;
|
||||
meta: Meta;
|
||||
|
||||
/**
|
||||
* A path to the dev-server path. This is used in tests.
|
||||
*/
|
||||
devServerPath?: string;
|
||||
}) {
|
||||
let nodeOptions = process.env.NODE_OPTIONS;
|
||||
const tsNodePath = options.require_.resolve('ts-node');
|
||||
const esmLoader = pathToFileURL(join(tsNodePath, '..', '..', 'esm.mjs'));
|
||||
const cjsLoader = join(tsNodePath, '..', '..', 'register', 'index.js');
|
||||
const devServerPath =
|
||||
options.devServerPath || join(__dirname, 'dev-server.js');
|
||||
|
||||
if (options.maybeTranspile) {
|
||||
if (options.isTypeScript) {
|
||||
if (options.isEsm) {
|
||||
nodeOptions = `--loader ${esmLoader} ${nodeOptions || ''}`;
|
||||
} else {
|
||||
nodeOptions = `--require ${cjsLoader} ${nodeOptions || ''}`;
|
||||
}
|
||||
} else {
|
||||
if (options.isEsm) {
|
||||
// no transform needed because Node.js supports ESM natively
|
||||
} else {
|
||||
nodeOptions = `--require ${cjsLoader} ${nodeOptions || ''}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const forkOptions: ForkOptions = {
|
||||
cwd: options.workPath,
|
||||
execArgv: [],
|
||||
env: cloneEnv(process.env, options.meta.env, {
|
||||
VERCEL_DEV_ENTRYPOINT: options.entrypoint,
|
||||
VERCEL_DEV_IS_ESM: options.isEsm ? '1' : undefined,
|
||||
VERCEL_DEV_CONFIG: JSON.stringify(options.config),
|
||||
VERCEL_DEV_BUILD_ENV: JSON.stringify(options.meta.buildEnv || {}),
|
||||
TS_NODE_TRANSPILE_ONLY: '1',
|
||||
TS_NODE_COMPILER_OPTIONS: options.tsConfig?.compilerOptions
|
||||
? JSON.stringify(options.tsConfig.compilerOptions)
|
||||
: undefined,
|
||||
NODE_OPTIONS: nodeOptions,
|
||||
}),
|
||||
};
|
||||
const child = fork(devServerPath, [], forkOptions);
|
||||
|
||||
checkForPid(devServerPath, child);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
function checkForPid(
|
||||
path: string,
|
||||
process: ChildProcess
|
||||
): asserts process is ChildProcess & { pid: number } {
|
||||
if (!process.pid) {
|
||||
throw new Error(`Child Process has no "pid" when forking: "${path}"`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When launching a dev-server, we want to know its state.
|
||||
* This function will be used to know whether it was exited (due to some error),
|
||||
* or it is listening to new requests, and we can start proxying requests.
|
||||
*/
|
||||
export async function readMessage(
|
||||
child: ChildProcess
|
||||
): Promise<
|
||||
| { state: 'message'; value: { port: number } }
|
||||
| { state: 'exit'; value: [number, string | null] }
|
||||
> {
|
||||
const onMessage = once<{ port: number }>(child, 'message');
|
||||
const onExit = once.spread<[number, string | null]>(child, 'close');
|
||||
const result = await Promise.race([
|
||||
onMessage.then(x => {
|
||||
return { state: 'message' as const, value: x };
|
||||
}),
|
||||
onExit.then(v => {
|
||||
return { state: 'exit' as const, value: v };
|
||||
}),
|
||||
]);
|
||||
onExit.cancel();
|
||||
onMessage.cancel();
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user