From e0856d8c8939ed1330b161268b04c63bf8fa21ea Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 24 Jun 2025 12:26:09 -0400 Subject: [PATCH] feat(@angular-devkit/build-angular): provide partial custom postcss configuration support The `browser` builder now supports providing a custom postcss configuration similar to the functionality available in the `@angular/build:application` builder. A postcss configuration file named either `postcss.config.json` or `.postcssrc.json` will be discovered in order from the project root or workspace root. Only plugins can be configured via the file. All other fields will be ignored. Additionally the plugins will be placed after any internal build system plugins but before `autoprefixer`. When using a custom postcss configuration, the automatic tailwind integration will be disabled. The Tailwind postcss plugin should be added to the configuration file in this scenario. Co-Authored-By: Jan Martin --- packages/angular/build/src/private.ts | 5 ++ .../tests/behavior/postcss_config_spec.ts | 57 ++++++++++++++++++ .../src/tools/webpack/configs/styles.ts | 59 ++++++++++++------- .../build_angular/src/utils/tailwind.ts | 40 ------------- 4 files changed, 101 insertions(+), 60 deletions(-) create mode 100644 packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/postcss_config_spec.ts delete mode 100644 packages/angular_devkit/build_angular/src/utils/tailwind.ts diff --git a/packages/angular/build/src/private.ts b/packages/angular/build/src/private.ts index 25eb48f22a86..2e2691b02485 100644 --- a/packages/angular/build/src/private.ts +++ b/packages/angular/build/src/private.ts @@ -84,3 +84,8 @@ export { type BundleStats, generateBuildStatsTable } from './utils/stats-table'; export { getSupportedBrowsers } from './utils/supported-browsers'; export { assertCompatibleAngularVersion } from './utils/version'; export { findTests, getTestEntrypoints } from './builders/karma/find-tests'; +export { + findTailwindConfiguration, + generateSearchDirectories, + loadPostcssConfiguration, +} from './utils/postcss-configuration'; diff --git a/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/postcss_config_spec.ts b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/postcss_config_spec.ts new file mode 100644 index 000000000000..0ed245f18b44 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/postcss_config_spec.ts @@ -0,0 +1,57 @@ +/** + * @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 + */ + +import { buildWebpackBrowser } from '../../index'; +import { BASE_OPTIONS, BROWSER_BUILDER_INFO, describeBuilder } from '../setup'; + +describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => { + describe('Behavior: "Postcss config file"', () => { + it('applies plugins from config file if one is present', async () => { + // See: + // https://github.com/postcss/postcss-plugin-boilerplate/blob/main/template/index.t.js + await harness.writeFile( + 'node_modules/my-postcss-plugin/index.cjs', + `module.exports = (opts = {}) => { + return { + postcssPlugin: 'my-postcss-plugin', + + Root(root, postcss) { + const newRule = new postcss.Rule({ + selector: '.my-postcss-plugin::before', + }); + newRule.append({ text: 'content: "from applied plugin";' }); + root.append(newRule); + }, + }; +}; + +module.exports.postcss = true; +`, + ); + + await harness.writeFile( + 'postcss.config.json', + JSON.stringify({ + plugins: { + 'my-postcss-plugin/index.cjs': {}, + }, + }), + ); + + harness.useTarget('build', { + ...BASE_OPTIONS, + styles: ['src/styles.css'], + }); + + const { result } = await harness.executeOnce(); + + expect(result?.success).toBe(true); + harness.expectFile('dist/styles.css').content.toMatch(/from applied plugin/); + }); + }); +}); diff --git a/packages/angular_devkit/build_angular/src/tools/webpack/configs/styles.ts b/packages/angular_devkit/build_angular/src/tools/webpack/configs/styles.ts index 5d92b574502f..c0216ab65069 100644 --- a/packages/angular_devkit/build_angular/src/tools/webpack/configs/styles.ts +++ b/packages/angular_devkit/build_angular/src/tools/webpack/configs/styles.ts @@ -6,14 +6,18 @@ * found in the LICENSE file at https://angular.dev/license */ -import { SassWorkerImplementation } from '@angular/build/private'; +import { + SassWorkerImplementation, + findTailwindConfiguration, + generateSearchDirectories, + loadPostcssConfiguration, +} from '@angular/build/private'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import * as path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import type { FileImporter } from 'sass'; import type { Configuration, LoaderContext, RuleSetUseItem } from 'webpack'; import { WebpackConfigOptions } from '../../../utils/build-options'; -import { findTailwindConfigurationFile } from '../../../utils/tailwind'; import { AnyComponentStyleBudgetChecker, PostcssCliResources, @@ -75,25 +79,40 @@ export async function getStylesConfig(wco: WebpackConfigOptions): Promise { - const dirEntries = [projectRoot, workspaceRoot].map((root) => - readdir(root, { withFileTypes: false }).then((entries) => ({ - root, - files: new Set(entries), - })), - ); - - // A configuration file can exist in the project or workspace root - for (const { root, files } of await Promise.all(dirEntries)) { - for (const potentialConfig of tailwindConfigFiles) { - if (files.has(potentialConfig)) { - return join(root, potentialConfig); - } - } - } - - return undefined; -}