Skip to content

Commit 9aa9b52

Browse files
committed
feat(@angular-devkit/build-angular): support autoprefixer/tailwind CSS with Less/Sass in esbuild builder
When using the esbuild-based browser application builder, Sass and Less stylesheets will now be post-processed with autoprefixer and/or Tailwind CSS when applicable. CSS stylesheets were already processed by these tools. Autoprefixer is queried based on the configured browserslist to determine if any processing is required and is not added to the build pipeline if no transformations are required. Likewise for Tailwind, if no Tailwind configuration file is present, Tailwind CSS will also not be added to the build pipeline. If both autoprefixer and Tailwind are not required, `postcss` (the tool used to post-process the stylesheets) itself is not added to the build pipeline. This removes the potential for unneeded build time overhead for projects that do not require these post-processing steps. The default browserslist currently does require the use of autoprefixer based on autoprefixer's prefix analysis.
1 parent b66d94e commit 9aa9b52

File tree

7 files changed

+377
-329
lines changed

7 files changed

+377
-329
lines changed

packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { extractLicenses } from './license-extractor';
3232
import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options';
3333
import { Schema as BrowserBuilderOptions } from './schema';
3434
import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
35-
import { shutdownSassWorkerPool } from './stylesheets/sass-plugin';
35+
import { shutdownSassWorkerPool } from './stylesheets/sass-language';
3636
import type { ChangedFiles } from './watcher';
3737

3838
const compressAsync = promisify(brotliCompress);

packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets/bundle-options.ts

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import { createHash } from 'node:crypto';
1111
import path from 'node:path';
1212
import { BundlerContext } from '../esbuild';
1313
import { LoadResultCache } from '../load-result-cache';
14-
import { createCssPlugin } from './css-plugin';
14+
import { CssStylesheetLanguage } from './css-language';
1515
import { createCssResourcePlugin } from './css-resource-plugin';
16-
import { createLessPlugin } from './less-plugin';
17-
import { createSassPlugin } from './sass-plugin';
16+
import { LessStylesheetLanguage } from './less-language';
17+
import { SassStylesheetLanguage } from './sass-language';
18+
import { StylesheetPluginFactory } from './stylesheet-plugin-factory';
1819

1920
/**
2021
* A counter for component styles used to generate unique build-time identifiers for each stylesheet.
@@ -44,6 +45,17 @@ export function createStylesheetBundleOptions(
4445
path.resolve(options.workspaceRoot, includePath),
4546
);
4647

48+
const pluginFactory = new StylesheetPluginFactory(
49+
{
50+
sourcemap: !!options.sourcemap,
51+
includePaths,
52+
inlineComponentData,
53+
browsers: options.browsers,
54+
tailwindConfiguration: options.tailwindConfiguration,
55+
},
56+
cache,
57+
);
58+
4759
return {
4860
absWorkingDir: options.workspaceRoot,
4961
bundle: true,
@@ -62,31 +74,9 @@ export function createStylesheetBundleOptions(
6274
conditions: ['style', 'sass'],
6375
mainFields: ['style', 'sass'],
6476
plugins: [
65-
createSassPlugin(
66-
{
67-
sourcemap: !!options.sourcemap,
68-
loadPaths: includePaths,
69-
inlineComponentData,
70-
},
71-
cache,
72-
),
73-
createLessPlugin(
74-
{
75-
sourcemap: !!options.sourcemap,
76-
includePaths,
77-
inlineComponentData,
78-
},
79-
cache,
80-
),
81-
createCssPlugin(
82-
{
83-
sourcemap: !!options.sourcemap,
84-
inlineComponentData,
85-
browsers: options.browsers,
86-
tailwindConfiguration: options.tailwindConfiguration,
87-
},
88-
cache,
89-
),
77+
pluginFactory.create(SassStylesheetLanguage),
78+
pluginFactory.create(LessStylesheetLanguage),
79+
pluginFactory.create(CssStylesheetLanguage),
9080
createCssResourcePlugin(cache),
9181
],
9282
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { StylesheetLanguage } from './stylesheet-plugin-factory';
10+
11+
export const CssStylesheetLanguage = Object.freeze<StylesheetLanguage>({
12+
name: 'css',
13+
componentFilter: /^css;/,
14+
fileFilter: /\.css$/,
15+
});

packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets/css-plugin.ts

Lines changed: 0 additions & 191 deletions
This file was deleted.

packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets/less-plugin.ts renamed to packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets/less-language.ts

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,16 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import type { OnLoadResult, Plugin, PluginBuild } from 'esbuild';
10-
import assert from 'node:assert';
9+
import type { OnLoadResult, PluginBuild } from 'esbuild';
1110
import { readFile } from 'node:fs/promises';
12-
import { LoadResultCache, createCachedLoad } from '../load-result-cache';
11+
import { StylesheetLanguage, StylesheetPluginOptions } from './stylesheet-plugin-factory';
1312

1413
/**
1514
* The lazy-loaded instance of the less stylesheet preprocessor.
1615
* It is only imported and initialized if a less stylesheet is used.
1716
*/
1817
let lessPreprocessor: typeof import('less') | undefined;
1918

20-
export interface LessPluginOptions {
21-
sourcemap: boolean;
22-
includePaths?: string[];
23-
inlineComponentData?: Record<string, string>;
24-
}
25-
2619
interface LessException extends Error {
2720
filename: string;
2821
line: number;
@@ -34,43 +27,19 @@ function isLessException(error: unknown): error is LessException {
3427
return !!error && typeof error === 'object' && 'column' in error;
3528
}
3629

37-
export function createLessPlugin(options: LessPluginOptions, cache?: LoadResultCache): Plugin {
38-
return {
39-
name: 'angular-less',
40-
setup(build: PluginBuild): void {
41-
// Add a load callback to support inline Component styles
42-
build.onLoad(
43-
{ filter: /^less;/, namespace: 'angular:styles/component' },
44-
createCachedLoad(cache, async (args) => {
45-
const data = options.inlineComponentData?.[args.path];
46-
assert(
47-
typeof data === 'string',
48-
`component style name should always be found [${args.path}]`,
49-
);
50-
51-
const [, , filePath] = args.path.split(';', 3);
52-
53-
return compileString(data, filePath, options, build.resolve.bind(build));
54-
}),
55-
);
56-
57-
// Add a load callback to support files from disk
58-
build.onLoad(
59-
{ filter: /\.less$/ },
60-
createCachedLoad(cache, async (args) => {
61-
const data = await readFile(args.path, 'utf-8');
62-
63-
return compileString(data, args.path, options, build.resolve.bind(build));
64-
}),
65-
);
66-
},
67-
};
68-
}
30+
export const LessStylesheetLanguage = Object.freeze<StylesheetLanguage>({
31+
name: 'less',
32+
componentFilter: /^less;/,
33+
fileFilter: /\.less$/,
34+
process(data, file, _, options, build) {
35+
return compileString(data, file, options, build.resolve.bind(build));
36+
},
37+
});
6938

7039
async function compileString(
7140
data: string,
7241
filename: string,
73-
options: LessPluginOptions,
42+
options: StylesheetPluginOptions,
7443
resolver: PluginBuild['resolve'],
7544
): Promise<OnLoadResult> {
7645
const less = (lessPreprocessor ??= (await import('less')).default);

0 commit comments

Comments
 (0)