@angular-devkit/build-angular
Version:
Angular Webpack Build Facade
369 lines (368 loc) • 15.4 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.getStylesConfig = getStylesConfig;
const private_1 = require("@angular/build/private");
const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin"));
const path = __importStar(require("node:path"));
const node_url_1 = require("node:url");
const tailwind_1 = require("../../../utils/tailwind");
const plugins_1 = require("../plugins");
const css_optimizer_plugin_1 = require("../plugins/css-optimizer-plugin");
const styles_webpack_plugin_1 = require("../plugins/styles-webpack-plugin");
const helpers_1 = require("../utils/helpers");
// eslint-disable-next-line max-lines-per-function
async function getStylesConfig(wco) {
const { root, buildOptions, logger, projectRoot } = wco;
const extraPlugins = [];
extraPlugins.push(new plugins_1.AnyComponentStyleBudgetChecker(buildOptions.budgets));
const cssSourceMap = buildOptions.sourceMap.styles;
// Determine hashing format.
const hashFormat = (0, helpers_1.getOutputHashFormat)(buildOptions.outputHashing);
// use includePaths from appConfig
const includePaths = buildOptions.stylePreprocessorOptions?.includePaths?.map((p) => path.resolve(root, p)) ?? [];
// Process global styles.
if (buildOptions.styles.length > 0) {
const { entryPoints, noInjectNames } = (0, helpers_1.normalizeGlobalStyles)(buildOptions.styles);
extraPlugins.push(new styles_webpack_plugin_1.StylesWebpackPlugin({
root,
entryPoints,
preserveSymlinks: buildOptions.preserveSymlinks,
}));
if (noInjectNames.length > 0) {
// Add plugin to remove hashes from lazy styles.
extraPlugins.push(new plugins_1.RemoveHashPlugin({ chunkNames: noInjectNames, hashFormat }));
}
}
const sassImplementation = new private_1.SassWorkerImplementation();
extraPlugins.push({
apply(compiler) {
compiler.hooks.shutdown.tap('sass-worker', () => {
void sassImplementation.close();
});
},
});
const assetNameTemplate = (0, helpers_1.assetNameTemplateFactory)(hashFormat);
const extraPostcssPlugins = [];
// Attempt to setup Tailwind CSS
// Only load Tailwind CSS plugin if configuration file was found.
// This acts as a guard to ensure the project actually wants to use Tailwind CSS.
// The package may be unknowningly present due to a third-party transitive package dependency.
const tailwindConfigPath = await (0, tailwind_1.findTailwindConfigurationFile)(root, projectRoot);
if (tailwindConfigPath) {
let tailwindPackagePath;
try {
tailwindPackagePath = require.resolve('tailwindcss', { paths: [root] });
}
catch {
const relativeTailwindConfigPath = path.relative(root, tailwindConfigPath);
logger.warn(`Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
` but the 'tailwindcss' package is not installed.` +
` To enable Tailwind CSS, please install the 'tailwindcss' package.`);
}
if (tailwindPackagePath) {
extraPostcssPlugins.push(require(tailwindPackagePath)({ config: tailwindConfigPath }));
}
}
const autoprefixer = require('autoprefixer');
const postcssOptionsCreator = (inlineSourcemaps, extracted) => {
const optionGenerator = (loader) => ({
map: inlineSourcemaps
? {
inline: true,
annotation: false,
}
: undefined,
plugins: [
(0, plugins_1.PostcssCliResources)({
baseHref: buildOptions.baseHref,
deployUrl: buildOptions.deployUrl,
resourcesOutputPath: buildOptions.resourcesOutputPath,
loader,
filename: assetNameTemplate,
emitFile: buildOptions.platform !== 'server',
extracted,
}),
...extraPostcssPlugins,
autoprefixer({
ignoreUnknownVersions: true,
overrideBrowserslist: buildOptions.supportedBrowsers,
}),
],
});
// postcss-loader fails when trying to determine configuration files for data URIs
optionGenerator.config = false;
return optionGenerator;
};
let componentsSourceMap = !!cssSourceMap;
if (cssSourceMap) {
if (buildOptions.optimization.styles.minify) {
// Never use component css sourcemap when style optimizations are on.
// It will just increase bundle size without offering good debug experience.
logger.warn('Components styles sourcemaps are not generated when styles optimization is enabled.');
componentsSourceMap = false;
}
else if (buildOptions.sourceMap.hidden) {
// Inline all sourcemap types except hidden ones, which are the same as no sourcemaps
// for component css.
logger.warn('Components styles sourcemaps are not generated when sourcemaps are hidden.');
componentsSourceMap = false;
}
}
// extract global css from js files into own css file.
extraPlugins.push(new mini_css_extract_plugin_1.default({ filename: `[name]${hashFormat.extract}.css` }));
if (!buildOptions.hmr) {
// don't remove `.js` files for `.css` when we are using HMR these contain HMR accept codes.
// suppress empty .js files in css only entry points.
extraPlugins.push(new plugins_1.SuppressExtractedTextChunksWebpackPlugin());
}
const postCss = require('postcss');
const postCssLoaderPath = require.resolve('postcss-loader');
const componentStyleLoaders = [
{
loader: require.resolve('css-loader'),
options: {
url: false,
sourceMap: componentsSourceMap,
importLoaders: 1,
exportType: 'string',
esModule: false,
},
},
{
loader: postCssLoaderPath,
options: {
implementation: postCss,
postcssOptions: postcssOptionsCreator(componentsSourceMap, false),
},
},
];
const globalStyleLoaders = [
{
loader: mini_css_extract_plugin_1.default.loader,
},
{
loader: require.resolve('css-loader'),
options: {
url: false,
sourceMap: !!cssSourceMap,
importLoaders: 1,
},
},
{
loader: postCssLoaderPath,
options: {
implementation: postCss,
postcssOptions: postcssOptionsCreator(false, true),
sourceMap: !!cssSourceMap,
},
},
];
const styleLanguages = [
{
extensions: ['css'],
use: [],
},
{
extensions: ['scss'],
use: [
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: cssSourceMap,
},
},
{
loader: require.resolve('sass-loader'),
options: getSassLoaderOptions(root, sassImplementation, includePaths, false, !!buildOptions.verbose, !!buildOptions.preserveSymlinks),
},
],
},
{
extensions: ['sass'],
use: [
{
loader: require.resolve('resolve-url-loader'),
options: {
sourceMap: cssSourceMap,
},
},
{
loader: require.resolve('sass-loader'),
options: getSassLoaderOptions(root, sassImplementation, includePaths, true, !!buildOptions.verbose, !!buildOptions.preserveSymlinks),
},
],
},
{
extensions: ['less'],
use: [
{
loader: require.resolve('less-loader'),
options: {
implementation: require('less'),
sourceMap: cssSourceMap,
lessOptions: {
javascriptEnabled: true,
paths: includePaths,
},
},
},
],
},
];
return {
module: {
rules: styleLanguages.map(({ extensions, use }) => ({
test: new RegExp(`\\.(?:${extensions.join('|')})$`, 'i'),
rules: [
// Setup processing rules for global and component styles
{
oneOf: [
// Global styles are only defined global styles
{
use: globalStyleLoaders,
resourceQuery: /\?ngGlobalStyle/,
},
// Component styles are all styles except defined global styles
{
use: componentStyleLoaders,
resourceQuery: /\?ngResource/,
},
],
},
{ use },
],
})),
},
optimization: {
minimizer: buildOptions.optimization.styles.minify
? [
new css_optimizer_plugin_1.CssOptimizerPlugin({
supportedBrowsers: buildOptions.supportedBrowsers,
}),
]
: undefined,
},
plugins: extraPlugins,
};
}
function getSassLoaderOptions(root, implementation, includePaths, indentedSyntax, verbose, preserveSymlinks) {
return {
sourceMap: true,
api: 'modern',
implementation,
// Webpack importer is only implemented in the legacy API and we have our own custom Webpack importer.
// See: https://github.com/webpack-contrib/sass-loader/blob/997f3eb41d86dd00d5fa49c395a1aeb41573108c/src/utils.js#L642-L651
webpackImporter: false,
sassOptions: (loaderContext) => ({
importers: [getSassResolutionImporter(loaderContext, root, preserveSymlinks)],
loadPaths: includePaths,
// Use expanded as otherwise sass will remove comments that are needed for autoprefixer
// Ex: /* autoprefixer grid: autoplace */
// See: https://github.com/webpack-contrib/sass-loader/blob/45ad0be17264ceada5f0b4fb87e9357abe85c4ff/src/getSassOptions.js#L68-L70
style: 'expanded',
// Silences compiler warnings from 3rd party stylesheets
quietDeps: !verbose,
verbose,
syntax: indentedSyntax ? 'indented' : 'scss',
sourceMapIncludeSources: true,
}),
};
}
function getSassResolutionImporter(loaderContext, root, preserveSymlinks) {
const commonResolverOptions = {
conditionNames: ['sass', 'style'],
mainFields: ['sass', 'style', 'main', '...'],
extensions: ['.scss', '.sass', '.css'],
restrictions: [/\.((sa|sc|c)ss)$/i],
preferRelative: true,
symlinks: !preserveSymlinks,
};
// Sass also supports import-only files. If you name a file <name>.import.scss, it will only be loaded for imports, not for @uses.
// See: https://sass-lang.com/documentation/at-rules/import#import-only-files
const resolveImport = loaderContext.getResolve({
...commonResolverOptions,
dependencyType: 'sass-import',
mainFiles: ['_index.import', '_index', 'index.import', 'index', '...'],
});
const resolveModule = loaderContext.getResolve({
...commonResolverOptions,
dependencyType: 'sass-module',
mainFiles: ['_index', 'index', '...'],
});
return {
findFileUrl: async (url, { fromImport, containingUrl }) => {
if (url.charAt(0) === '.') {
// Let Sass handle relative imports.
return null;
}
let resolveDir = root;
if (containingUrl) {
resolveDir = path.dirname((0, node_url_1.fileURLToPath)(containingUrl));
}
const resolve = fromImport ? resolveImport : resolveModule;
// Try to resolve from root of workspace
const result = await tryResolve(resolve, resolveDir, url);
return result ? (0, node_url_1.pathToFileURL)(result) : null;
},
};
}
async function tryResolve(resolve, root, url) {
try {
return await resolve(root, url);
}
catch {
// Try to resolve a partial file
// @use '@material/button/button' as mdc-button;
// `@material/button/button` -> `@material/button/_button`
const lastSlashIndex = url.lastIndexOf('/');
const underscoreIndex = lastSlashIndex + 1;
if (underscoreIndex > 0 && url.charAt(underscoreIndex) !== '_') {
const partialFileUrl = `${url.slice(0, underscoreIndex)}_${url.slice(underscoreIndex)}`;
return resolve(root, partialFileUrl).catch(() => undefined);
}
}
return undefined;
}
;