Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): process nested tailwind usage in …
Browse files Browse the repository at this point in the history
…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)
  • Loading branch information
clydin authored and alan-agius4 committed Oct 18, 2023
1 parent 8a3e300 commit 58bd397
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 13 deletions.
Expand Up @@ -17,7 +17,7 @@ import { LoadResultCache, createCachedLoad } from '../load-result-cache';
* The lazy-loaded instance of the postcss stylesheet postprocessor.
* It is only imported and initialized if postcss is needed.
*/
let postcss: typeof import('postcss')['default'] | undefined;
let postcss: (typeof import('postcss'))['default'] | undefined;

/**
* An object containing the plugin options to use when processing stylesheets.
Expand Down Expand Up @@ -121,12 +121,6 @@ export class StylesheetPluginFactory {
);

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

return processStylesheet(
language,
Expand All @@ -135,7 +129,7 @@ export class StylesheetPluginFactory {
format,
options,
build,
needsPostcss ? postcssProcessor : undefined,
postcssProcessor,
);
}),
);
Expand All @@ -145,8 +139,6 @@ export class StylesheetPluginFactory {
{ filter: language.fileFilter },
createCachedLoad(cache, async (args) => {
const data = await readFile(args.path, 'utf-8');
const needsPostcss =
!!postcssProcessor && TAILWIND_KEYWORDS.some((keyword) => data.includes(keyword));

return processStylesheet(
language,
Expand All @@ -155,7 +147,7 @@ export class StylesheetPluginFactory {
extname(args.path).toLowerCase().slice(1),
options,
build,
needsPostcss ? postcssProcessor : undefined,
postcssProcessor,
);
}),
);
Expand Down Expand Up @@ -186,8 +178,15 @@ async function processStylesheet(
};
}

// Transform with postcss if needed and there are no errors
if (postcssProcessor && result.contents && !result.errors?.length) {
// Return early if there are no contents to further process
if (!result.contents) {
return result;
}

// Only use postcss if Tailwind processing is required.
// NOTE: If postcss is used for more than just Tailwind in the future this check MUST
// be updated to account for the additional use.
if (postcssProcessor && !result.errors?.length && hasTailwindKeywords(result.contents)) {
const postcssResult = await compileString(
typeof result.contents === 'string'
? result.contents
Expand Down Expand Up @@ -219,6 +218,24 @@ async function processStylesheet(
return result;
}

/**
* Searches the provided contents for keywords that indicate Tailwind is used
* within a stylesheet.
* @param contents A string or Uint8Array containing UTF-8 text.
* @returns True, if the contents contains tailwind keywords; False, otherwise.
*/
function hasTailwindKeywords(contents: string | Uint8Array): boolean {
// TODO: use better search algorithm for keywords
if (typeof contents === 'string') {
return TAILWIND_KEYWORDS.some((keyword) => contents.includes(keyword));
}

// Contents is a Uint8Array
const data = contents instanceof Buffer ? contents : Buffer.from(contents);

return TAILWIND_KEYWORDS.some((keyword) => data.includes(keyword));
}

/**
* Compiles the provided CSS stylesheet data using a provided postcss processor and provides an
* esbuild load result that can be used directly by an esbuild Plugin.
Expand Down
23 changes: 23 additions & 0 deletions tests/legacy-cli/e2e/tests/build/styles/tailwind-v3.ts
Expand Up @@ -55,6 +55,29 @@ export default async function () {
),
);

// Add Tailwind directives to an imported global style
await writeFile('src/tailwind.scss', '@tailwind base; @tailwind components;');
await writeFile('src/styles.css', '@import "./tailwind.scss";');

// Build should succeed and process Tailwind directives
await ng('build', '--configuration=development');

// Check for Tailwind output
await expectFileToMatch('dist/test-project/browser/styles.css', /::placeholder/);
await expectFileToMatch('dist/test-project/browser/main.js', /::placeholder/);
await expectToFail(() =>
expectFileToMatch(
'dist/test-project/browser/styles.css',
/@tailwind base;\s+@tailwind components;/,
),
);
await expectToFail(() =>
expectFileToMatch(
'dist/test-project/browser/main.js',
/@tailwind base;(?:\\n|\s*)@tailwind components;/,
),
);

// Remove configuration file
await deleteFile('tailwind.config.js');

Expand Down

0 comments on commit 58bd397

Please sign in to comment.