Skip to content

Commit 974748c

Browse files
committed
perf(@angular-devkit/build-angular): filter postcss usage based on content in esbuild builder
When using the esbuild-based browser application builder, stylesheets that do not need any postcss processing will now skip the postcss step of the stylesheet build pipeline. Currently, only Tailwind CSS leverages postcss and Tailwind CSS is an opt-in feature. As a result, postcss will only be used if Tailwind CSS is configured for the project and a stylesheet contains one or more of the directives/functions specific to Tailwind as provided by https://tailwindcss.com/docs/functions-and-directives. This change should be most beneficial for component stylesheets which will rarely contain Tailwind specific rules that need additional processing.
1 parent b0d6b87 commit 974748c

File tree

1 file changed

+46
-13
lines changed

1 file changed

+46
-13
lines changed

Diff for: packages/angular_devkit/build_angular/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.ts

+46-13
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ export interface StylesheetPluginOptions {
2929
*/
3030
sourcemap: boolean;
3131

32+
/**
33+
* An optional array of paths that will be searched for stylesheets if the default
34+
* resolution process for the stylesheet language does not succeed.
35+
*/
3236
includePaths?: string[];
3337

3438
/**
@@ -37,9 +41,21 @@ export interface StylesheetPluginOptions {
3741
*/
3842
inlineComponentData?: Record<string, string>;
3943

44+
/**
45+
* Optional information used to load and configure Tailwind CSS. If present, the postcss
46+
* will be added to the stylesheet processing with the Tailwind plugin setup as provided
47+
* by the configuration file.
48+
*/
4049
tailwindConfiguration?: { file: string; package: string };
4150
}
4251

52+
/**
53+
* An array of keywords that indicate Tailwind CSS processing is required for a stylesheet.
54+
*
55+
* Based on https://tailwindcss.com/docs/functions-and-directives
56+
*/
57+
const TAILWIND_KEYWORDS = ['@tailwind', '@layer', '@apply', '@config', 'theme(', 'screen('];
58+
4359
export interface StylesheetLanguage {
4460
name: string;
4561
componentFilter: RegExp;
@@ -54,6 +70,8 @@ export interface StylesheetLanguage {
5470
}
5571

5672
export class StylesheetPluginFactory {
73+
private postcssProcessor?: import('postcss').Processor;
74+
5775
constructor(
5876
private readonly options: StylesheetPluginOptions,
5977
private readonly cache?: LoadResultCache,
@@ -69,21 +87,28 @@ export class StylesheetPluginFactory {
6987
}
7088

7189
const { cache, options } = this;
90+
const setupPostcss = async () => {
91+
// Return already created processor if present
92+
if (this.postcssProcessor) {
93+
return this.postcssProcessor;
94+
}
95+
96+
if (options.tailwindConfiguration) {
97+
postcss ??= (await import('postcss')).default;
98+
const tailwind = await import(options.tailwindConfiguration.package);
99+
this.postcssProcessor = postcss().use(
100+
tailwind.default({ config: options.tailwindConfiguration.file }),
101+
);
102+
}
103+
104+
return this.postcssProcessor;
105+
};
72106

73107
return {
74108
name: 'angular-' + language.name,
75109
async setup(build) {
76-
// Setup postcss if needed by tailwind
77-
// TODO: Move this into the plugin factory to avoid repeat setup per created plugin
78-
let postcssProcessor: import('postcss').Processor | undefined;
79-
if (options.tailwindConfiguration) {
80-
postcss ??= (await import('postcss')).default;
81-
postcssProcessor = postcss();
82-
if (options.tailwindConfiguration) {
83-
const tailwind = await import(options.tailwindConfiguration.package);
84-
postcssProcessor.use(tailwind.default({ config: options.tailwindConfiguration.file }));
85-
}
86-
}
110+
// Setup postcss if needed
111+
const postcssProcessor = await setupPostcss();
87112

88113
// Add a load callback to support inline Component styles
89114
build.onLoad(
@@ -96,6 +121,12 @@ export class StylesheetPluginFactory {
96121
);
97122

98123
const [format, , filename] = args.path.split(';', 3);
124+
// Only use postcss if Tailwind processing is required.
125+
// NOTE: If postcss is used for more than just Tailwind in the future this check MUST
126+
// be updated to account for the additional use.
127+
// TODO: use better search algorithm for keywords
128+
const needsPostcss =
129+
!!postcssProcessor && TAILWIND_KEYWORDS.some((keyword) => data.includes(keyword));
99130

100131
return processStylesheet(
101132
language,
@@ -104,7 +135,7 @@ export class StylesheetPluginFactory {
104135
format,
105136
options,
106137
build,
107-
postcssProcessor,
138+
needsPostcss ? postcssProcessor : undefined,
108139
);
109140
}),
110141
);
@@ -114,6 +145,8 @@ export class StylesheetPluginFactory {
114145
{ filter: language.fileFilter },
115146
createCachedLoad(cache, async (args) => {
116147
const data = await readFile(args.path, 'utf-8');
148+
const needsPostcss =
149+
!!postcssProcessor && TAILWIND_KEYWORDS.some((keyword) => data.includes(keyword));
117150

118151
return processStylesheet(
119152
language,
@@ -122,7 +155,7 @@ export class StylesheetPluginFactory {
122155
extname(args.path).toLowerCase().slice(1),
123156
options,
124157
build,
125-
postcssProcessor,
158+
needsPostcss ? postcssProcessor : undefined,
126159
);
127160
}),
128161
);

0 commit comments

Comments
 (0)