@anatine/esbuildnx
Version:
Esbuild plugin for Nx
316 lines • 13.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildExecutor = void 0;
const tslib_1 = require("tslib");
const normalize_options_1 = require("../../utils/normalize-options");
const fs_extra_1 = require("fs-extra");
const workspace_1 = require("@nrwl/workspace");
const esbuild_1 = require("esbuild");
const child_process_1 = require("child_process");
const esbuild_decorators_1 = require("@anatine/esbuild-decorators");
const chalk_1 = require("chalk");
const node_watch_1 = tslib_1.__importDefault(require("node-watch"));
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const rxjs_for_await_1 = require("rxjs-for-await");
const date_fns_1 = require("date-fns");
// import { exportDiagnostics } from '../../utils/print-diagnostics';
const util_1 = require("util");
const walk_packages_1 = require("../../utils/walk-packages");
const assets_1 = require("../../utils/assets");
const constants_1 = require("../../utils/constants");
function buildExecutor(rawOptions, context) {
const { sourceRoot, root } = context.workspace.projects[context.projectName];
if (!sourceRoot) {
throw new Error(`${context.projectName} does not have a sourceRoot.`);
}
if (!root) {
throw new Error(`${context.projectName} does not have a root.`);
}
// Eventually, it would be great to expose more esbuild settings on command line.
// For now, the app root directory can utilize an esbuild.json file for build API settings
// https://esbuild.github.io/api/#build-api
const esBuildExists = (0, fs_extra_1.pathExistsSync)(`${root}/esbuild.json`);
const packageExists = (0, fs_extra_1.pathExistsSync)(`${root}/package.json`);
const esbuildConfig = esBuildExists
? (0, workspace_1.readJsonFile)(`${root}/esbuild.json`)
: { external: [] };
const projectPackage = packageExists
? (0, workspace_1.readJsonFile)(`${root}/package.json`)
: {};
const options = (0, normalize_options_1.normalizeBuildOptions)(rawOptions, esbuildConfig, context.root, sourceRoot, root);
const outdir = `${options.outputPath}`;
const outfile = `${outdir}/${constants_1.OUTFILE_NAME}`;
const watchDir = `${options.root}/${options.sourceRoot}`;
const packages = packageExists
? Object.keys(projectPackage.dependencies)
: [];
esbuildConfig.external = [...packages, ...(esbuildConfig.external || [])];
const esbuildOptions = Object.assign(Object.assign({ logLevel: 'silent', platform: 'node', bundle: options.bundle || true, sourcemap: 'external', charset: 'utf8', color: true, conditions: options.watch ? ['development'] : ['production'], watch: options.watch || false, absWorkingDir: options.root, plugins: [
(0, esbuild_decorators_1.esbuildDecorators)({
cwd: options.root,
}),
],
// banner: {
// js: '// Compiled by esbuildnx ',
// },
tsconfig: options.tsConfig, entryPoints: [options.main], outdir }, esbuildConfig), { incremental: options.watch || false });
let buildCounter = 1;
const buildSubscriber = runBuild(esbuildOptions, watchDir).pipe((0, operators_1.map)(({ buildResult, buildFailure }) => {
let message = '';
const timeString = (0, date_fns_1.format)(new Date(), 'h:mm:ss a');
const count = (0, chalk_1.gray)(`[${buildCounter}]`);
const prefix = `esbuild ${count} ${timeString}`;
// const warnings: string[] = [];
if ((buildResult === null || buildResult === void 0 ? void 0 : buildResult.warnings.length) > 0) {
let warningMessage = (0, chalk_1.yellow)(`${prefix} - Warnings:`);
buildResult === null || buildResult === void 0 ? void 0 : buildResult.warnings.forEach((warning) => {
warningMessage += `\n ${(0, chalk_1.yellow)(warning.location.file)}(${warning.location.line},${warning.location.column}):`;
warningMessage += ` ${warning.location.lineText.trim()}`;
warningMessage += (0, chalk_1.gray)(`\n ${warning.text}\n`);
});
// console.log(warningMessage);
message += warningMessage;
}
if (buildFailure) {
// console.log(red(`\nEsbuild Error ${count}`));
// console.error(stats.buildFailure);
message += (0, chalk_1.red)(`Esbuild Error ${count}`);
message += buildFailure;
}
else if ((buildResult === null || buildResult === void 0 ? void 0 : buildResult.warnings.length) > 0) {
message += (0, chalk_1.green)(`${prefix} - Build finished with ${(0, chalk_1.yellow)(buildResult === null || buildResult === void 0 ? void 0 : buildResult.warnings.length)} warnings. \n`);
}
else {
message += (0, chalk_1.green)(`${prefix} - Build finished \n`);
}
buildCounter++;
return {
success: !buildFailure,
message,
};
}));
let typeCounter = 1;
const tscBufferTrigger = new rxjs_1.Subject();
const tscSubscriber = runTsc({
tsconfigPath: options.tsConfig,
watch: options.watch || !!esbuildOptions.watch,
root: options.root,
useGlobal: false,
}).pipe((0, operators_1.map)(({ info, error, end }) => {
let message = '';
let hasErrors = Boolean(error);
const count = (0, chalk_1.gray)(`[${typeCounter}]`);
const prefix = `tsc ${count}`;
if (error) {
message += (0, chalk_1.red)(`${prefix} ${error.replace(/\n/g, '')} \n`);
}
else if (info) {
if (info.match(/Found\s\d*\serror/)) {
if (info.includes('Found 0 errors')) {
message += (0, chalk_1.green)(`${prefix} ${info.replace(/\n/g, '')} \n`);
}
else {
hasErrors = true;
message += (0, chalk_1.yellow)(`${prefix} ${info.replace(/\n/g, '')} \n`);
}
tscBufferTrigger.next(true);
}
else {
message += (0, chalk_1.green)(`${prefix} ${info.replace(/\n/g, '')} \n`);
}
}
return { info, error, end, message, hasErrors };
}), bufferUntil(({ info }) => !!(info === null || info === void 0 ? void 0 : info.match(/Found\s\d*\serror/))),
// bufferUntil(({ info }) => true),
(0, operators_1.map)((values) => {
typeCounter++;
let message = '';
values.forEach((value) => (message += value.message));
// console.log(message);
return {
success: !values.find((value) => value.hasErrors),
message,
};
}));
const packageCopySubscriber = runCopyPackages(process.cwd(), options.outputPath, esbuildOptions.external).pipe((0, operators_1.map)((result) => {
var _a;
const message = (_a = result.error) !== null && _a !== void 0 ? _a : result.copyResult;
return {
success: result.success,
message,
};
}));
const assetCopySubscriber = runCopyAssets(options.assets, '', options.outputPath).pipe((0, operators_1.map)((result) => result));
// exportDiagnostics(
// `OUTPUT_LOG.ts`,
// `const output = ${inspect(
// {
// cwd: process.cwd(),
// options,
// rawOptions,
// context,
// projGraph,
// workspace,
// },
// false,
// 10
// )}`
// );
if (options.watch) {
return (0, rxjs_for_await_1.eachValueFrom)((0, rxjs_1.zip)(buildSubscriber, tscSubscriber).pipe((0, operators_1.map)(([buildResults, tscResults]) => {
// console.log('\x1Bc');
console.log(tscResults.message);
console.log(buildResults.message);
return {
success: (buildResults === null || buildResults === void 0 ? void 0 : buildResults.success) && (tscResults === null || tscResults === void 0 ? void 0 : tscResults.success),
outfile,
};
})));
}
return (0, rxjs_for_await_1.eachValueFrom)((0, rxjs_1.zip)(buildSubscriber, tscSubscriber, packageCopySubscriber, assetCopySubscriber).pipe((0, operators_1.map)(([buildResults, tscResults, packageCopyResults, assetCopyResults]) => {
// console.log('\x1Bc');
console.log(tscResults.message);
console.log(buildResults.message);
if (packageCopyResults.message.length !== 0) {
console.log(`Copied node_modules: ${(0, util_1.inspect)(packageCopyResults.message, false, 10, true)}`);
}
if (assetCopyResults.error) {
console.error(`Error copying assets: ${assetCopyResults.error}`);
}
return {
success: (buildResults === null || buildResults === void 0 ? void 0 : buildResults.success) &&
(tscResults === null || tscResults === void 0 ? void 0 : tscResults.success) &&
packageCopyResults.success &&
assetCopyResults.success,
outfile,
};
})));
}
exports.buildExecutor = buildExecutor;
function runBuild(options, watchDir) {
return new rxjs_1.Observable((subscriber) => {
const cwd = watchDir || options.absWorkingDir || process.cwd();
// We will use the org watch settings with node-watch for better refresh performance
const { watch: buildWatch } = options, opts = tslib_1.__rest(options, ["watch"]);
(0, esbuild_1.build)(opts)
.then((buildResult) => {
subscriber.next({ buildResult, buildFailure: null });
// Helper to send back data for watch events & supporting existing esbuild settings
const watchNext = ({ buildFailure, buildResult }) => {
subscriber.next({ buildFailure, buildResult });
if (typeof buildWatch === 'object' && buildWatch.onRebuild) {
buildWatch.onRebuild(buildFailure, buildResult);
}
};
// When in watch mode, it will continue to report back
if (buildWatch) {
(0, node_watch_1.default)(cwd, { recursive: true }, () => {
buildResult
.rebuild()
.then((watchResult) => {
watchNext({
buildFailure: null,
buildResult: watchResult,
});
})
.catch((watchFailure) => {
watchNext({
buildFailure: watchFailure,
buildResult: null,
});
});
});
}
else {
subscriber.complete();
}
})
.catch((buildFailure) => {
subscriber.next({ buildResult: null, buildFailure });
subscriber.complete();
});
});
}
function runTsc({ tsconfigPath, watch, root, useGlobal }) {
return new rxjs_1.Observable((subscriber) => {
// Build command
const modeModulesPath = useGlobal
? ''
: (root ? root + '/' : './') + 'node_modules/typescript/bin/';
const command = `${modeModulesPath}tsc`;
// Build arguments
const args = ['--noEmit']; // --noEmit so as to not save out data
if (watch) {
args.push('-w');
}
args.push('-p');
args.push(tsconfigPath);
let errorCount = 0;
// Run command
const child = (0, child_process_1.spawn)(command, args, { shell: true });
child.stdout.on('data', (data) => {
const decoded = data.toString();
// eslint-disable-next-line no-control-regex
if (decoded.match(/\x1Bc/g))
return;
if (decoded.includes('): error T')) {
errorCount++;
subscriber.next({ error: decoded });
}
else {
subscriber.next({ info: decoded });
}
});
child.stderr.on('error', (tscError) => {
subscriber.next({ tscError });
});
child.stdout.on('end', () => {
subscriber.next({
info: `Type check complete. Found ${errorCount} errors`,
});
});
});
}
function runCopyPackages(root, destination, external = []) {
return new rxjs_1.Observable((subscriber) => {
(0, walk_packages_1.getPackagesToCopy)(root, external)
.then((modules) => (0, walk_packages_1.copyPackages)(root, destination, modules))
.then((directories) => {
subscriber.next({
copyResult: directories !== null && directories !== void 0 ? directories : [],
success: true,
});
})
.catch((error) => {
subscriber.next({
copyResult: [],
success: false,
error,
});
});
});
}
function runCopyAssets(assets, root, destination) {
return new rxjs_1.Observable((subscriber) => {
(0, assets_1.copyAssets)(assets, root, destination)
.then((response) => {
subscriber.next(response);
})
.catch((error) => {
subscriber.next({
success: false,
error,
});
});
});
}
function bufferUntil(predicate) {
return function (source) {
const share$ = source.pipe((0, operators_1.share)());
const until$ = share$.pipe((0, operators_1.filter)(predicate), (0, operators_1.delay)(0));
return share$.pipe((0, operators_1.buffer)(until$));
};
}
exports.default = buildExecutor;
//# sourceMappingURL=build.impl.js.map