Skip to content

Commit 01b3bcf

Browse files
clydinangular-robot[bot]
authored andcommittedFeb 8, 2023
feat(@angular-devkit/build-angular): add Less stylesheet support to experimental esbuild-based builder
When using the experimental esbuild-based browser application builder, stylesheets written in the Less stylesheet language can now be used throughout an application. The support allows Less stylesheets to be used in all locations where CSS and/or Sass can be used. This includes global stylesheets and both inline and external component styles. When using inline component styles, the `inlineLanguageStyle` build option must be set to `less`. Currently, import resolution within a Less stylesheet is limited to default Less behavior which does not include full node package resolution. Full resolution behavior will be added in a future change.
1 parent c07fbf6 commit 01b3bcf

File tree

6 files changed

+124
-11
lines changed

6 files changed

+124
-11
lines changed
 

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
"@types/inquirer": "^8.0.0",
107107
"@types/jasmine": "~4.3.0",
108108
"@types/karma": "^6.3.0",
109+
"@types/less": "^3.0.3",
109110
"@types/loader-utils": "^2.0.0",
110111
"@types/minimatch": "5.1.2",
111112
"@types/node": "^14.15.0",

‎packages/angular_devkit/build_angular/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ ts_library(
125125
"@npm//@types/glob",
126126
"@npm//@types/inquirer",
127127
"@npm//@types/karma",
128+
"@npm//@types/less",
128129
"@npm//@types/loader-utils",
129130
"@npm//@types/node",
130131
"@npm//@types/parse5-html-rewriting-stream",

‎packages/angular_devkit/build_angular/src/builders/browser-esbuild/experimental-warnings.ts

-4
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,4 @@ export function logExperimentalWarnings(options: BrowserBuilderOptions, context:
5757
`The '${unsupportedOption}' option is currently unsupported by this experimental builder and will be ignored.`,
5858
);
5959
}
60-
61-
if (options.inlineStyleLanguage === 'less') {
62-
context.logger.warn('The less stylesheet preprocessor is not currently supported.');
63-
}
6460
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 type { OnLoadResult, Plugin, PluginBuild } from 'esbuild';
10+
import assert from 'node:assert';
11+
import { readFile } from 'node:fs/promises';
12+
13+
/**
14+
* The lazy-loaded instance of the less stylesheet preprocessor.
15+
* It is only imported and initialized if a less stylesheet is used.
16+
*/
17+
let lessPreprocessor: typeof import('less') | undefined;
18+
19+
export interface LessPluginOptions {
20+
sourcemap: boolean;
21+
includePaths?: string[];
22+
inlineComponentData?: Record<string, string>;
23+
}
24+
25+
interface LessException extends Error {
26+
filename: string;
27+
line: number;
28+
column: number;
29+
extract?: string[];
30+
}
31+
32+
function isLessException(error: unknown): error is LessException {
33+
return !!error && typeof error === 'object' && 'column' in error;
34+
}
35+
36+
export function createLessPlugin(options: LessPluginOptions): Plugin {
37+
return {
38+
name: 'angular-less',
39+
setup(build: PluginBuild): void {
40+
// Add a load callback to support inline Component styles
41+
build.onLoad({ filter: /^less;/, namespace: 'angular:styles/component' }, async (args) => {
42+
const data = options.inlineComponentData?.[args.path];
43+
assert(data, `component style name should always be found [${args.path}]`);
44+
45+
const [, , filePath] = args.path.split(';', 3);
46+
47+
return compileString(data, filePath, options);
48+
});
49+
50+
// Add a load callback to support files from disk
51+
build.onLoad({ filter: /\.less$/ }, async (args) => {
52+
const data = await readFile(args.path, 'utf-8');
53+
54+
return compileString(data, args.path, options);
55+
});
56+
},
57+
};
58+
}
59+
60+
async function compileString(
61+
data: string,
62+
filename: string,
63+
options: LessPluginOptions,
64+
): Promise<OnLoadResult> {
65+
const less = (lessPreprocessor ??= (await import('less')).default);
66+
67+
try {
68+
const result = await less.render(data, {
69+
filename,
70+
paths: options.includePaths,
71+
rewriteUrls: 'all',
72+
sourceMap: options.sourcemap
73+
? {
74+
sourceMapFileInline: true,
75+
outputSourceFiles: true,
76+
}
77+
: undefined,
78+
} as Less.Options);
79+
80+
return {
81+
contents: result.css,
82+
loader: 'css',
83+
};
84+
} catch (error) {
85+
if (isLessException(error)) {
86+
return {
87+
errors: [
88+
{
89+
text: error.message,
90+
location: {
91+
file: error.filename,
92+
line: error.line,
93+
column: error.column,
94+
// Middle element represents the line containing the error
95+
lineText: error.extract && error.extract[Math.trunc(error.extract.length / 2)],
96+
},
97+
},
98+
],
99+
loader: 'css',
100+
};
101+
}
102+
103+
throw error;
104+
}
105+
}

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

+12-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { BuildOptions, OutputFile } from 'esbuild';
1010
import * as path from 'node:path';
1111
import { createCssResourcePlugin } from './css-resource-plugin';
1212
import { BundlerContext } from './esbuild';
13+
import { createLessPlugin } from './less-plugin';
1314
import { createSassPlugin } from './sass-plugin';
1415

1516
/**
@@ -32,6 +33,11 @@ export function createStylesheetBundleOptions(
3233
options: BundleStylesheetOptions,
3334
inlineComponentData?: Record<string, string>,
3435
): BuildOptions & { plugins: NonNullable<BuildOptions['plugins']> } {
36+
// Ensure preprocessor include paths are absolute based on the workspace root
37+
const includePaths = options.includePaths?.map((includePath) =>
38+
path.resolve(options.workspaceRoot, includePath),
39+
);
40+
3541
return {
3642
absWorkingDir: options.workspaceRoot,
3743
bundle: true,
@@ -52,10 +58,12 @@ export function createStylesheetBundleOptions(
5258
plugins: [
5359
createSassPlugin({
5460
sourcemap: !!options.sourcemap,
55-
// Ensure Sass load paths are absolute based on the workspace root
56-
loadPaths: options.includePaths?.map((includePath) =>
57-
path.resolve(options.workspaceRoot, includePath),
58-
),
61+
loadPaths: includePaths,
62+
inlineComponentData,
63+
}),
64+
createLessPlugin({
65+
sourcemap: !!options.sourcemap,
66+
includePaths,
5967
inlineComponentData,
6068
}),
6169
createCssResourcePlugin(),

‎yarn.lock

+5-3
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@
121121

122122
"@angular/build-tooling@https://github.com/angular/dev-infra-private-build-tooling-builds.git#ffd5dec0bf78a2c8ff068482ad3c8434c21b54c7":
123123
version "0.0.0-fb077c1937f280aac6327969fa3ab50f98b4d25a"
124-
uid ffd5dec0bf78a2c8ff068482ad3c8434c21b54c7
125124
resolved "https://github.com/angular/dev-infra-private-build-tooling-builds.git#ffd5dec0bf78a2c8ff068482ad3c8434c21b54c7"
126125
dependencies:
127126
"@angular-devkit/build-angular" "15.2.0-next.3"
@@ -307,7 +306,6 @@
307306

308307
"@angular/ng-dev@https://github.com/angular/dev-infra-private-ng-dev-builds.git#f4601b680d6d0017880115cc8ee99249c34f0c12":
309308
version "0.0.0-fb077c1937f280aac6327969fa3ab50f98b4d25a"
310-
uid f4601b680d6d0017880115cc8ee99249c34f0c12
311309
resolved "https://github.com/angular/dev-infra-private-ng-dev-builds.git#f4601b680d6d0017880115cc8ee99249c34f0c12"
312310
dependencies:
313311
"@yarnpkg/lockfile" "^1.1.0"
@@ -3170,6 +3168,11 @@
31703168
"@types/node" "*"
31713169
log4js "^6.4.1"
31723170

3171+
"@types/less@^3.0.3":
3172+
version "3.0.3"
3173+
resolved "https://registry.yarnpkg.com/@types/less/-/less-3.0.3.tgz#f9451dbb9548d25391107d65d6401a0cfb15db92"
3174+
integrity sha512-1YXyYH83h6We1djyoUEqTlVyQtCfJAFXELSKW2ZRtjHD4hQ82CC4lvrv5D0l0FLcKBaiPbXyi3MpMsI9ZRgKsw==
3175+
31733176
"@types/loader-utils@^2.0.0":
31743177
version "2.0.3"
31753178
resolved "https://registry.yarnpkg.com/@types/loader-utils/-/loader-utils-2.0.3.tgz#fbc2337358f8f4a7dc532ac0a3646c74275edf2d"
@@ -10107,7 +10110,6 @@ sass@1.58.0:
1010710110

1010810111
"sauce-connect-proxy@https://saucelabs.com/downloads/sc-4.8.1-linux.tar.gz":
1010910112
version "0.0.0"
10110-
uid "9c16682e4c9716734432789884f868212f95f563"
1011110113
resolved "https://saucelabs.com/downloads/sc-4.8.1-linux.tar.gz#9c16682e4c9716734432789884f868212f95f563"
1011210114

1011310115
saucelabs@^1.5.0:

0 commit comments

Comments
 (0)
Please sign in to comment.