Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): process stylesheet resources from…
Browse files Browse the repository at this point in the history
… 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.

(cherry picked from commit f474bf5)
  • Loading branch information
clydin committed Aug 9, 2022
1 parent dd47a5e commit 67b3a08
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 3 deletions.
Expand Up @@ -9,7 +9,14 @@
import type { CompilerHost } from '@angular/compiler-cli';
import { transformAsync } from '@babel/core';
import * as assert from 'assert';
import type { OnStartResult, PartialMessage, PartialNote, Plugin, PluginBuild } from 'esbuild';
import type {
OnStartResult,
OutputFile,
PartialMessage,
PartialNote,
Plugin,
PluginBuild,
} from 'esbuild';
import { promises as fs } from 'fs';
import * as path from 'path';
import ts from 'typescript';
Expand Down Expand Up @@ -190,9 +197,15 @@ export function createCompilerPlugin(
// The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files
let fileEmitter: FileEmitter | undefined;

// The stylesheet resources from component stylesheets that will be added to the build results output files
let stylesheetResourceFiles: OutputFile[];

build.onStart(async () => {
const result: OnStartResult = {};

// Reset stylesheet resource output files
stylesheetResourceFiles = [];

// Create TypeScript compiler host
const host = ts.createIncrementalCompilerHost(compilerOptions);

Expand All @@ -205,10 +218,14 @@ export function createCompilerPlugin(
return this.readFile(fileName) ?? '';
}

const { contents, errors, warnings } = await bundleStylesheetFile(fileName, styleOptions);
const { contents, resourceFiles, errors, warnings } = await bundleStylesheetFile(
fileName,
styleOptions,
);

(result.errors ??= []).push(...errors);
(result.warnings ??= []).push(...warnings);
stylesheetResourceFiles.push(...resourceFiles);

return contents;
};
Expand All @@ -224,7 +241,7 @@ export function createCompilerPlugin(
// or the file containing the inline component style text (containingFile).
const file = context.resourceFile ?? context.containingFile;

const { contents, errors, warnings } = await bundleStylesheetText(
const { contents, resourceFiles, errors, warnings } = await bundleStylesheetText(
data,
{
resolvePath: path.dirname(file),
Expand All @@ -235,6 +252,7 @@ export function createCompilerPlugin(

(result.errors ??= []).push(...errors);
(result.warnings ??= []).push(...warnings);
stylesheetResourceFiles.push(...resourceFiles);

return { content: contents };
};
Expand Down Expand Up @@ -429,6 +447,12 @@ export function createCompilerPlugin(
loader: 'js',
};
});

build.onEnd((result) => {
if (stylesheetResourceFiles.length) {
result.outputFiles?.push(...stylesheetResourceFiles);
}
});
},
};
}
Expand Down
@@ -0,0 +1,62 @@
/**
* @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 type { Plugin, PluginBuild } from 'esbuild';
import { readFile } from 'fs/promises';

/**
* Symbol marker used to indicate CSS resource resolution is being attempted.
* This is used to prevent an infinite loop within the plugin's resolve hook.
*/
const CSS_RESOURCE_RESOLUTION = Symbol('CSS_RESOURCE_RESOLUTION');

/**
* Creates an esbuild {@link Plugin} that loads all CSS url token references using the
* built-in esbuild `file` loader. A plugin is used to allow for all file extensions
* and types to be supported without needing to manually specify all extensions
* within the build configuration.
*
* @returns An esbuild {@link Plugin} instance.
*/
export function createCssResourcePlugin(): Plugin {
return {
name: 'angular-css-resource',
setup(build: PluginBuild): void {
build.onResolve({ filter: /.*/ }, async (args) => {
// Only attempt to resolve url tokens which only exist inside CSS.
// Also, skip this plugin if already attempting to resolve the url-token.
if (args.kind !== 'url-token' || args.pluginData?.[CSS_RESOURCE_RESOLUTION]) {
return null;
}

const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
pluginData[CSS_RESOURCE_RESOLUTION] = true;

const result = await build.resolve(args.path, {
importer,
kind,
namespace,
pluginData,
resolveDir,
});

return {
...result,
namespace: 'css-resource',
};
});

build.onLoad({ filter: /.*/, namespace: 'css-resource' }, async (args) => {
return {
contents: await readFile(args.path),
loader: 'file',
};
});
},
};
}
Expand Up @@ -168,6 +168,7 @@ export async function buildEsbuildBrowser(
outputNames: noInjectNames.includes(name) ? { media: outputNames.media } : outputNames,
includePaths: options.stylePreprocessorOptions?.includePaths,
preserveSymlinks: options.preserveSymlinks,
externalDependencies: options.externalDependencies,
},
);

Expand Down Expand Up @@ -354,6 +355,7 @@ async function bundleCode(
!!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'),
outputNames,
includePaths: options.stylePreprocessorOptions?.includePaths,
externalDependencies: options.externalDependencies,
},
),
],
Expand Down
Expand Up @@ -8,6 +8,7 @@

import type { BuildOptions, OutputFile } from 'esbuild';
import * as path from 'path';
import { createCssResourcePlugin } from './css-resource-plugin';
import { bundle } from './esbuild';
import { createSassPlugin } from './sass-plugin';

Expand All @@ -18,6 +19,7 @@ export interface BundleStylesheetOptions {
sourcemap: boolean | 'external' | 'inline';
outputNames?: { bundles?: string; media?: string };
includePaths?: string[];
externalDependencies?: string[];
}

async function bundleStylesheet(
Expand All @@ -38,10 +40,12 @@ async function bundleStylesheet(
write: false,
platform: 'browser',
preserveSymlinks: options.preserveSymlinks,
external: options.externalDependencies,
conditions: ['style', 'sass'],
mainFields: ['style', 'sass'],
plugins: [
createSassPlugin({ sourcemap: !!options.sourcemap, includePaths: options.includePaths }),
createCssResourcePlugin(),
],
});

Expand Down

0 comments on commit 67b3a08

Please sign in to comment.