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; -}