@angular-devkit/build-angular
Version:
Angular Webpack Build Facade
304 lines (303 loc) • 11.6 kB
JavaScript
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getOutputHashFormat = getOutputHashFormat;
exports.normalizeExtraEntryPoints = normalizeExtraEntryPoints;
exports.assetNameTemplateFactory = assetNameTemplateFactory;
exports.getInstrumentationExcludedPaths = getInstrumentationExcludedPaths;
exports.normalizeGlobalStyles = normalizeGlobalStyles;
exports.getCacheSettings = getCacheSettings;
exports.globalScriptsByBundleName = globalScriptsByBundleName;
exports.assetPatterns = assetPatterns;
exports.getStatsOptions = getStatsOptions;
exports.isPackageInstalled = isPackageInstalled;
const fast_glob_1 = __importDefault(require("fast-glob"));
const node_crypto_1 = require("node:crypto");
const path = __importStar(require("node:path"));
const schema_1 = require("../../../builders/browser/schema");
const package_version_1 = require("../../../utils/package-version");
function getOutputHashFormat(outputHashing = schema_1.OutputHashing.None, length = 20) {
const hashTemplate = `.[contenthash:${length}]`;
switch (outputHashing) {
case 'media':
return {
chunk: '',
extract: '',
file: hashTemplate,
script: '',
};
case 'bundles':
return {
chunk: hashTemplate,
extract: hashTemplate,
file: '',
script: hashTemplate,
};
case 'all':
return {
chunk: hashTemplate,
extract: hashTemplate,
file: hashTemplate,
script: hashTemplate,
};
case 'none':
default:
return {
chunk: '',
extract: '',
file: '',
script: '',
};
}
}
function normalizeExtraEntryPoints(extraEntryPoints, defaultBundleName) {
return extraEntryPoints.map((entry) => {
if (typeof entry === 'string') {
return { input: entry, inject: true, bundleName: defaultBundleName };
}
const { inject = true, ...newEntry } = entry;
let bundleName;
if (entry.bundleName) {
bundleName = entry.bundleName;
}
else if (!inject) {
// Lazy entry points use the file name as bundle name.
bundleName = path.parse(entry.input).name;
}
else {
bundleName = defaultBundleName;
}
return { ...newEntry, inject, bundleName };
});
}
function assetNameTemplateFactory(hashFormat) {
const visitedFiles = new Map();
return (resourcePath) => {
if (hashFormat.file) {
// File names are hashed therefore we don't need to handle files with the same file name.
return `[name]${hashFormat.file}.[ext]`;
}
const filename = path.basename(resourcePath);
// Check if the file with the same name has already been processed.
const visited = visitedFiles.get(filename);
if (!visited) {
// Not visited.
visitedFiles.set(filename, resourcePath);
return filename;
}
else if (visited === resourcePath) {
// Same file.
return filename;
}
// File has the same name but it's in a different location.
return '[path][name].[ext]';
};
}
function getInstrumentationExcludedPaths(root, excludedPaths) {
const excluded = new Set();
for (const excludeGlob of excludedPaths) {
const excludePath = excludeGlob[0] === '/' ? excludeGlob.slice(1) : excludeGlob;
fast_glob_1.default.sync(excludePath, { cwd: root }).forEach((p) => excluded.add(path.join(root, p)));
}
return excluded;
}
function normalizeGlobalStyles(styleEntrypoints) {
const entryPoints = {};
const noInjectNames = [];
if (styleEntrypoints.length === 0) {
return { entryPoints, noInjectNames };
}
for (const style of normalizeExtraEntryPoints(styleEntrypoints, 'styles')) {
// Add style entry points.
entryPoints[style.bundleName] ??= [];
entryPoints[style.bundleName].push(style.input);
// Add non injected styles to the list.
if (!style.inject) {
noInjectNames.push(style.bundleName);
}
}
return { entryPoints, noInjectNames };
}
function getCacheSettings(wco, angularVersion) {
const { enabled, path: cacheDirectory } = wco.buildOptions.cache;
if (enabled) {
return {
type: 'filesystem',
profile: wco.buildOptions.verbose,
cacheDirectory: path.join(cacheDirectory, 'angular-webpack'),
maxMemoryGenerations: 1,
// We use the versions and build options as the cache name. The Webpack configurations are too
// dynamic and shared among different build types: test, build and serve.
// None of which are "named".
name: (0, node_crypto_1.createHash)('sha1')
.update(angularVersion)
.update(package_version_1.VERSION)
.update(wco.projectRoot)
.update(JSON.stringify(wco.tsConfig))
.update(JSON.stringify({
...wco.buildOptions,
// Needed because outputPath changes on every build when using i18n extraction
// https://github.com/angular/angular-cli/blob/736a5f89deaca85f487b78aec9ff66d4118ceb6a/packages/angular_devkit/build_angular/src/utils/i18n-options.ts#L264-L265
outputPath: undefined,
}))
.digest('hex'),
};
}
if (wco.buildOptions.watch) {
return {
type: 'memory',
maxGenerations: 1,
};
}
return false;
}
function globalScriptsByBundleName(scripts) {
return normalizeExtraEntryPoints(scripts, 'scripts').reduce((prev, curr) => {
const { bundleName, inject, input } = curr;
const existingEntry = prev.find((el) => el.bundleName === bundleName);
if (existingEntry) {
if (existingEntry.inject && !inject) {
// All entries have to be lazy for the bundle to be lazy.
throw new Error(`The ${bundleName} bundle is mixing injected and non-injected scripts.`);
}
existingEntry.paths.push(input);
}
else {
prev.push({
bundleName,
inject,
paths: [input],
});
}
return prev;
}, []);
}
function assetPatterns(root, assets) {
return assets.map((asset, index) => {
// Resolve input paths relative to workspace root and add slash at the end.
// eslint-disable-next-line prefer-const
let { input, output = '', ignore = [], glob } = asset;
input = path.resolve(root, input).replace(/\\/g, '/');
input = input.endsWith('/') ? input : input + '/';
output = output.endsWith('/') ? output : output + '/';
if (output.startsWith('..')) {
throw new Error('An asset cannot be written to a location outside of the output path.');
}
return {
context: input,
// Now we remove starting slash to make Webpack place it from the output root.
to: output.replace(/^\//, ''),
from: glob,
noErrorOnMissing: true,
force: true,
globOptions: {
dot: true,
followSymbolicLinks: !!asset.followSymlinks,
ignore: [
'.gitkeep',
'**/.DS_Store',
'**/Thumbs.db',
// Negate patterns needs to be absolute because copy-webpack-plugin uses absolute globs which
// causes negate patterns not to match.
// See: https://github.com/webpack-contrib/copy-webpack-plugin/issues/498#issuecomment-639327909
...ignore,
].map((i) => path.posix.join(input, i)),
},
priority: index,
};
});
}
function getStatsOptions(verbose = false) {
const webpackOutputOptions = {
all: false, // Fallback value for stats options when an option is not defined. It has precedence over local webpack defaults.
colors: true,
hash: true, // required by custom stat output
timings: true, // required by custom stat output
chunks: true, // required by custom stat output
builtAt: true, // required by custom stat output
warnings: true,
errors: true,
assets: true, // required by custom stat output
cachedAssets: true, // required for bundle size calculators
// Needed for markAsyncChunksNonInitial.
ids: true,
entrypoints: true,
};
const verboseWebpackOutputOptions = {
// The verbose output will most likely be piped to a file, so colors just mess it up.
colors: false,
usedExports: true,
optimizationBailout: true,
reasons: true,
children: true,
assets: true,
version: true,
chunkModules: true,
errorDetails: true,
errorStack: true,
moduleTrace: true,
logging: 'verbose',
modulesSpace: Infinity,
};
return verbose
? { ...webpackOutputOptions, ...verboseWebpackOutputOptions }
: webpackOutputOptions;
}
/**
* Checks if a specified package is installed in the given workspace.
*
* @param root - The root directory of the workspace.
* @param name - The name of the package to check for.
* @returns `true` if the package is installed, `false` otherwise.
*/
function isPackageInstalled(root, name) {
try {
require.resolve(name, { paths: [root] });
return true;
}
catch {
return false;
}
}
;