@nx/esbuild
Version:
199 lines (198 loc) • 9.73 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.esbuildExecutor = esbuildExecutor;
const pc = require("picocolors");
const devkit_1 = require("@nx/devkit");
const js_1 = require("@nx/js");
const esbuild = require("esbuild");
const normalize_1 = require("./lib/normalize");
const async_iterable_1 = require("@nx/devkit/src/utils/async-iterable");
const build_esbuild_options_1 = require("./lib/build-esbuild-options");
const get_extra_dependencies_1 = require("./lib/get-extra-dependencies");
const node_fs_1 = require("node:fs");
const path_1 = require("path");
const BUILD_WATCH_FAILED = `[ ${pc.red('watch')} ] build finished with errors (see above), watching for changes...`;
const BUILD_WATCH_SUCCEEDED = `[ ${pc.green('watch')} ] build succeeded, watching for changes...`;
async function* esbuildExecutor(_options, context) {
process.env.NODE_ENV ??= context.configurationName ?? 'production';
const options = (0, normalize_1.normalizeOptions)(_options, context);
if (options.deleteOutputPath)
(0, node_fs_1.rmSync)(options.outputPath, { recursive: true, force: true });
const assetsResult = await (0, js_1.copyAssets)(options, context);
const externalDependencies = options.external.reduce((acc, name) => {
const externalNode = context.projectGraph.externalNodes[`npm:${name}`];
if (externalNode) {
acc.push({
name,
outputs: [],
node: externalNode,
});
}
return acc;
}, []);
if (!options.thirdParty) {
const thirdPartyDependencies = (0, get_extra_dependencies_1.getExtraDependencies)(context.projectName, context.projectGraph);
for (const tpd of thirdPartyDependencies) {
options.external.push(tpd.node.data.packageName);
externalDependencies.push(tpd);
}
}
let packageJsonResult;
if (options.generatePackageJson) {
if (context.projectGraph.nodes[context.projectName].type !== 'app') {
devkit_1.logger.warn((0, devkit_1.stripIndents) `The project ${context.projectName} is using the 'generatePackageJson' option which is deprecated for library projects. It should only be used for applications.
For libraries, configure the project to use the '@nx/dependency-checks' ESLint rule instead (https://nx.dev/nx-api/eslint-plugin/documents/dependency-checks).`);
}
const cpjOptions = {
...options,
format: options.format,
// TODO(jack): make types generate with esbuild
skipTypings: true,
generateLockfile: true,
outputFileExtensionForCjs: (0, build_esbuild_options_1.getOutExtension)('cjs', options, context),
outputFileExtensionForEsm: (0, build_esbuild_options_1.getOutExtension)('esm', options, context),
excludeLibsInPackageJson: !options.thirdParty,
// TODO(jack): Remove the need to pass updateBuildableProjectDepsInPackageJson option when overrideDependencies or extraDependencies are passed.
// Add this back to fix a regression.
// See: https://github.com/nrwl/nx/issues/19773
updateBuildableProjectDepsInPackageJson: externalDependencies.length > 0,
};
// If we're bundling third-party packages, then any extra deps from external should be the only deps in package.json
if (options.thirdParty && externalDependencies.length > 0) {
cpjOptions.overrideDependencies = externalDependencies;
}
else {
cpjOptions.extraDependencies = externalDependencies;
}
packageJsonResult = await (0, js_1.copyPackageJson)(cpjOptions, context);
}
if (options.watch) {
return yield* (0, async_iterable_1.createAsyncIterable)(async ({ next, done }) => {
let hasTypeErrors = false;
const disposeFns = await Promise.all(options.format.map(async (format, idx) => {
const esbuildOptions = (0, build_esbuild_options_1.buildEsbuildOptions)(format, options, context);
const ctx = await esbuild.context({
...esbuildOptions,
plugins: [
// Only emit info on one of the watch processes.
idx === 0
? {
name: 'nx-watch-plugin',
setup(build) {
build.onEnd(async (result) => {
if (!options.skipTypeCheck ||
options.isTsSolutionSetup) {
const { errors } = await runTypeCheck(options, context);
hasTypeErrors = errors.length > 0;
}
const success = result.errors.length === 0 && !hasTypeErrors;
if (!success) {
devkit_1.logger.info(BUILD_WATCH_FAILED);
}
else {
devkit_1.logger.info(BUILD_WATCH_SUCCEEDED);
}
next({
success,
// Need to call getOutfile directly in the case of bundle=false and outfile is not set for esbuild.
outfile: (0, path_1.join)(context.root, (0, build_esbuild_options_1.getOutfile)(format, options, context)),
});
});
},
}
: null,
...(esbuildOptions?.plugins || []),
].filter(Boolean),
});
await ctx.watch();
return async () => ctx.dispose();
}));
registerCleanupCallback(() => {
if (typeof assetsResult?.stop === 'function')
assetsResult.stop();
if (typeof packageJsonResult?.stop === 'function') {
packageJsonResult.stop();
}
disposeFns.forEach(async (fn) => {
await fn();
});
done(); // return from async iterable
});
});
}
else {
// Run type-checks first and bail if they don't pass.
if (!options.skipTypeCheck || options.isTsSolutionSetup) {
const { errors } = await runTypeCheck(options, context);
if (errors.length > 0) {
yield { success: false };
return;
}
}
// Emit a build event for each file format.
for (let i = 0; i < options.format.length; i++) {
const format = options.format[i];
const esbuildOptions = (0, build_esbuild_options_1.buildEsbuildOptions)(format, options, context);
const buildResult = await esbuild.build(esbuildOptions);
if (options.metafile) {
const filename = options.format.length === 1
? 'meta.json'
: `meta.${options.format[i]}.json`;
(0, devkit_1.writeJsonFile)((0, devkit_1.joinPathFragments)(options.outputPath, filename), buildResult.metafile);
}
yield {
success: buildResult.errors.length === 0,
// Need to call getOutfile directly in the case of bundle=false and outfile is not set for esbuild.
// This field is needed for `@nx/js:node` executor to work.
outfile: (0, path_1.join)(context.root, (0, build_esbuild_options_1.getOutfile)(format, options, context)),
};
}
}
}
function getTypeCheckOptions(options, context) {
const { watch, tsConfig, outputPath } = options;
const projectRoot = context.projectGraph.nodes[context.projectName].data.root;
const typeCheckOptions = {
...(options.declaration
? {
mode: 'emitDeclarationOnly',
outDir: outputPath,
}
: {
mode: 'noEmit',
}),
tsConfigPath: (0, path_1.relative)(process.cwd(), (0, path_1.join)(context.root, tsConfig)),
workspaceRoot: context.root,
rootDir: options.declarationRootDir ?? context.root,
projectRoot,
};
if (watch) {
typeCheckOptions.incremental = true;
typeCheckOptions.cacheDir = devkit_1.cacheDir;
}
if (options.isTsSolutionSetup && options.skipTypeCheck) {
typeCheckOptions.ignoreDiagnostics = true;
}
return typeCheckOptions;
}
async function runTypeCheck(options, context) {
const { errors, warnings } = await (0, js_1.runTypeCheck)(getTypeCheckOptions(options, context));
const hasErrors = errors.length > 0;
const hasWarnings = warnings.length > 0;
if (hasErrors || hasWarnings) {
await (0, js_1.printDiagnostics)(errors, warnings);
}
return { errors, warnings };
}
function registerCleanupCallback(callback) {
const wrapped = () => {
callback();
process.off('SIGINT', wrapped);
process.off('SIGTERM', wrapped);
process.off('exit', wrapped);
};
process.on('SIGINT', wrapped);
process.on('SIGTERM', wrapped);
process.on('exit', wrapped);
}
exports.default = esbuildExecutor;