Skip to content

Commit e84a40b

Browse files
committed
fix(@angular-devkit/build-angular): process stylesheet resources from url tokens with esbuild browser builder
Stylesheet url tokens (`url(....)`) will now be processed when using the esbuild-based experimental browser application builder. The paths will be resolved via the bundler's resolution system and then loaded via the bundler's `file` loader. The functionality is implemented using an esbuild plugin to allow for all file types to be supported without the need to manually specify each current and future file extension within the build configuration. The `externalDependencies` option also applies to the referenced resources. This allows for resource paths specified with the option to remain unprocessed within the application output. This is useful if the relative path for the resource does not exist on disk but will be available when the application is deployed.
1 parent 36b68a5 commit e84a40b

File tree

3 files changed

+71
-1
lines changed

3 files changed

+71
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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 { Plugin, PluginBuild } from 'esbuild';
10+
import { readFile } from 'fs/promises';
11+
12+
/**
13+
* Symbol marker used to indicate CSS resource resolution is being attempted.
14+
* This is used to prevent an infinite loop within the plugin's resolve hook.
15+
*/
16+
const CSS_RESOURCE_RESOLUTION = Symbol('CSS_RESOURCE_RESOLUTION');
17+
18+
/**
19+
* Creates an esbuild {@link Plugin} that loads all CSS url token references using the
20+
* built-in esbuild `file` loader. A plugin is used to allow for all file extensions
21+
* and types to be supported without needing to manually specify all extensions
22+
* within the build configuration.
23+
*
24+
* @returns An esbuild {@link Plugin} instance.
25+
*/
26+
export function createCssResourcePlugin(): Plugin {
27+
return {
28+
name: 'angular-css-resource',
29+
setup(build: PluginBuild): void {
30+
build.onResolve({ filter: /.*/ }, async (args) => {
31+
// Only attempt to resolve url tokens which only exist inside CSS.
32+
// Also, skip this plugin if already attempting to resolve the url-token.
33+
if (args.kind !== 'url-token' || args.pluginData?.[CSS_RESOURCE_RESOLUTION]) {
34+
return null;
35+
}
36+
37+
const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
38+
pluginData[CSS_RESOURCE_RESOLUTION] = true;
39+
40+
const result = await build.resolve(args.path, {
41+
importer,
42+
kind,
43+
namespace,
44+
pluginData,
45+
resolveDir,
46+
});
47+
48+
return {
49+
...result,
50+
namespace: 'css-resource',
51+
};
52+
});
53+
54+
build.onLoad({ filter: /.*/, namespace: 'css-resource' }, async (args) => {
55+
return {
56+
contents: await readFile(args.path),
57+
loader: 'file',
58+
};
59+
});
60+
},
61+
};
62+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export async function buildEsbuildBrowser(
168168
outputNames: noInjectNames.includes(name) ? { media: outputNames.media } : outputNames,
169169
includePaths: options.stylePreprocessorOptions?.includePaths,
170170
preserveSymlinks: options.preserveSymlinks,
171+
externalDependencies: options.externalDependencies,
171172
},
172173
);
173174

@@ -354,6 +355,7 @@ async function bundleCode(
354355
!!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'),
355356
outputNames,
356357
includePaths: options.stylePreprocessorOptions?.includePaths,
358+
externalDependencies: options.externalDependencies,
357359
},
358360
),
359361
],

Diff for: packages/angular_devkit/build_angular/src/builders/browser-esbuild/stylesheets.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import type { BuildOptions, OutputFile } from 'esbuild';
1010
import * as path from 'path';
11+
import { createCssResourcePlugin } from './css-resource-plugin';
1112
import { bundle } from './esbuild';
1213
import { createSassPlugin } from './sass-plugin';
1314

@@ -18,6 +19,7 @@ export interface BundleStylesheetOptions {
1819
sourcemap: boolean | 'external' | 'inline';
1920
outputNames?: { bundles?: string; media?: string };
2021
includePaths?: string[];
22+
externalDependencies?: string[];
2123
}
2224

2325
async function bundleStylesheet(
@@ -42,9 +44,13 @@ async function bundleStylesheet(
4244
write: false,
4345
platform: 'browser',
4446
preserveSymlinks: options.preserveSymlinks,
47+
external: options.externalDependencies,
4548
conditions: ['style', 'sass'],
4649
mainFields: ['style', 'sass'],
47-
plugins: [createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths })],
50+
plugins: [
51+
createSassPlugin({ sourcemap: !!options.sourcemap, loadPaths }),
52+
createCssResourcePlugin(),
53+
],
4854
});
4955

5056
// Extract the result of the bundling from the output files

0 commit comments

Comments
 (0)