214 lines
6.2 KiB
TypeScript
214 lines
6.2 KiB
TypeScript
import type { ExecutorContext } from '@nrwl/devkit';
|
|
import type { Configuration, Stats } from 'webpack';
|
|
import { from, of } from 'rxjs';
|
|
import { bufferCount, mergeScan, switchMap, tap } from 'rxjs/operators';
|
|
import { eachValueFrom } from 'rxjs-for-await';
|
|
import { execSync } from 'child_process';
|
|
import { Range, satisfies } from 'semver';
|
|
import { basename, join } from 'path';
|
|
|
|
import { readCachedProjectGraph } from '@nrwl/workspace/src/core/project-graph';
|
|
import {
|
|
calculateProjectDependencies,
|
|
checkDependentProjectsHaveBeenBuilt,
|
|
createTmpTsConfig,
|
|
} from '@nrwl/workspace/src/utilities/buildable-libs-utils';
|
|
import { readTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
|
|
|
import { normalizeWebBuildOptions } from '../../utils/normalize';
|
|
import { getWebConfig } from '../../utils/web.config';
|
|
import type { BuildBuilderOptions } from '../../utils/shared-models';
|
|
import { ExtraEntryPoint } from '../../utils/shared-models';
|
|
import { getEmittedFiles, runWebpack } from '../../utils/run-webpack';
|
|
import { BuildBrowserFeatures } from '../../utils/webpack/build-browser-features';
|
|
import { deleteOutputDir } from '../../utils/fs';
|
|
import {
|
|
CrossOriginValue,
|
|
writeIndexHtml,
|
|
} from '../../utils/webpack/write-index-html';
|
|
|
|
export interface WebWebpackExecutorOptions extends BuildBuilderOptions {
|
|
index: string;
|
|
budgets?: any[];
|
|
baseHref?: string;
|
|
deployUrl?: string;
|
|
|
|
crossOrigin?: CrossOriginValue;
|
|
|
|
polyfills?: string;
|
|
es2015Polyfills?: string;
|
|
|
|
scripts: ExtraEntryPoint[];
|
|
styles: ExtraEntryPoint[];
|
|
|
|
vendorChunk?: boolean;
|
|
commonChunk?: boolean;
|
|
|
|
namedChunks?: boolean;
|
|
|
|
stylePreprocessorOptions?: any;
|
|
subresourceIntegrity?: boolean;
|
|
|
|
verbose?: boolean;
|
|
buildLibsFromSource?: boolean;
|
|
|
|
deleteOutputPath?: boolean;
|
|
|
|
generateIndexHtml?: boolean;
|
|
}
|
|
|
|
function getWebpackConfigs(
|
|
options: WebWebpackExecutorOptions,
|
|
context: ExecutorContext
|
|
): Configuration[] {
|
|
const metadata = context.workspace.projects[context.projectName];
|
|
const sourceRoot = metadata.sourceRoot;
|
|
const projectRoot = metadata.root;
|
|
options = normalizeWebBuildOptions(options, context.root, sourceRoot);
|
|
const isScriptOptimizeOn =
|
|
typeof options.optimization === 'boolean'
|
|
? options.optimization
|
|
: options.optimization && options.optimization.scripts
|
|
? options.optimization.scripts
|
|
: false;
|
|
const tsConfig = readTsConfig(options.tsConfig);
|
|
const scriptTarget = tsConfig.options.target;
|
|
|
|
const buildBrowserFeatures = new BuildBrowserFeatures(
|
|
projectRoot,
|
|
scriptTarget
|
|
);
|
|
|
|
return [
|
|
// ESM build for modern browsers.
|
|
getWebConfig(
|
|
context.root,
|
|
projectRoot,
|
|
sourceRoot,
|
|
options,
|
|
true,
|
|
isScriptOptimizeOn,
|
|
context.configurationName
|
|
),
|
|
// ES5 build for legacy browsers.
|
|
isScriptOptimizeOn && buildBrowserFeatures.isDifferentialLoadingNeeded()
|
|
? getWebConfig(
|
|
context.root,
|
|
projectRoot,
|
|
sourceRoot,
|
|
options,
|
|
false,
|
|
isScriptOptimizeOn,
|
|
context.configurationName
|
|
)
|
|
: undefined,
|
|
]
|
|
.filter(Boolean)
|
|
.map((config) =>
|
|
options.webpackConfig
|
|
? require(options.webpackConfig)(config, {
|
|
options,
|
|
configuration: context.configurationName,
|
|
})
|
|
: config
|
|
);
|
|
}
|
|
|
|
export async function* run(
|
|
options: WebWebpackExecutorOptions,
|
|
context: ExecutorContext
|
|
) {
|
|
// Node versions 12.2-12.8 has a bug where prod builds will hang for 2-3 minutes
|
|
// after the program exits.
|
|
const nodeVersion = execSync(`node --version`).toString('utf-8').trim();
|
|
const supportedRange = new Range('10 || >=12.9');
|
|
if (!satisfies(nodeVersion, supportedRange)) {
|
|
throw new Error(
|
|
`Node version ${nodeVersion} is not supported. Supported range is "${supportedRange.raw}".`
|
|
);
|
|
}
|
|
|
|
process.env.NODE_ENV ||= 'production';
|
|
|
|
const metadata = context.workspace.projects[context.projectName];
|
|
|
|
if (!options.buildLibsFromSource && context.targetName) {
|
|
const { dependencies } = calculateProjectDependencies(
|
|
readCachedProjectGraph(),
|
|
context.root,
|
|
context.projectName,
|
|
context.targetName,
|
|
context.configurationName
|
|
);
|
|
options.tsConfig = createTmpTsConfig(
|
|
join(context.root, options.tsConfig),
|
|
context.root,
|
|
metadata.root,
|
|
dependencies
|
|
);
|
|
|
|
if (
|
|
!checkDependentProjectsHaveBeenBuilt(
|
|
context.root,
|
|
context.projectName,
|
|
context.targetName,
|
|
dependencies
|
|
)
|
|
) {
|
|
throw new Error();
|
|
}
|
|
}
|
|
|
|
// Delete output path before bundling
|
|
if (options.deleteOutputPath) {
|
|
deleteOutputDir(context.root, options.outputPath);
|
|
}
|
|
|
|
const configs = getWebpackConfigs(options, context);
|
|
return yield* eachValueFrom(
|
|
from(configs).pipe(
|
|
// Run build sequentially and bail when first one fails.
|
|
mergeScan(
|
|
(acc, config) => {
|
|
if (!acc.hasErrors()) {
|
|
return runWebpack(config).pipe(
|
|
tap((stats) => {
|
|
console.info(stats.toString(config.stats));
|
|
})
|
|
);
|
|
} else {
|
|
return of();
|
|
}
|
|
},
|
|
{ hasErrors: () => false } as Stats,
|
|
1
|
|
),
|
|
// Collect build results as an array.
|
|
bufferCount(configs.length),
|
|
switchMap(async ([result1, result2]) => {
|
|
const success =
|
|
result1 && !result1.hasErrors() && (!result2 || !result2.hasErrors());
|
|
const emittedFiles1 = getEmittedFiles(result1);
|
|
const emittedFiles2 = result2 ? getEmittedFiles(result2) : [];
|
|
if (options.generateIndexHtml) {
|
|
await writeIndexHtml({
|
|
crossOrigin: options.crossOrigin,
|
|
outputPath: join(options.outputPath, basename(options.index)),
|
|
indexPath: join(context.root, options.index),
|
|
files: emittedFiles1.filter((x) => x.extension === '.css'),
|
|
noModuleFiles: emittedFiles2,
|
|
moduleFiles: emittedFiles1,
|
|
baseHref: options.baseHref,
|
|
deployUrl: options.deployUrl,
|
|
scripts: options.scripts,
|
|
styles: options.styles,
|
|
});
|
|
}
|
|
return { success, emittedFiles: [...emittedFiles1, ...emittedFiles2] };
|
|
})
|
|
)
|
|
);
|
|
}
|
|
|
|
export default run;
|