From c1493782d0480200659a79edfc388681e0cf373d Mon Sep 17 00:00:00 2001 From: vltansky Date: Thu, 11 Feb 2021 23:49:18 +0200 Subject: [PATCH] feat(@angular-devkit/build-angular): tailwindcss purge --- .../src/webpack/configs/styles.ts | 22 +++++- .../e2e/tests/build/styles/tailwind.ts | 72 ++++++++++++++++--- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts index f96ae4382155..4a17ebb1e508 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/styles.ts @@ -186,7 +186,27 @@ export function getStylesConfig(wco: WebpackConfigOptions) { ); } if (tailwindPackagePath) { - extraPostcssPlugins.push(require(tailwindPackagePath)({ config: tailwindConfigPath })); + const tailwindConfig = require(tailwindConfigPath); + if (typeof tailwindConfig !== 'function') { + if (!tailwindConfig.purge) { + tailwindConfig.purge = {}; + } + if (Array.isArray(tailwindConfig.purge)) { + tailwindConfig.purge = { + content: [...tailwindConfig.purge], + }; + } + if (typeof tailwindConfig.purge.enabled === 'undefined') { + tailwindConfig.purge.enabled = !!buildOptions.optimization?.styles.minify; + } + + } else { + wco.logger.warn( + `Tailwind CSS configuration file export function instead of object.` + + ` To enable Purge in Tailwind CSS, please make sure to export an object.`, + ); + } + extraPostcssPlugins.push(require(tailwindPackagePath)(tailwindConfig)); } } diff --git a/tests/legacy-cli/e2e/tests/build/styles/tailwind.ts b/tests/legacy-cli/e2e/tests/build/styles/tailwind.ts index 00818606c932..ea5be365e0eb 100644 --- a/tests/legacy-cli/e2e/tests/build/styles/tailwind.ts +++ b/tests/legacy-cli/e2e/tests/build/styles/tailwind.ts @@ -1,6 +1,7 @@ -import { deleteFile, expectFileToMatch, writeFile } from '../../../utils/fs'; +import { deleteFile, expectFileToMatch, replaceInFile, writeFile } from '../../../utils/fs'; import { installPackage, uninstallPackage } from '../../../utils/packages'; import { ng, silentExec } from '../../../utils/process'; +import { updateJsonFile } from '../../../utils/project'; import { expectToFail } from '../../../utils/utils'; export default async function () { @@ -14,12 +15,18 @@ export default async function () { // Create configuration file await silentExec('npx', 'tailwindcss', 'init'); + await replaceInFile('tailwind.config.js', `purge: [],`, `purge: ['./src/**/*.{html,ts}'],`); + await updateJsonFile('angular.json', json => { + json.projects['test-project'].architect.build.configurations.production.budgets = [ + { type: 'all', maximumError: '100mb' }, + ]; + }); // Add Tailwind directives to a component style - await writeFile('src/app/app.component.css', '@tailwind base; @tailwind components;'); + await writeFile('src/app/app.component.css', '@tailwind base; @tailwind components; @tailwind utilities'); // Add Tailwind directives to a global style - await writeFile('src/styles.css', '@tailwind base; @tailwind components;'); + await writeFile('src/styles.css', '@tailwind base; @tailwind components; @tailwind utilities'); // Build should succeed and process Tailwind directives await ng('build'); @@ -28,19 +35,66 @@ export default async function () { await expectFileToMatch('dist/test-project/styles.css', /::placeholder/); await expectFileToMatch('dist/test-project/main.js', /::placeholder/); await expectToFail(() => - expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'), + expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components; @tailwind utilities'), ); await expectToFail(() => - expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'), + expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components; @tailwind utilities'), ); + + await writeFile('src/app/app.component.html', '
Test
'); + await ng('build', '--prod', '--output-hashing=none'); + await expectFileToMatch('dist/test-project/styles.css', /\.rounded-md/); + await expectFileToMatch('dist/test-project/main.js', /\.rounded-md/); + await expectToFail(() => + expectFileToMatch('dist/test-project/styles.css', /\.py-3/), + ); + await expectToFail(() => + expectFileToMatch('dist/test-project/main.js', /\.py-3/), + ); + await replaceInFile('tailwind.config.js', `purge: ['./src/**/*.{html,ts}'],`, `purge: { + content: [ + './src/**/*.{html,ts}', + ] + },`); + await ng('build', '--prod', '--output-hashing=none'); + await expectFileToMatch('dist/test-project/styles.css', /\.rounded-md/); + await expectFileToMatch('dist/test-project/main.js', /\.rounded-md/); + await expectToFail(() => + expectFileToMatch('dist/test-project/styles.css', /\.py-3/), + ); + await expectToFail(() => + expectFileToMatch('dist/test-project/main.js', /\.py-3/), + ); + + await replaceInFile('tailwind.config.js', `purge: {`, `purge: { enabled: false,`); + await ng('build', '--prod', '--output-hashing=none'); + expectFileToMatch('dist/test-project/styles.css', /\.py-3/), + expectFileToMatch('dist/test-project/main.js', /\.py-3/), + await replaceInFile('tailwind.config.js', `purge: { enabled: false,`, `purge: { enabled: true,`); + await ng('build'); + await expectToFail(() => + expectFileToMatch('dist/test-project/styles.css', /\.py-3/), + ); + await expectToFail(() => + expectFileToMatch('dist/test-project/main.js', /\.py-3/), + ); + + + await writeFile('tailwind.config.js', 'module.exports = () => {}'); + const { stderr: _err } = await ng('build'); + if (!_err.includes("file export function instead of object")) { + throw new Error('Expected tailwind config error'); + } + + // Remove configuration file await deleteFile('tailwind.config.js'); // Ensure Tailwind is disabled when no configuration file is present await ng('build'); - await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); - await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); + await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components; @tailwind utilities'); + await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components; @tailwind utilities'); // Recreate configuration file await silentExec('npx', 'tailwindcss', 'init'); @@ -55,6 +109,6 @@ export default async function () { } // Tailwind directives should be unprocessed with missing package - await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'); - await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'); + await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components; @tailwind utilities'); + await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components; @tailwind utilities'); }