Skip to content

Commit 73b4098

Browse files
clydinalan-agius4
authored andcommitted
feat(@angular-devkit/build-angular): detect and use tailwindcss in projects
This feature adds detection of the `tailwindcss` package (https://tailwindcss.com) and provides a mechanism to automatically include support. To enable tailwindcss for a project, two actions must be taken: 1) Install `tailwindcss` into the Angular workspace (`npm install -D tailwindcss`/`yarn add -D tailwindcss`) 2) Create a tailwindcss configuration file (`tailwind.config.js`) in either the workspace root or the project root. A configuration file in the project root takes precedence over one in the workspace root. When both conditions are met, the Angular CLI will initialize and integrate tailwindcss into the stylesheet build pipeline.
1 parent 00ee385 commit 73b4098

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

packages/angular_devkit/build_angular/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"karma": "^5.2.0 || ^6.0.0",
8888
"ng-packagr": "^11.0.0 || ^11.1.0-next",
8989
"protractor": "^7.0.0",
90+
"tailwindcss": "^2.0.0",
9091
"tslint": "^6.1.0",
9192
"typescript": "~4.0.0 || ~4.1.0"
9293
},
@@ -106,6 +107,9 @@
106107
"protractor": {
107108
"optional": true
108109
},
110+
"tailwindcss": {
111+
"optional": true
112+
},
109113
"tslint": {
110114
"optional": true
111115
}

packages/angular_devkit/build_angular/src/webpack/configs/styles.ts

+33
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,38 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
156156

157157
const assetNameTemplate = assetNameTemplateFactory(hashFormat);
158158

159+
const extraPostcssPlugins: import('postcss').Plugin[] = [];
160+
161+
// Attempt to setup Tailwind CSS
162+
// A configuration file can exist in the project or workspace root
163+
const tailwindConfigFile = 'tailwind.config.js';
164+
let tailwindConfigPath;
165+
for (const basePath of [wco.projectRoot, wco.root]) {
166+
const fullPath = path.join(basePath, tailwindConfigFile);
167+
if (fs.existsSync(fullPath)) {
168+
tailwindConfigPath = fullPath;
169+
}
170+
}
171+
// Only load Tailwind CSS plugin if configuration file was found.
172+
// This acts as a guard to ensure the project actually wants to use Tailwind CSS.
173+
// The package may be unknowningly present due to a third-party transitive package dependency.
174+
if (tailwindConfigPath) {
175+
let tailwindPackagePath;
176+
try {
177+
tailwindPackagePath = require.resolve('tailwindcss', { paths: [wco.root] });
178+
} catch {
179+
const relativeTailwindConfigPath = path.relative(wco.root, tailwindConfigPath);
180+
wco.logger.warn(
181+
`Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
182+
` but the 'tailwindcss' package is not installed.` +
183+
` To enable Tailwind CSS, please install the 'tailwindcss' package.`,
184+
);
185+
}
186+
if (tailwindPackagePath) {
187+
extraPostcssPlugins.push(require(tailwindPackagePath)({ config: tailwindConfigPath }));
188+
}
189+
}
190+
159191
const postcssOptionsCreator = (sourceMap: boolean, extracted: boolean | undefined) => {
160192
return (loader: webpack.loader.LoaderContext) => ({
161193
map: sourceMap && {
@@ -189,6 +221,7 @@ export function getStylesConfig(wco: WebpackConfigOptions) {
189221
emitFile: buildOptions.platform !== 'server',
190222
extracted,
191223
}),
224+
...extraPostcssPlugins,
192225
autoprefixer(),
193226
],
194227
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { deleteFile, expectFileToMatch, writeFile } from '../../../utils/fs';
2+
import { installPackage, uninstallPackage } from '../../../utils/packages';
3+
import { ng, silentExec } from '../../../utils/process';
4+
import { expectToFail } from '../../../utils/utils';
5+
6+
export default async function () {
7+
// Install Tailwind
8+
await installPackage('tailwindcss');
9+
10+
// Create configuration file
11+
await silentExec('npx', 'tailwindcss', 'init');
12+
13+
// Add Tailwind directives to a component style
14+
await writeFile('src/app/app.component.css', '@tailwind base; @tailwind components;');
15+
16+
// Add Tailwind directives to a global style
17+
await writeFile('src/styles.css', '@tailwind base; @tailwind components;');
18+
19+
// Build should succeed and process Tailwind directives
20+
await ng('build');
21+
22+
// Check for Tailwind output
23+
await expectFileToMatch('dist/test-project/styles.css', /::placeholder/);
24+
await expectFileToMatch('dist/test-project/main.js', /::placeholder/);
25+
await expectToFail(() =>
26+
expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;'),
27+
);
28+
await expectToFail(() =>
29+
expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;'),
30+
);
31+
32+
// Remove configuration file
33+
await deleteFile('tailwind.config.js');
34+
35+
// Ensure Tailwind is disabled when no configuration file is present
36+
await ng('build');
37+
await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;');
38+
await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;');
39+
40+
// Recreate configuration file
41+
await silentExec('npx', 'tailwindcss', 'init');
42+
43+
// Uninstall Tailwind
44+
await uninstallPackage('tailwindcss');
45+
46+
// Ensure installation warning is present
47+
const { stderr } = await ng('build');
48+
if (!stderr.includes("To enable Tailwind CSS, please install the 'tailwindcss' package.")) {
49+
throw new Error('Expected tailwind installation warning');
50+
}
51+
52+
// Tailwind directives should be unprocessed with missing package
53+
await expectFileToMatch('dist/test-project/styles.css', '@tailwind base; @tailwind components;');
54+
await expectFileToMatch('dist/test-project/main.js', '@tailwind base; @tailwind components;');
55+
}

0 commit comments

Comments
 (0)