Skip to content

Commit 52969db

Browse files
clydinangular-robot[bot]
authored andcommittedMar 22, 2023
feat(@angular-devkit/build-angular): initial tailwindcss support for CSS in esbuild builder
When using the experimental esbuild-based browser application builder, CSS stylesheets will now be processed by tailwindcss if a tailwind configuration file is present and the `tailwindcss` package has been installed in the project. This provides equivalent behavior to the use of tailwind with the current default Webpack-based build system. Currently, only CSS stylesheets are processed. Preprocessor support including Sass and Less will be added in a future change.
1 parent 3b815e1 commit 52969db

File tree

5 files changed

+70
-4
lines changed

5 files changed

+70
-4
lines changed
 

Diff for: ‎packages/angular_devkit/build_angular/src/builders/browser-esbuild/css-plugin.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export interface CssPluginOptions {
3737
* of the esbuild formatted target.
3838
*/
3939
browsers: string[];
40+
41+
tailwindConfiguration?: { file: string; package: string };
4042
}
4143

4244
/**
@@ -61,12 +63,19 @@ export function createCssPlugin(options: CssPluginOptions): Plugin {
6163
const autoprefixerInfo = autoprefixer.info({ from: build.initialOptions.absWorkingDir });
6264
const skipAutoprefixer = autoprefixerInfo.includes('Awesome!');
6365

64-
if (skipAutoprefixer) {
66+
if (skipAutoprefixer && !options.tailwindConfiguration) {
6567
return;
6668
}
6769

6870
postcss ??= (await import('postcss')).default;
69-
const postcssProcessor = postcss([autoprefixer]);
71+
const postcssProcessor = postcss();
72+
if (options.tailwindConfiguration) {
73+
const tailwind = await import(options.tailwindConfiguration.package);
74+
postcssProcessor.use(tailwind({ config: options.tailwindConfiguration.file }));
75+
}
76+
if (!skipAutoprefixer) {
77+
postcssProcessor.use(autoprefixer);
78+
}
7079

7180
// Add a load callback to support inline Component styles
7281
build.onLoad({ filter: /^css;/, namespace: 'angular:styles/component' }, async (args) => {

Diff for: ‎packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ function createCodeBundleOptions(
332332
advancedOptimizations,
333333
inlineStyleLanguage,
334334
jit,
335+
tailwindConfiguration,
335336
} = options;
336337

337338
return {
@@ -386,6 +387,7 @@ function createCodeBundleOptions(
386387
target,
387388
inlineStyleLanguage,
388389
browsers,
390+
tailwindConfiguration,
389391
},
390392
),
391393
],
@@ -464,6 +466,7 @@ function createGlobalStylesBundleOptions(
464466
preserveSymlinks,
465467
externalDependencies,
466468
stylePreprocessorOptions,
469+
tailwindConfiguration,
467470
} = options;
468471

469472
const buildOptions = createStylesheetBundleOptions({
@@ -476,6 +479,7 @@ function createGlobalStylesBundleOptions(
476479
outputNames,
477480
includePaths: stylePreprocessorOptions?.includePaths,
478481
browsers,
482+
tailwindConfiguration,
479483
});
480484
buildOptions.legalComments = options.extractLicenses ? 'none' : 'eof';
481485

Diff for: ‎packages/angular_devkit/build_angular/src/builders/browser-esbuild/options.ts

+44-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
*/
88

99
import { BuilderContext } from '@angular-devkit/architect';
10-
import * as path from 'path';
10+
import fs from 'node:fs';
11+
import { createRequire } from 'node:module';
12+
import path from 'node:path';
1113
import { normalizeAssetPatterns, normalizeOptimization, normalizeSourceMaps } from '../../utils';
1214
import { normalizeCacheOptions } from '../../utils/normalize-cache';
1315
import { normalizePolyfills } from '../../utils/normalize-polyfills';
@@ -100,6 +102,25 @@ export async function normalizeOptions(
100102
}
101103
}
102104

105+
let tailwindConfiguration: { file: string; package: string } | undefined;
106+
const tailwindConfigurationPath = findTailwindConfigurationFile(workspaceRoot, projectRoot);
107+
if (tailwindConfigurationPath) {
108+
const resolver = createRequire(projectRoot);
109+
try {
110+
tailwindConfiguration = {
111+
file: tailwindConfigurationPath,
112+
package: resolver.resolve('tailwindcss'),
113+
};
114+
} catch {
115+
const relativeTailwindConfigPath = path.relative(workspaceRoot, tailwindConfigurationPath);
116+
context.logger.warn(
117+
`Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
118+
` but the 'tailwindcss' package is not installed.` +
119+
` To enable Tailwind CSS, please install the 'tailwindcss' package.`,
120+
);
121+
}
122+
}
123+
103124
let serviceWorkerOptions;
104125
if (options.serviceWorker) {
105126
// If ngswConfigPath is not specified, the default is 'ngsw-config.json' within the project root
@@ -181,5 +202,27 @@ export async function normalizeOptions(
181202
globalStyles,
182203
serviceWorkerOptions,
183204
indexHtmlOptions,
205+
tailwindConfiguration,
184206
};
185207
}
208+
209+
function findTailwindConfigurationFile(
210+
workspaceRoot: string,
211+
projectRoot: string,
212+
): string | undefined {
213+
// A configuration file can exist in the project or workspace root
214+
// The list of valid config files can be found:
215+
// https://github.com/tailwindlabs/tailwindcss/blob/8845d112fb62d79815b50b3bae80c317450b8b92/src/util/resolveConfigPath.js#L46-L52
216+
const tailwindConfigFiles = ['tailwind.config.js', 'tailwind.config.cjs'];
217+
for (const basePath of [projectRoot, workspaceRoot]) {
218+
for (const configFile of tailwindConfigFiles) {
219+
// Project level configuration should always take precedence.
220+
const fullPath = path.join(basePath, configFile);
221+
if (fs.existsSync(fullPath)) {
222+
return fullPath;
223+
}
224+
}
225+
}
226+
227+
return undefined;
228+
}

Diff for: ‎packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface BundleStylesheetOptions {
2929
externalDependencies?: string[];
3030
target: string[];
3131
browsers: string[];
32+
tailwindConfiguration?: { file: string; package: string };
3233
}
3334

3435
export function createStylesheetBundleOptions(
@@ -72,6 +73,7 @@ export function createStylesheetBundleOptions(
7273
sourcemap: !!options.sourcemap,
7374
inlineComponentData,
7475
browsers: options.browsers,
76+
tailwindConfiguration: options.tailwindConfiguration,
7577
}),
7678
createCssResourcePlugin(),
7779
],

Diff for: ‎tests/legacy-cli/e2e.bzl

+9-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@ TEST_TAGS = ["no-remote-exec", "requires-network"]
2929
# Subset of tests for yarn/esbuild
3030
BROWSER_TESTS = ["tests/misc/browsers.js"]
3131
YARN_TESTS = ["tests/basic/**", "tests/update/**", "tests/commands/add/**"]
32-
ESBUILD_TESTS = ["tests/basic/**", "tests/build/prod-build.js", "tests/build/relative-sourcemap.js", "tests/build/styles/scss.js", "tests/build/styles/include-paths.js", "tests/commands/add/add-pwa.js"]
32+
ESBUILD_TESTS = [
33+
"tests/basic/**",
34+
"tests/build/prod-build.js",
35+
"tests/build/relative-sourcemap.js",
36+
"tests/build/styles/scss.js",
37+
"tests/build/styles/include-paths.js",
38+
"tests/build/styles/tailwind*.js",
39+
"tests/commands/add/add-pwa.js",
40+
]
3341

3442
# Tests excluded for esbuild
3543
ESBUILD_IGNORE_TESTS = [

0 commit comments

Comments
 (0)