Skip to content

Commit 3a1bf5c

Browse files
alan-agius4clydin
authored andcommittedJun 10, 2024·
fix(@angular/build): Initiate PostCSS only once
Previously, PostCSS was initialized three times (once for each preprocessor), resulting in plugins being applied multiple times to each instance. This issue occured due to a race condition triggered by multiple esbuild plugins. Fixes #27804 (cherry picked from commit f102f81)
1 parent 20fc6ca commit 3a1bf5c

File tree

1 file changed

+54
-50
lines changed

1 file changed

+54
-50
lines changed
 

‎packages/angular/build/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.ts

+54-50
Original file line numberDiff line numberDiff line change
@@ -96,76 +96,30 @@ export interface StylesheetLanguage {
9696
const postcssProcessors = new Map<string, WeakRef<PostcssProcessor>>();
9797

9898
export class StylesheetPluginFactory {
99-
private postcssProcessor?: PostcssProcessor;
100-
10199
constructor(
102100
private readonly options: StylesheetPluginOptions,
103101
private readonly cache?: LoadResultCache,
104102
) {}
105103

106104
create(language: Readonly<StylesheetLanguage>): Plugin {
105+
const { cache, options, setupPostcss } = this;
106+
107107
// Return a noop plugin if no load actions are required
108-
if (
109-
!language.process &&
110-
!this.options.postcssConfiguration &&
111-
!this.options.tailwindConfiguration
112-
) {
108+
if (!language.process && !options.postcssConfiguration && !options.tailwindConfiguration) {
113109
return {
114110
name: 'angular-' + language.name,
115111
setup() {},
116112
};
117113
}
118114

119-
const { cache, options } = this;
120-
const setupPostcss = async () => {
121-
// Return already created processor if present
122-
if (this.postcssProcessor) {
123-
return this.postcssProcessor;
124-
}
125-
126-
if (options.postcssConfiguration) {
127-
const postCssInstanceKey = JSON.stringify(options.postcssConfiguration);
128-
129-
this.postcssProcessor = postcssProcessors.get(postCssInstanceKey)?.deref();
130-
131-
if (!this.postcssProcessor) {
132-
postcss ??= (await import('postcss')).default;
133-
this.postcssProcessor = postcss();
134-
135-
for (const [pluginName, pluginOptions] of options.postcssConfiguration.plugins) {
136-
const { default: plugin } = await import(pluginName);
137-
if (typeof plugin !== 'function' || plugin.postcss !== true) {
138-
throw new Error(`Attempted to load invalid Postcss plugin: "${pluginName}"`);
139-
}
140-
this.postcssProcessor.use(plugin(pluginOptions));
141-
}
142-
143-
postcssProcessors.set(postCssInstanceKey, new WeakRef(this.postcssProcessor));
144-
}
145-
} else if (options.tailwindConfiguration) {
146-
const { package: tailwindPackage, file: config } = options.tailwindConfiguration;
147-
const postCssInstanceKey = tailwindPackage + ':' + config;
148-
this.postcssProcessor = postcssProcessors.get(postCssInstanceKey)?.deref();
149-
150-
if (!this.postcssProcessor) {
151-
postcss ??= (await import('postcss')).default;
152-
const tailwind = await import(tailwindPackage);
153-
this.postcssProcessor = postcss().use(tailwind.default({ config }));
154-
postcssProcessors.set(postCssInstanceKey, new WeakRef(this.postcssProcessor));
155-
}
156-
}
157-
158-
return this.postcssProcessor;
159-
};
160-
161115
return {
162116
name: 'angular-' + language.name,
163117
async setup(build) {
164118
// Setup postcss if needed
165119
let postcssProcessor: PostcssProcessor | undefined;
166120
build.onStart(async () => {
167121
try {
168-
postcssProcessor = await setupPostcss();
122+
postcssProcessor = await setupPostcss;
169123
} catch {
170124
return {
171125
errors: [
@@ -229,6 +183,56 @@ export class StylesheetPluginFactory {
229183
},
230184
};
231185
}
186+
187+
private setupPostcssPromise: Promise<PostcssProcessor | undefined> | undefined;
188+
private get setupPostcss(): Promise<PostcssProcessor | undefined> {
189+
return (this.setupPostcssPromise ??= this.initPostcss());
190+
}
191+
192+
private initPostcssCallCount = 0;
193+
/**
194+
* This method should not be called directly.
195+
* Use {@link setupPostcss} instead.
196+
*/
197+
private async initPostcss(): Promise<PostcssProcessor | undefined> {
198+
assert.equal(++this.initPostcssCallCount, 1, '`initPostcss` was called more than once.');
199+
200+
const { options } = this;
201+
if (options.postcssConfiguration) {
202+
const postCssInstanceKey = JSON.stringify(options.postcssConfiguration);
203+
let postcssProcessor = postcssProcessors.get(postCssInstanceKey)?.deref();
204+
205+
if (!postcssProcessor) {
206+
postcss ??= (await import('postcss')).default;
207+
postcssProcessor = postcss();
208+
for (const [pluginName, pluginOptions] of options.postcssConfiguration.plugins) {
209+
const { default: plugin } = await import(pluginName);
210+
if (typeof plugin !== 'function' || plugin.postcss !== true) {
211+
throw new Error(`Attempted to load invalid Postcss plugin: "${pluginName}"`);
212+
}
213+
214+
postcssProcessor.use(plugin(pluginOptions));
215+
}
216+
217+
postcssProcessors.set(postCssInstanceKey, new WeakRef(postcssProcessor));
218+
}
219+
220+
return postcssProcessor;
221+
} else if (options.tailwindConfiguration) {
222+
const { package: tailwindPackage, file: config } = options.tailwindConfiguration;
223+
const postCssInstanceKey = tailwindPackage + ':' + config;
224+
let postcssProcessor = postcssProcessors.get(postCssInstanceKey)?.deref();
225+
226+
if (!postcssProcessor) {
227+
postcss ??= (await import('postcss')).default;
228+
const tailwind = await import(tailwindPackage);
229+
postcssProcessor = postcss().use(tailwind.default({ config }));
230+
postcssProcessors.set(postCssInstanceKey, new WeakRef(postcssProcessor));
231+
}
232+
233+
return postcssProcessor;
234+
}
235+
}
232236
}
233237

234238
async function processStylesheet(

0 commit comments

Comments
 (0)
Please sign in to comment.