"use strict"; /** * @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.io/license */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getStylesConfig = void 0; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const sass_service_1 = require("../../sass/sass-service"); const build_browser_features_1 = require("../../utils/build-browser-features"); const environment_options_1 = require("../../utils/environment-options"); const plugins_1 = require("../plugins"); const esbuild_executor_1 = require("../plugins/esbuild-executor"); const helpers_1 = require("../utils/helpers"); function resolveGlobalStyles(styleEntrypoints, root, preserveSymlinks) { const entryPoints = {}; const noInjectNames = []; const paths = []; if (styleEntrypoints.length === 0) { return { entryPoints, noInjectNames, paths }; } for (const style of helpers_1.normalizeExtraEntryPoints(styleEntrypoints, 'styles')) { let resolvedPath = path.resolve(root, style.input); if (!fs.existsSync(resolvedPath)) { try { resolvedPath = require.resolve(style.input, { paths: [root] }); } catch { } } if (!preserveSymlinks) { resolvedPath = fs.realpathSync(resolvedPath); } // Add style entry points. if (entryPoints[style.bundleName]) { entryPoints[style.bundleName].push(resolvedPath); } else { entryPoints[style.bundleName] = [resolvedPath]; } // Add non injected styles to the list. if (!style.inject) { noInjectNames.push(style.bundleName); } // Add global css paths. paths.push(resolvedPath); } return { entryPoints, noInjectNames, paths }; } // eslint-disable-next-line max-lines-per-function function getStylesConfig(wco) { var _a, _b, _c; const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const postcssImports = require('postcss-import'); const postcssPresetEnv = require('postcss-preset-env'); const { root, buildOptions } = wco; const extraPlugins = []; extraPlugins.push(new plugins_1.AnyComponentStyleBudgetChecker(buildOptions.budgets)); const cssSourceMap = buildOptions.sourceMap.styles; // Determine hashing format. const hashFormat = helpers_1.getOutputHashFormat(buildOptions.outputHashing); // use includePaths from appConfig const includePaths = (_c = (_b = (_a = buildOptions.stylePreprocessorOptions) === null || _a === void 0 ? void 0 : _a.includePaths) === null || _b === void 0 ? void 0 : _b.map((p) => path.resolve(root, p))) !== null && _c !== void 0 ? _c : []; // Process global styles. const { entryPoints, noInjectNames, paths: globalStylePaths, } = resolveGlobalStyles(buildOptions.styles, root, !!buildOptions.preserveSymlinks); if (noInjectNames.length > 0) { // Add plugin to remove hashes from lazy styles. extraPlugins.push(new plugins_1.RemoveHashPlugin({ chunkNames: noInjectNames, hashFormat })); } if (globalStylePaths.some((p) => p.endsWith('.styl'))) { wco.logger.warn('Stylus usage is deprecated and will be removed in a future major version. ' + 'To opt-out of the deprecated behaviour, please migrate to another stylesheet language.'); } let sassImplementation; try { sassImplementation = require('node-sass'); wco.logger.warn(`'node-sass' usage is deprecated and will be removed in a future major version. ` + `To opt-out of the deprecated behaviour and start using 'sass' uninstall 'node-sass'.`); } catch { sassImplementation = new sass_service_1.SassWorkerImplementation(); extraPlugins.push({ apply(compiler) { compiler.hooks.shutdown.tap('sass-worker', () => { sassImplementation === null || sassImplementation === void 0 ? void 0 : sassImplementation.close(); }); }, }); } const assetNameTemplate = helpers_1.assetNameTemplateFactory(hashFormat); const extraPostcssPlugins = []; // Attempt to setup Tailwind CSS // A configuration file can exist in the project or workspace root const tailwindConfigFile = 'tailwind.config.js'; let tailwindConfigPath; for (const basePath of [wco.projectRoot, wco.root]) { const fullPath = path.join(basePath, tailwindConfigFile); if (fs.existsSync(fullPath)) { tailwindConfigPath = fullPath; break; } } // 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. if (tailwindConfigPath) { let tailwindPackagePath; try { tailwindPackagePath = require.resolve('tailwindcss', { paths: [wco.root] }); } catch { const relativeTailwindConfigPath = path.relative(wco.root, tailwindConfigPath); wco.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) { if (process.env['TAILWIND_MODE'] === undefined) { process.env['TAILWIND_MODE'] = buildOptions.watch ? 'watch' : 'build'; } extraPostcssPlugins.push(require(tailwindPackagePath)({ config: tailwindConfigPath })); } } const { supportedBrowsers } = new build_browser_features_1.BuildBrowserFeatures(wco.projectRoot); const postcssPresetEnvPlugin = postcssPresetEnv({ browsers: supportedBrowsers, autoprefixer: true, stage: 3, }); const postcssOptionsCreator = (inlineSourcemaps, extracted) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const optionGenerator = (loader) => ({ map: inlineSourcemaps ? { inline: true, annotation: false, } : undefined, plugins: [ postcssImports({ resolve: (url) => (url.startsWith('~') ? url.substr(1) : url), load: (filename) => { return new Promise((resolve, reject) => { loader.fs.readFile(filename, (err, data) => { if (err) { reject(err); return; } const content = data.toString(); resolve(content); }); }); }, }), plugins_1.PostcssCliResources({ baseHref: buildOptions.baseHref, deployUrl: buildOptions.deployUrl, resourcesOutputPath: buildOptions.resourcesOutputPath, loader, filename: assetNameTemplate, emitFile: buildOptions.platform !== 'server', extracted, }), ...extraPostcssPlugins, postcssPresetEnvPlugin, ], }); // postcss-loader fails when trying to determine configuration files for data URIs optionGenerator.config = false; return optionGenerator; }; // load component css as raw strings const componentsSourceMap = !!(cssSourceMap && // Never use component css sourcemap when style optimizations are on. // It will just increase bundle size without offering good debug experience. !buildOptions.optimization.styles.minify && // Inline all sourcemap types except hidden ones, which are the same as no sourcemaps // for component css. !buildOptions.sourceMap.hidden); if (buildOptions.extractCss) { // extract global css from js files into own css file. extraPlugins.push(new MiniCssExtractPlugin({ 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: postCssLoaderPath, options: { implementation: postCss, postcssOptions: postcssOptionsCreator(componentsSourceMap, false), }, }, ]; const globalStyleLoaders = [ buildOptions.extractCss ? { loader: MiniCssExtractPlugin.loader, } : require.resolve('style-loader'), { loader: require.resolve('css-loader'), options: { url: false, sourceMap: !!cssSourceMap, }, }, { loader: postCssLoaderPath, options: { implementation: postCss, postcssOptions: postcssOptionsCreator(false, buildOptions.extractCss), sourceMap: !!cssSourceMap, }, }, ]; const extraMinimizers = []; if (buildOptions.optimization.styles.minify) { const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const esbuild = new esbuild_executor_1.EsbuildExecutor(); const cssnanoOptions = { preset: [ 'default', { // Disable SVG optimizations, as this can cause optimizations which are not compatible in all browsers. svgo: false, // Disable `calc` optimizations, due to several issues. #16910, #16875, #17890 calc: false, // Disable CSS rules sorted due to several issues #20693, https://github.com/ionic-team/ionic-framework/issues/23266 and https://github.com/cssnano/cssnano/issues/1054 cssDeclarationSorter: false, // Workaround for Critters as it doesn't work when `@media all {}` is minified to `@media {}`. // TODO: Remove once they move to postcss. minifyParams: !buildOptions.optimization.styles.inlineCritical, }, ], }; const globalBundlesRegExp = new RegExp(`^(${Object.keys(entryPoints).join('|')})(\.[0-9a-f]{20})?.css$`); const target = transformSupportedBrowsersToTargets(supportedBrowsers); extraMinimizers.push( // Component styles use esbuild which is faster and generates smaller files on average. // esbuild does not yet support style sourcemaps but component style sourcemaps are not // supported by the CLI when style minify is enabled. new CssMinimizerPlugin({ // Component styles retain their original file name test: /\.(?:css|scss|sass|less|styl)$/, exclude: globalBundlesRegExp, parallel: false, minify: async (data) => { const [[sourcefile, input]] = Object.entries(data); const { code, warnings } = await esbuild.transform(input, { loader: 'css', minify: true, sourcefile, target, }); return { code, warnings: warnings.length > 0 ? await esbuild.formatMessages(warnings, { kind: 'warning' }) : [], }; }, }), // Global styles use cssnano since sourcemap support is required even when minify // is enabled. Once esbuild supports style sourcemaps this can be changed. // esbuild stylesheet source map support issue: https://github.com/evanw/esbuild/issues/519 new CssMinimizerPlugin({ test: /\.css$/, include: globalBundlesRegExp, parallel: environment_options_1.maxWorkers, minify: [CssMinimizerPlugin.cssnanoMinify], minimizerOptions: cssnanoOptions, })); } const styleLanguages = [ { extensions: ['css'], use: [], }, { extensions: ['scss'], use: [ { loader: require.resolve('resolve-url-loader'), options: { sourceMap: cssSourceMap, }, }, { loader: require.resolve('sass-loader'), options: { implementation: sassImplementation, sourceMap: true, sassOptions: { // Prevent use of `fibers` package as it no longer works in newer Node.js versions fiber: false, // bootstrap-sass requires a minimum precision of 8 precision: 8, 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 outputStyle: 'expanded', // Silences compiler warnings from 3rd party stylesheets quietDeps: !buildOptions.verbose, verbose: buildOptions.verbose, }, }, }, ], }, { extensions: ['sass'], use: [ { loader: require.resolve('resolve-url-loader'), options: { sourceMap: cssSourceMap, }, }, { loader: require.resolve('sass-loader'), options: { implementation: sassImplementation, sourceMap: true, sassOptions: { // Prevent use of `fibers` package as it no longer works in newer Node.js versions fiber: false, indentedSyntax: true, // bootstrap-sass requires a minimum precision of 8 precision: 8, 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 outputStyle: 'expanded', // Silences compiler warnings from 3rd party stylesheets quietDeps: !buildOptions.verbose, verbose: buildOptions.verbose, }, }, }, ], }, { extensions: ['less'], use: [ { loader: require.resolve('less-loader'), options: { implementation: require('less'), sourceMap: cssSourceMap, lessOptions: { javascriptEnabled: true, paths: includePaths, }, }, }, ], }, { extensions: ['styl'], use: [ { loader: require.resolve('stylus-loader'), options: { sourceMap: cssSourceMap, stylusOptions: { compress: false, sourceMap: { comment: false }, paths: includePaths, }, }, }, ], }, ]; return { entry: entryPoints, module: { rules: styleLanguages.map(({ extensions, use }) => ({ test: new RegExp(`\\.(?:${extensions.join('|')})$`, 'i'), rules: [ // Setup processing rules for global and component styles { oneOf: [ // Component styles are all styles except defined global styles { exclude: globalStylePaths, use: componentStyleLoaders, type: 'asset/source', }, // Global styles are only defined global styles { include: globalStylePaths, use: globalStyleLoaders, }, ], }, { use }, ], })), }, optimization: { minimizer: extraMinimizers, }, plugins: extraPlugins, }; } exports.getStylesConfig = getStylesConfig; function transformSupportedBrowsersToTargets(supportedBrowsers) { const transformed = []; // https://esbuild.github.io/api/#target const esBuildSupportedBrowsers = new Set(['safari', 'firefox', 'edge', 'chrome', 'ios']); for (const browser of supportedBrowsers) { let [browserName, version] = browser.split(' '); // browserslist uses the name `ios_saf` for iOS Safari whereas esbuild uses `ios` if (browserName === 'ios_saf') { browserName = 'ios'; // browserslist also uses ranges for iOS Safari versions but only the lowest is required // to perform minimum supported feature checks. esbuild also expects a single version. [version] = version.split('-'); } if (browserName === 'ie') { transformed.push('edge12'); } else if (esBuildSupportedBrowsers.has(browserName)) { if (browserName === 'safari' && version === 'TP') { // esbuild only supports numeric versions so `TP` is converted to a high number (999) since // a Technology Preview (TP) of Safari is assumed to support all currently known features. version = '999'; } transformed.push(browserName + version); } } return transformed.length ? transformed : undefined; }