Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): support autoprefixer/tailwind CS…
Browse files Browse the repository at this point in the history
…S 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.
  • Loading branch information
clydin committed May 30, 2023
1 parent b66d94e commit 9aa9b52
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 329 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { extractLicenses } from './license-extractor';
import { BrowserEsbuildOptions, NormalizedBrowserOptions, normalizeOptions } from './options';
import { Schema as BrowserBuilderOptions } from './schema';
import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
import { shutdownSassWorkerPool } from './stylesheets/sass-plugin';
import { shutdownSassWorkerPool } from './stylesheets/sass-language';
import type { ChangedFiles } from './watcher';

const compressAsync = promisify(brotliCompress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { createHash } from 'node:crypto';
import path from 'node:path';
import { BundlerContext } from '../esbuild';
import { LoadResultCache } from '../load-result-cache';
import { createCssPlugin } from './css-plugin';
import { CssStylesheetLanguage } from './css-language';
import { createCssResourcePlugin } from './css-resource-plugin';
import { createLessPlugin } from './less-plugin';
import { createSassPlugin } from './sass-plugin';
import { LessStylesheetLanguage } from './less-language';
import { SassStylesheetLanguage } from './sass-language';
import { StylesheetPluginFactory } from './stylesheet-plugin-factory';

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

const pluginFactory = new StylesheetPluginFactory(
{
sourcemap: !!options.sourcemap,
includePaths,
inlineComponentData,
browsers: options.browsers,
tailwindConfiguration: options.tailwindConfiguration,
},
cache,
);

return {
absWorkingDir: options.workspaceRoot,
bundle: true,
Expand All @@ -62,31 +74,9 @@ export function createStylesheetBundleOptions(
conditions: ['style', 'sass'],
mainFields: ['style', 'sass'],
plugins: [
createSassPlugin(
{
sourcemap: !!options.sourcemap,
loadPaths: includePaths,
inlineComponentData,
},
cache,
),
createLessPlugin(
{
sourcemap: !!options.sourcemap,
includePaths,
inlineComponentData,
},
cache,
),
createCssPlugin(
{
sourcemap: !!options.sourcemap,
inlineComponentData,
browsers: options.browsers,
tailwindConfiguration: options.tailwindConfiguration,
},
cache,
),
pluginFactory.create(SassStylesheetLanguage),
pluginFactory.create(LessStylesheetLanguage),
pluginFactory.create(CssStylesheetLanguage),
createCssResourcePlugin(cache),
],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { StylesheetLanguage } from './stylesheet-plugin-factory';

export const CssStylesheetLanguage = Object.freeze<StylesheetLanguage>({
name: 'css',
componentFilter: /^css;/,
fileFilter: /\.css$/,
});

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/

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

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

export interface LessPluginOptions {
sourcemap: boolean;
includePaths?: string[];
inlineComponentData?: Record<string, string>;
}

interface LessException extends Error {
filename: string;
line: number;
Expand All @@ -34,43 +27,19 @@ function isLessException(error: unknown): error is LessException {
return !!error && typeof error === 'object' && 'column' in error;
}

export function createLessPlugin(options: LessPluginOptions, cache?: LoadResultCache): Plugin {
return {
name: 'angular-less',
setup(build: PluginBuild): void {
// Add a load callback to support inline Component styles
build.onLoad(
{ filter: /^less;/, namespace: 'angular:styles/component' },
createCachedLoad(cache, async (args) => {
const data = options.inlineComponentData?.[args.path];
assert(
typeof data === 'string',
`component style name should always be found [${args.path}]`,
);

const [, , filePath] = args.path.split(';', 3);

return compileString(data, filePath, options, build.resolve.bind(build));
}),
);

// Add a load callback to support files from disk
build.onLoad(
{ filter: /\.less$/ },
createCachedLoad(cache, async (args) => {
const data = await readFile(args.path, 'utf-8');

return compileString(data, args.path, options, build.resolve.bind(build));
}),
);
},
};
}
export const LessStylesheetLanguage = Object.freeze<StylesheetLanguage>({
name: 'less',
componentFilter: /^less;/,
fileFilter: /\.less$/,
process(data, file, _, options, build) {
return compileString(data, file, options, build.resolve.bind(build));
},
});

async function compileString(
data: string,
filename: string,
options: LessPluginOptions,
options: StylesheetPluginOptions,
resolver: PluginBuild['resolve'],
): Promise<OnLoadResult> {
const less = (lessPreprocessor ??= (await import('less')).default);
Expand Down
Loading

0 comments on commit 9aa9b52

Please sign in to comment.