Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(@angular-devkit/build-angular): cache Sass in memory with esbuild watch mode #24962

Merged
merged 1 commit into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { pathToFileURL } from 'node:url';
import ts from 'typescript';
import { maxWorkers } from '../../../utils/environment-options';
import { JavaScriptTransformer } from '../javascript-transformer';
import { LoadResultCache, MemoryLoadResultCache } from '../load-result-cache';
import {
logCumulativeDurations,
profileAsync,
Expand Down Expand Up @@ -124,12 +125,14 @@ export class SourceFileCache extends Map<string, ts.SourceFile> {
readonly modifiedFiles = new Set<string>();
readonly babelFileCache = new Map<string, Uint8Array>();
readonly typeScriptFileCache = new Map<string, Uint8Array>();
readonly loadResultCache = new MemoryLoadResultCache();

invalidate(files: Iterable<string>): void {
this.modifiedFiles.clear();
for (let file of files) {
this.babelFileCache.delete(file);
this.typeScriptFileCache.delete(pathToFileURL(file).href);
this.loadResultCache.invalidate(file);

// Normalize separators to allow matching TypeScript Host paths
if (USING_WINDOWS) {
Expand All @@ -150,6 +153,7 @@ export interface CompilerPluginOptions {
thirdPartySourcemaps?: boolean;
fileReplacements?: Record<string, string>;
sourceFileCache?: SourceFileCache;
loadResultCache?: LoadResultCache;
}

// eslint-disable-next-line max-lines-per-function
Expand Down Expand Up @@ -272,6 +276,7 @@ export function createCompilerPlugin(
filename,
!stylesheetFile,
styleOptions,
pluginOptions.loadResultCache,
);

const { contents, resourceFiles, errors, warnings } = stylesheetResult;
Expand Down Expand Up @@ -415,7 +420,12 @@ export function createCompilerPlugin(

// Setup bundling of component templates and stylesheets when in JIT mode
if (pluginOptions.jit) {
setupJitPluginCallbacks(build, styleOptions, stylesheetResourceFiles);
setupJitPluginCallbacks(
build,
styleOptions,
stylesheetResourceFiles,
pluginOptions.loadResultCache,
);
}

build.onEnd((result) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import type { OutputFile, PluginBuild } from 'esbuild';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { LoadResultCache } from '../load-result-cache';
import { BundleStylesheetOptions, bundleComponentStylesheet } from '../stylesheets';
import {
JIT_NAMESPACE_REGEXP,
Expand Down Expand Up @@ -65,6 +66,7 @@ export function setupJitPluginCallbacks(
build: PluginBuild,
styleOptions: BundleStylesheetOptions & { inlineStyleLanguage: string },
stylesheetResourceFiles: OutputFile[],
cache?: LoadResultCache,
): void {
const root = build.initialOptions.absWorkingDir ?? '';

Expand Down Expand Up @@ -110,6 +112,7 @@ export function setupJitPluginCallbacks(
entry.path,
entry.contents !== undefined,
styleOptions,
cache,
);

stylesheetResourceFiles.push(...resourceFiles);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { BundlerContext, logMessages } from './esbuild';
import { logExperimentalWarnings } from './experimental-warnings';
import { createGlobalScriptsBundleOptions } from './global-scripts';
import { extractLicenses } from './license-extractor';
import { LoadResultCache } from './load-result-cache';
import { NormalizedBrowserOptions, normalizeOptions } from './options';
import { shutdownSassWorkerPool } from './sass-plugin';
import { Schema as BrowserBuilderOptions } from './schema';
Expand Down Expand Up @@ -122,7 +123,7 @@ async function execute(
new BundlerContext(
workspaceRoot,
!!options.watch,
createGlobalStylesBundleOptions(options, target, browsers),
createGlobalStylesBundleOptions(options, target, browsers, codeBundleCache?.loadResultCache),
);

const globalScriptsBundleContext = new BundlerContext(
Expand Down Expand Up @@ -390,6 +391,7 @@ function createCodeBundleOptions(
advancedOptimizations,
fileReplacements,
sourceFileCache,
loadResultCache: sourceFileCache?.loadResultCache,
},
// Component stylesheet options
{
Expand Down Expand Up @@ -508,6 +510,7 @@ function createGlobalStylesBundleOptions(
options: NormalizedBrowserOptions,
target: string[],
browsers: string[],
cache?: LoadResultCache,
): BuildOptions {
const {
workspaceRoot,
Expand All @@ -521,18 +524,21 @@ function createGlobalStylesBundleOptions(
tailwindConfiguration,
} = options;

const buildOptions = createStylesheetBundleOptions({
workspaceRoot,
optimization: !!optimizationOptions.styles.minify,
sourcemap: !!sourcemapOptions.styles,
preserveSymlinks,
target,
externalDependencies,
outputNames,
includePaths: stylePreprocessorOptions?.includePaths,
browsers,
tailwindConfiguration,
});
const buildOptions = createStylesheetBundleOptions(
{
workspaceRoot,
optimization: !!optimizationOptions.styles.minify,
sourcemap: !!sourcemapOptions.styles,
preserveSymlinks,
target,
externalDependencies,
outputNames,
includePaths: stylePreprocessorOptions?.includePaths,
browsers,
tailwindConfiguration,
},
cache,
);
buildOptions.legalComments = options.extractLicenses ? 'none' : 'eof';

const namespace = 'angular:styles/global';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @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 { OnLoadResult } from 'esbuild';

export interface LoadResultCache {
get(path: string): OnLoadResult | undefined;
put(path: string, result: OnLoadResult): Promise<void>;
}

export class MemoryLoadResultCache implements LoadResultCache {
#loadResults = new Map<string, OnLoadResult>();
#fileDependencies = new Map<string, Set<string>>();

get(path: string): OnLoadResult | undefined {
return this.#loadResults.get(path);
}

async put(path: string, result: OnLoadResult): Promise<void> {
this.#loadResults.set(path, result);
if (result.watchFiles) {
for (const watchFile of result.watchFiles) {
let affected = this.#fileDependencies.get(watchFile);
if (affected === undefined) {
affected = new Set();
this.#fileDependencies.set(watchFile, affected);
}
affected.add(path);
}
}
}

invalidate(path: string): boolean {
const affected = this.#fileDependencies.get(path);
let found = false;

if (affected) {
affected.forEach((a) => (found ||= this.#loadResults.delete(a)));
this.#fileDependencies.delete(path);
}

found ||= this.#loadResults.delete(path);

return found;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
FileImporterWithRequestContextOptions,
SassWorkerImplementation,
} from '../../sass/sass-service';
import type { LoadResultCache } from './load-result-cache';

export interface SassPluginOptions {
sourcemap: boolean;
Expand All @@ -34,7 +35,7 @@ export function shutdownSassWorkerPool(): void {
sassWorkerPool = undefined;
}

export function createSassPlugin(options: SassPluginOptions): Plugin {
export function createSassPlugin(options: SassPluginOptions, cache?: LoadResultCache): Plugin {
return {
name: 'angular-sass',
setup(build: PluginBuild): void {
Expand Down Expand Up @@ -69,17 +70,35 @@ export function createSassPlugin(options: SassPluginOptions): Plugin {
`component style name should always be found [${args.path}]`,
);

const [language, , filePath] = args.path.split(';', 3);
const syntax = language === 'sass' ? 'indented' : 'scss';
let result = cache?.get(data);
if (result === undefined) {
const [language, , filePath] = args.path.split(';', 3);
const syntax = language === 'sass' ? 'indented' : 'scss';

return compileString(data, filePath, syntax, options, resolveUrl);
result = await compileString(data, filePath, syntax, options, resolveUrl);
if (result.errors === undefined) {
// Cache the result if there were no errors
await cache?.put(data, result);
}
}

return result;
});

build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => {
const data = await readFile(args.path, 'utf-8');
const syntax = extname(args.path).toLowerCase() === '.sass' ? 'indented' : 'scss';
let result = cache?.get(args.path);
if (result === undefined) {
const data = await readFile(args.path, 'utf-8');
const syntax = extname(args.path).toLowerCase() === '.sass' ? 'indented' : 'scss';

result = await compileString(data, args.path, syntax, options, resolveUrl);
if (result.errors === undefined) {
// Cache the result if there were no errors
await cache?.put(args.path, result);
}
}

return compileString(data, args.path, syntax, options, resolveUrl);
return result;
});
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { createCssPlugin } from './css-plugin';
import { createCssResourcePlugin } from './css-resource-plugin';
import { BundlerContext } from './esbuild';
import { createLessPlugin } from './less-plugin';
import { LoadResultCache } from './load-result-cache';
import { createSassPlugin } from './sass-plugin';

/**
Expand All @@ -34,6 +35,7 @@ export interface BundleStylesheetOptions {

export function createStylesheetBundleOptions(
options: BundleStylesheetOptions,
cache?: LoadResultCache,
inlineComponentData?: Record<string, string>,
): BuildOptions & { plugins: NonNullable<BuildOptions['plugins']> } {
// Ensure preprocessor include paths are absolute based on the workspace root
Expand All @@ -59,11 +61,14 @@ export function createStylesheetBundleOptions(
conditions: ['style', 'sass'],
mainFields: ['style', 'sass'],
plugins: [
createSassPlugin({
sourcemap: !!options.sourcemap,
loadPaths: includePaths,
inlineComponentData,
}),
createSassPlugin(
{
sourcemap: !!options.sourcemap,
loadPaths: includePaths,
inlineComponentData,
},
cache,
),
createLessPlugin({
sourcemap: !!options.sourcemap,
includePaths,
Expand Down Expand Up @@ -100,11 +105,12 @@ export async function bundleComponentStylesheet(
filename: string,
inline: boolean,
options: BundleStylesheetOptions,
cache?: LoadResultCache,
) {
const namespace = 'angular:styles/component';
const entry = [language, componentStyleCounter++, filename].join(';');

const buildOptions = createStylesheetBundleOptions(options, { [entry]: data });
const buildOptions = createStylesheetBundleOptions(options, cache, { [entry]: data });
buildOptions.entryPoints = [`${namespace};${entry}`];
buildOptions.plugins.push({
name: 'angular-component-styles',
Expand Down