nx/packages/js/src/executors/swc/swc.impl.ts

230 lines
6.8 KiB
TypeScript

import { ExecutorContext, readJsonFile } from '@nx/devkit';
import { assetGlobsToFiles, FileInputOutput } from '../../utils/assets/assets';
import { removeSync } from 'fs-extra';
import { sync as globSync } from 'fast-glob';
import { dirname, join, relative, resolve, normalize } from 'path';
import { copyAssets } from '../../utils/assets';
import { checkDependencies } from '../../utils/check-dependencies';
import {
getHelperDependency,
HelperDependency,
} from '../../utils/compiler-helper-dependency';
import {
handleInliningBuild,
isInlineGraphEmpty,
postProcessInlinedDependencies,
} from '../../utils/inline';
import { copyPackageJson } from '../../utils/package-json';
import {
NormalizedSwcExecutorOptions,
SwcExecutorOptions,
} from '../../utils/schema';
import { compileSwc, compileSwcWatch } from '../../utils/swc/compile-swc';
import { getSwcrcPath } from '../../utils/swc/get-swcrc-path';
import { generateTmpSwcrc } from '../../utils/swc/inline';
function normalizeOptions(
options: SwcExecutorOptions,
root: string,
sourceRoot: string,
projectRoot: string
): NormalizedSwcExecutorOptions {
const outputPath = join(root, options.outputPath);
if (options.skipTypeCheck == null) {
options.skipTypeCheck = false;
}
if (options.watch == null) {
options.watch = false;
}
// TODO: put back when inlining story is more stable
// if (options.external == null) {
// options.external = 'all';
// } else if (Array.isArray(options.external) && options.external.length === 0) {
// options.external = 'none';
// }
if (Array.isArray(options.external) && options.external.length > 0) {
const firstItem = options.external[0];
if (firstItem === 'all' || firstItem === 'none') {
options.external = firstItem;
}
}
const files: FileInputOutput[] = assetGlobsToFiles(
options.assets,
root,
outputPath
);
const projectRootParts = projectRoot.split('/');
// We pop the last part of the `projectRoot` to pass
// the last part (projectDir) and the remainder (projectRootParts) to swc
const projectDir = projectRootParts.pop();
// default to current directory if projectRootParts is [].
// Eg: when a project is at the root level, outside of layout dir
const swcCwd = projectRootParts.join('/') || '.';
const { swcrcPath, tmpSwcrcPath } = getSwcrcPath(options, root, projectRoot);
const swcCliOptions = {
srcPath: projectDir,
destPath: relative(join(root, swcCwd), outputPath),
swcCwd,
swcrcPath,
};
return {
...options,
mainOutputPath: resolve(
outputPath,
options.main.replace(`${projectRoot}/`, '').replace('.ts', '.js')
),
files,
root,
sourceRoot,
projectRoot,
originalProjectRoot: projectRoot,
outputPath,
tsConfig: join(root, options.tsConfig),
swcCliOptions,
tmpSwcrcPath,
} as NormalizedSwcExecutorOptions;
}
export async function* swcExecutor(
_options: SwcExecutorOptions,
context: ExecutorContext
) {
const { sourceRoot, root } =
context.projectsConfigurations.projects[context.projectName];
const options = normalizeOptions(_options, context.root, sourceRoot, root);
const { tmpTsConfig, dependencies } = checkDependencies(
context,
options.tsConfig
);
if (tmpTsConfig) {
options.tsConfig = tmpTsConfig;
}
const swcHelperDependency = getHelperDependency(
HelperDependency.swc,
options.swcCliOptions.swcrcPath,
dependencies,
context.projectGraph
);
if (swcHelperDependency) {
dependencies.push(swcHelperDependency);
}
const inlineProjectGraph = handleInliningBuild(
context,
options,
options.tsConfig
);
if (!isInlineGraphEmpty(inlineProjectGraph)) {
options.projectRoot = '.'; // set to root of workspace to include other libs for type check
// remap paths for SWC compilation
options.swcCliOptions.srcPath = options.swcCliOptions.swcCwd;
options.swcCliOptions.swcCwd = '.';
options.swcCliOptions.destPath = join(
options.swcCliOptions.destPath.split(normalize('../')).at(-1),
options.swcCliOptions.srcPath
);
// tmp swcrc with dependencies to exclude
// - buildable libraries
// - other libraries that are not dependent on the current project
options.swcCliOptions.swcrcPath = generateTmpSwcrc(
inlineProjectGraph,
options.swcCliOptions.swcrcPath,
options.tmpSwcrcPath
);
}
function determineModuleFormatFromSwcrc(
absolutePathToSwcrc: string
): 'cjs' | 'esm' {
const swcrc = readJsonFile(absolutePathToSwcrc);
return swcrc.module?.type?.startsWith('es') ? 'esm' : 'cjs';
}
if (options.watch) {
let disposeFn: () => void;
process.on('SIGINT', () => disposeFn());
process.on('SIGTERM', () => disposeFn());
return yield* compileSwcWatch(context, options, async () => {
const assetResult = await copyAssets(options, context);
const packageJsonResult = await copyPackageJson(
{
...options,
additionalEntryPoints: createEntryPoints(options, context),
format: [
determineModuleFormatFromSwcrc(options.swcCliOptions.swcrcPath),
],
// As long as d.ts files match their .js counterparts, we don't need to emit them.
// TSC can match them correctly based on file names.
skipTypings: true,
},
context
);
removeTmpSwcrc(options.swcCliOptions.swcrcPath);
disposeFn = () => {
assetResult?.stop();
packageJsonResult?.stop();
};
});
} else {
return yield compileSwc(context, options, async () => {
await copyAssets(options, context);
await copyPackageJson(
{
...options,
additionalEntryPoints: createEntryPoints(options, context),
format: [
determineModuleFormatFromSwcrc(options.swcCliOptions.swcrcPath),
],
// As long as d.ts files match their .js counterparts, we don't need to emit them.
// TSC can match them correctly based on file names.
skipTypings: true,
extraDependencies: swcHelperDependency ? [swcHelperDependency] : [],
},
context
);
removeTmpSwcrc(options.swcCliOptions.swcrcPath);
postProcessInlinedDependencies(
options.outputPath,
options.originalProjectRoot,
inlineProjectGraph
);
});
}
}
function removeTmpSwcrc(swcrcPath: string) {
if (
swcrcPath.includes(normalize('tmp/')) &&
swcrcPath.includes('.generated.swcrc')
) {
removeSync(dirname(swcrcPath));
}
}
function createEntryPoints(
options: { additionalEntryPoints?: string[] },
context: ExecutorContext
): string[] {
if (!options.additionalEntryPoints?.length) return [];
return globSync(options.additionalEntryPoints, {
cwd: context.root,
});
}
export default swcExecutor;