nx/packages/js/src/utils/swc/compile-swc.ts

165 lines
4.9 KiB
TypeScript

import { ExecutorContext, logger } from '@nrwl/devkit';
import { exec, execSync } from 'child_process';
import { EMPTY, Observable, zip } from 'rxjs';
import { concatMap, map } from 'rxjs/operators';
import { NormalizedSwcExecutorOptions } from '../schema';
import { printDiagnostics } from '../typescript/print-diagnostics';
import {
runTypeCheck,
runTypeCheckWatch,
TypeCheckOptions,
} from '../typescript/run-type-check';
export function compileSwc(
context: ExecutorContext,
normalizedOptions: NormalizedSwcExecutorOptions,
postCompilationCallback: () => Promise<void>
) {
logger.log(`Compiling with SWC for ${context.projectName}...`);
const srcPath = `../${normalizedOptions.swcCliOptions.projectDir}`;
let swcCmd = `npx swc ${srcPath} -d ${normalizedOptions.swcCliOptions.destPath} --source-maps --no-swcrc --config-file=${normalizedOptions.swcrcPath}`;
const postCompilationOperator = () =>
concatMap(({ success }) => {
if (success) {
return postCompilationCallback().then(() => ({ success }));
}
return EMPTY;
});
const compile$ = new Observable<{ success: boolean }>((subscriber) => {
if (normalizedOptions.watch) {
swcCmd += ' --watch';
const watchProcess = createSwcWatchProcess(
swcCmd,
normalizedOptions.projectRoot,
(success) => {
subscriber.next({ success });
}
);
return () => {
watchProcess.close();
subscriber.complete();
};
}
const swcCmdLog = execSync(swcCmd, {
cwd: normalizedOptions.projectRoot,
}).toString();
logger.log(swcCmdLog.replace(/\n/, ''));
subscriber.next({ success: swcCmdLog.includes('Successfully compiled') });
return () => {
subscriber.complete();
};
});
if (normalizedOptions.skipTypeCheck) {
return compile$.pipe(postCompilationOperator());
}
const tsOptions = {
outputPath: normalizedOptions.outputPath,
projectName: context.projectName,
projectRoot: normalizedOptions.projectRoot,
tsConfig: normalizedOptions.tsConfig,
watch: normalizedOptions.watch,
};
const outDir = tsOptions.outputPath.replace(`/${tsOptions.projectRoot}`, '');
const typeCheck$ = new Observable<{ success: boolean }>((subscriber) => {
const typeCheckOptions: TypeCheckOptions = {
mode: 'emitDeclarationOnly',
tsConfigPath: tsOptions.tsConfig,
outDir,
workspaceRoot: normalizedOptions.root,
};
if (normalizedOptions.watch) {
let typeCheckRunner: { close: () => void };
let preEmit = false;
runTypeCheckWatch(
typeCheckOptions,
(diagnostic, formattedDiagnostic, errorCount) => {
// 6031 and 6032 are to skip watchCompilerHost initialization (Start watching for changes... message)
// We also skip if preEmit has been set to true, because it means that the first type check before
// the WatchCompiler emits.
if (preEmit && diagnostic.code !== 6031 && diagnostic.code !== 6032) {
const hasErrors = errorCount > 0;
if (hasErrors) {
void printDiagnostics([formattedDiagnostic]);
} else {
void printDiagnostics([], [formattedDiagnostic]);
}
subscriber.next({ success: !hasErrors });
}
}
).then(({ close, preEmitErrors, preEmitWarnings }) => {
const hasErrors = preEmitErrors.length > 0;
if (hasErrors) {
void printDiagnostics(preEmitErrors, preEmitWarnings);
}
typeCheckRunner = { close };
subscriber.next({ success: !hasErrors });
preEmit = true;
});
return () => {
if (typeCheckRunner) {
typeCheckRunner.close();
}
subscriber.complete();
};
}
runTypeCheck(typeCheckOptions).then(({ errors, warnings }) => {
const hasErrors = errors.length > 0;
if (hasErrors) {
void printDiagnostics(errors, warnings);
}
subscriber.next({ success: !hasErrors });
subscriber.complete();
});
return () => {
subscriber.complete();
};
});
return zip(compile$, typeCheck$).pipe(
map(([compileResult, typeCheckResult]) => ({
success: compileResult.success && typeCheckResult.success,
})),
postCompilationOperator()
);
}
function createSwcWatchProcess(
swcCmd: string,
cwd: string,
callback: (success: boolean) => void
) {
const watchProcess = exec(swcCmd, { cwd });
watchProcess.stdout.on('data', (data) => {
process.stdout.write(data);
callback(data.includes('Successfully compiled'));
});
watchProcess.stderr.on('data', (err) => {
process.stderr.write(err);
callback(false);
});
const processExitListener = () => watchProcess.kill();
process.on('SIGINT', processExitListener);
process.on('SIGTERM', processExitListener);
process.on('exit', processExitListener);
watchProcess.on('exit', () => {
callback(true);
});
return { close: () => watchProcess.kill() };
}