Skip to content

Commit 58bd397

Browse files
clydinalan-agius4
authored andcommitted
fix(@angular-devkit/build-angular): process nested tailwind usage in application builder
When using the application builder with Tailwind directives not in a directly referenced Sass stylesheet, the Tailwind process was previously skipped. To avoid this problem, the Tailwind keyword checks are now performed on the result of any stylesheet language processing which will contain all the used stylesheet content. (cherry picked from commit a03b83c)
1 parent 8a3e300 commit 58bd397

File tree

2 files changed

+53
-13
lines changed

2 files changed

+53
-13
lines changed

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

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { LoadResultCache, createCachedLoad } from '../load-result-cache';
1717
* The lazy-loaded instance of the postcss stylesheet postprocessor.
1818
* It is only imported and initialized if postcss is needed.
1919
*/
20-
let postcss: typeof import('postcss')['default'] | undefined;
20+
let postcss: (typeof import('postcss'))['default'] | undefined;
2121

2222
/**
2323
* An object containing the plugin options to use when processing stylesheets.
@@ -121,12 +121,6 @@ export class StylesheetPluginFactory {
121121
);
122122

123123
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));
130124

131125
return processStylesheet(
132126
language,
@@ -135,7 +129,7 @@ export class StylesheetPluginFactory {
135129
format,
136130
options,
137131
build,
138-
needsPostcss ? postcssProcessor : undefined,
132+
postcssProcessor,
139133
);
140134
}),
141135
);
@@ -145,8 +139,6 @@ export class StylesheetPluginFactory {
145139
{ filter: language.fileFilter },
146140
createCachedLoad(cache, async (args) => {
147141
const data = await readFile(args.path, 'utf-8');
148-
const needsPostcss =
149-
!!postcssProcessor && TAILWIND_KEYWORDS.some((keyword) => data.includes(keyword));
150142

151143
return processStylesheet(
152144
language,
@@ -155,7 +147,7 @@ export class StylesheetPluginFactory {
155147
extname(args.path).toLowerCase().slice(1),
156148
options,
157149
build,
158-
needsPostcss ? postcssProcessor : undefined,
150+
postcssProcessor,
159151
);
160152
}),
161153
);
@@ -186,8 +178,15 @@ async function processStylesheet(
186178
};
187179
}
188180

189-
// Transform with postcss if needed and there are no errors
190-
if (postcssProcessor && result.contents && !result.errors?.length) {
181+
// Return early if there are no contents to further process
182+
if (!result.contents) {
183+
return result;
184+
}
185+
186+
// Only use postcss if Tailwind processing is required.
187+
// NOTE: If postcss is used for more than just Tailwind in the future this check MUST
188+
// be updated to account for the additional use.
189+
if (postcssProcessor && !result.errors?.length && hasTailwindKeywords(result.contents)) {
191190
const postcssResult = await compileString(
192191
typeof result.contents === 'string'
193192
? result.contents
@@ -219,6 +218,24 @@ async function processStylesheet(
219218
return result;
220219
}
221220

221+
/**
222+
* Searches the provided contents for keywords that indicate Tailwind is used
223+
* within a stylesheet.
224+
* @param contents A string or Uint8Array containing UTF-8 text.
225+
* @returns True, if the contents contains tailwind keywords; False, otherwise.
226+
*/
227+
function hasTailwindKeywords(contents: string | Uint8Array): boolean {
228+
// TODO: use better search algorithm for keywords
229+
if (typeof contents === 'string') {
230+
return TAILWIND_KEYWORDS.some((keyword) => contents.includes(keyword));
231+
}
232+
233+
// Contents is a Uint8Array
234+
const data = contents instanceof Buffer ? contents : Buffer.from(contents);
235+
236+
return TAILWIND_KEYWORDS.some((keyword) => data.includes(keyword));
237+
}
238+
222239
/**
223240
* Compiles the provided CSS stylesheet data using a provided postcss processor and provides an
224241
* esbuild load result that can be used directly by an esbuild Plugin.

tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ export default async function () {
5555
),
5656
);
5757

58+
// Add Tailwind directives to an imported global style
59+
await writeFile('src/tailwind.scss', '@tailwind base; @tailwind components;');
60+
await writeFile('src/styles.css', '@import "./tailwind.scss";');
61+
62+
// Build should succeed and process Tailwind directives
63+
await ng('build', '--configuration=development');
64+
65+
// Check for Tailwind output
66+
await expectFileToMatch('dist/test-project/browser/styles.css', /::placeholder/);
67+
await expectFileToMatch('dist/test-project/browser/main.js', /::placeholder/);
68+
await expectToFail(() =>
69+
expectFileToMatch(
70+
'dist/test-project/browser/styles.css',
71+
/@tailwind base;\s+@tailwind components;/,
72+
),
73+
);
74+
await expectToFail(() =>
75+
expectFileToMatch(
76+
'dist/test-project/browser/main.js',
77+
/@tailwind base;(?:\\n|\s*)@tailwind components;/,
78+
),
79+
);
80+
5881
// Remove configuration file
5982
await deleteFile('tailwind.config.js');
6083

0 commit comments

Comments
 (0)