mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-15 18:13:38 +08:00
refactor(@angular-devkit/build-angular): use helper to setup esbuild plugin load caching
Within the esbuild-based browser application builder, a helper function has been introduced to streamline the use of the load result cache within the internal plugins. This removes repeat code that would otherwise be needed. The ability to use a load result cache with the global script processing has also been added but has not yet been enabled.
This commit is contained in:
parent
4c82bb8e81
commit
ffea33fc45
@ -12,7 +12,8 @@ import assert from 'node:assert';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { assertIsError } from '../../utils/error';
|
||||
import { NormalizedBrowserOptions } from './options';
|
||||
import { LoadResultCache, createCachedLoad } from './load-result-cache';
|
||||
import type { NormalizedBrowserOptions } from './options';
|
||||
import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
|
||||
|
||||
/**
|
||||
@ -24,6 +25,7 @@ import { createSourcemapIngorelistPlugin } from './sourcemap-ignorelist-plugin';
|
||||
export function createGlobalScriptsBundleOptions(
|
||||
options: NormalizedBrowserOptions,
|
||||
initial: boolean,
|
||||
loadCache?: LoadResultCache,
|
||||
): BuildOptions | undefined {
|
||||
const {
|
||||
globalScripts,
|
||||
@ -91,17 +93,23 @@ export function createGlobalScriptsBundleOptions(
|
||||
external: true,
|
||||
};
|
||||
});
|
||||
build.onLoad({ filter: /./, namespace }, async (args) => {
|
||||
const files = globalScripts.find(({ name }) => name === args.path.slice(0, -3))?.files;
|
||||
build.onLoad(
|
||||
{ filter: /./, namespace },
|
||||
createCachedLoad(loadCache, async (args) => {
|
||||
const files = globalScripts.find(
|
||||
({ name }) => name === args.path.slice(0, -3),
|
||||
)?.files;
|
||||
assert(files, `Invalid operation: global scripts name not found [${args.path}]`);
|
||||
|
||||
// Global scripts are concatenated using magic-string instead of bundled via esbuild.
|
||||
const bundleContent = new Bundle();
|
||||
const watchFiles = [];
|
||||
for (const filename of files) {
|
||||
let fileContent;
|
||||
try {
|
||||
// Attempt to read as a relative path from the workspace root
|
||||
fileContent = await readFile(path.join(workspaceRoot, filename), 'utf-8');
|
||||
watchFiles.push(filename);
|
||||
} catch (e) {
|
||||
assertIsError(e);
|
||||
if (e.code !== 'ENOENT') {
|
||||
@ -125,6 +133,7 @@ export function createGlobalScriptsBundleOptions(
|
||||
};
|
||||
}
|
||||
|
||||
watchFiles.push(path.relative(resolveResult.path, workspaceRoot));
|
||||
fileContent = await readFile(resolveResult.path, 'utf-8');
|
||||
}
|
||||
|
||||
@ -134,8 +143,10 @@ export function createGlobalScriptsBundleOptions(
|
||||
return {
|
||||
contents: bundleContent.toString(),
|
||||
loader: 'js',
|
||||
watchFiles,
|
||||
};
|
||||
});
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -6,13 +6,38 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import type { OnLoadResult } from 'esbuild';
|
||||
import type { OnLoadResult, PluginBuild } from 'esbuild';
|
||||
|
||||
export interface LoadResultCache {
|
||||
get(path: string): OnLoadResult | undefined;
|
||||
put(path: string, result: OnLoadResult): Promise<void>;
|
||||
}
|
||||
|
||||
export function createCachedLoad(
|
||||
cache: LoadResultCache | undefined,
|
||||
callback: Parameters<PluginBuild['onLoad']>[1],
|
||||
): Parameters<PluginBuild['onLoad']>[1] {
|
||||
if (cache === undefined) {
|
||||
return callback;
|
||||
}
|
||||
|
||||
return async (args) => {
|
||||
const loadCacheKey = `${args.namespace}:${args.path}`;
|
||||
let result: OnLoadResult | null | undefined = cache.get(loadCacheKey);
|
||||
|
||||
if (result === undefined) {
|
||||
result = await callback(args);
|
||||
|
||||
// Do not cache null or undefined or results with errors
|
||||
if (result && result.errors === undefined) {
|
||||
await cache.put(loadCacheKey, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export class MemoryLoadResultCache implements LoadResultCache {
|
||||
#loadResults = new Map<string, OnLoadResult>();
|
||||
#fileDependencies = new Map<string, Set<string>>();
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import type { BuildOptions, OutputFile } from 'esbuild';
|
||||
import { createHash } from 'node:crypto';
|
||||
import path from 'node:path';
|
||||
import { BundlerContext } from '../esbuild';
|
||||
import { LoadResultCache } from '../load-result-cache';
|
||||
@ -108,7 +109,11 @@ export async function bundleComponentStylesheet(
|
||||
cache?: LoadResultCache,
|
||||
) {
|
||||
const namespace = 'angular:styles/component';
|
||||
const entry = [language, componentStyleCounter++, filename].join(';');
|
||||
// Use a hash of the inline stylesheet content to ensure a consistent identifier. External stylesheets will resolve
|
||||
// to the actual stylesheet file path.
|
||||
// TODO: Consider xxhash instead for hashing
|
||||
const id = inline ? createHash('sha256').update(data).digest('hex') : componentStyleCounter++;
|
||||
const entry = [language, id, filename].join(';');
|
||||
|
||||
const buildOptions = createStylesheetBundleOptions(options, cache, { [entry]: data });
|
||||
buildOptions.entryPoints = [`${namespace};${entry}`];
|
||||
|
@ -16,7 +16,7 @@ import type {
|
||||
FileImporterWithRequestContextOptions,
|
||||
SassWorkerImplementation,
|
||||
} from '../../../sass/sass-service';
|
||||
import type { LoadResultCache } from '../load-result-cache';
|
||||
import { LoadResultCache, createCachedLoad } from '../load-result-cache';
|
||||
|
||||
export interface SassPluginOptions {
|
||||
sourcemap: boolean;
|
||||
@ -63,43 +63,33 @@ export function createSassPlugin(options: SassPluginOptions, cache?: LoadResultC
|
||||
return result;
|
||||
};
|
||||
|
||||
build.onLoad({ filter: /^s[ac]ss;/, namespace: 'angular:styles/component' }, async (args) => {
|
||||
// Load inline component stylesheets
|
||||
build.onLoad(
|
||||
{ filter: /^s[ac]ss;/, 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}]`,
|
||||
);
|
||||
|
||||
let result = cache?.get(data);
|
||||
if (result === undefined) {
|
||||
const [language, , filePath] = args.path.split(';', 3);
|
||||
const syntax = language === 'sass' ? 'indented' : 'scss';
|
||||
|
||||
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 compileString(data, filePath, syntax, options, resolveUrl);
|
||||
}),
|
||||
);
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => {
|
||||
let result = cache?.get(args.path);
|
||||
if (result === undefined) {
|
||||
// Load file stylesheets
|
||||
build.onLoad(
|
||||
{ filter: /\.s[ac]ss$/ },
|
||||
createCachedLoad(cache, async (args) => {
|
||||
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 result;
|
||||
});
|
||||
return compileString(data, args.path, syntax, options, resolveUrl);
|
||||
}),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user