From 5904afd1de3ffa0bb6cd1757795ba9abfce9e523 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Fri, 1 Oct 2021 08:22:28 +0200 Subject: [PATCH] feat(@angular-devkit/build-angular): enable disk cache by default and provide configurable options Persistent disk build cache is now enabled by default. A number of options have been added to allow fine tuning of the cache. The options can be configuration in `cli.cache` section in the `angular.json` as shown below. - `enabled`: Configure whether disk caching is enabled. Defaults to `true` - `environment`: Configure in which environment disk cache is enabled. Valid values `ci`, `local` or `all`. Defaults to: `local` - `path`: cache base path. Defaults to `.angular/cache` DEPRECATED: `NG_BUILD_CACHE` environment variable option will be removed in the next major version. Configure `cli.cache` in the workspace configuration instead. BREAKING CHANGE: `NG_PERSISTENT_BUILD_CACHE` environment variable option no longer have effect. Configure `cli.cache` in the workspace configuration instead. ```json { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "cli": { "cache": { "enabled": true, "path": ".custom-cache-path", "environment": "all" } } ... } ``` --- package.json | 2 - packages/angular/cli/commands/config-impl.ts | 6 +- .../cli/lib/config/workspace-schema.json | 19 +++++ .../angular_devkit/build_angular/BUILD.bazel | 2 - .../angular_devkit/build_angular/package.json | 1 - .../src/builders/browser/index.ts | 9 ++- .../src/builders/dev-server/index.ts | 78 +++++++++++-------- .../src/builders/ng-packagr/index.ts | 53 ++++++++----- .../build_angular/src/utils/build-options.ts | 3 +- .../build_angular/src/utils/cache-path.ts | 20 ----- .../src/utils/environment-options.ts | 41 +++++----- .../utils/index-file/index-html-generator.ts | 2 + .../src/utils/index-file/inline-fonts.ts | 24 +++--- .../src/utils/normalize-builder-schema.ts | 5 +- .../src/utils/normalize-cache.ts | 56 +++++++++++++ .../src/utils/webpack-browser-config.ts | 10 ++- .../src/webpack/configs/common.ts | 18 ++--- .../test/hello-world-app/angular.json | 4 +- .../test/hello-world-lib/angular.json | 3 + .../legacy-cli/e2e/tests/build/disk-cache.ts | 53 +++++++++++++ 20 files changed, 281 insertions(+), 128 deletions(-) delete mode 100644 packages/angular_devkit/build_angular/src/utils/cache-path.ts create mode 100644 packages/angular_devkit/build_angular/src/utils/normalize-cache.ts create mode 100644 tests/legacy-cli/e2e/tests/build/disk-cache.ts diff --git a/package.json b/package.json index eabe632dd4bc..3d795e0bb596 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,6 @@ "@types/copy-webpack-plugin": "^8.0.0", "@types/debug": "^4.1.2", "@types/express": "^4.16.0", - "@types/find-cache-dir": "^3.0.0", "@types/glob": "^7.1.1", "@types/http-proxy": "^1.17.4", "@types/inquirer": "^8.0.0", @@ -149,7 +148,6 @@ "eslint-plugin-import": "2.24.2", "express": "4.17.1", "fast-json-stable-stringify": "2.1.0", - "find-cache-dir": "3.3.2", "font-awesome": "^4.7.0", "gh-got": "^9.0.0", "git-raw-commits": "^2.0.0", diff --git a/packages/angular/cli/commands/config-impl.ts b/packages/angular/cli/commands/config-impl.ts index 35d212ffb687..35efbeddecb9 100644 --- a/packages/angular/cli/commands/config-impl.ts +++ b/packages/angular/cli/commands/config-impl.ts @@ -21,10 +21,14 @@ const validCliPaths = new Map< ['cli.warnings.versionMismatch', undefined], ['cli.defaultCollection', undefined], ['cli.packageManager', undefined], - ['cli.analytics', undefined], + ['cli.analyticsSharing.tracking', undefined], ['cli.analyticsSharing.uuid', (v) => (v ? `${v}` : uuidV4())], + + ['cli.cache.enabled', undefined], + ['cli.cache.environment', undefined], + ['cli.cache.path', undefined], ]); /** diff --git a/packages/angular/cli/lib/config/workspace-schema.json b/packages/angular/cli/lib/config/workspace-schema.json index b40cee9713bc..c0f154bbf786 100644 --- a/packages/angular/cli/lib/config/workspace-schema.json +++ b/packages/angular/cli/lib/config/workspace-schema.json @@ -76,6 +76,25 @@ "type": "string" } } + }, + "cache": { + "description": "Control disk cache.", + "type": "object", + "properties": { + "environment": { + "description": "Configure in which environment disk cache is enabled.", + "type": "string", + "enum": ["local", "ci", "all"] + }, + "enabled": { + "description": "Configure whether disk caching is enabled.", + "type": "boolean" + }, + "path": { + "description": "Cache base path.", + "type": "string" + } + } } }, "additionalProperties": false diff --git a/packages/angular_devkit/build_angular/BUILD.bazel b/packages/angular_devkit/build_angular/BUILD.bazel index 54bf345fb49d..8162eb81ca63 100644 --- a/packages/angular_devkit/build_angular/BUILD.bazel +++ b/packages/angular_devkit/build_angular/BUILD.bazel @@ -119,7 +119,6 @@ ts_library( "@npm//@types/cacache", "@npm//@types/caniuse-lite", "@npm//@types/copy-webpack-plugin", - "@npm//@types/find-cache-dir", "@npm//@types/glob", "@npm//@types/inquirer", "@npm//@types/karma", @@ -145,7 +144,6 @@ ts_library( "@npm//css-loader", "@npm//esbuild", "@npm//esbuild-wasm", - "@npm//find-cache-dir", "@npm//glob", "@npm//https-proxy-agent", "@npm//inquirer", diff --git a/packages/angular_devkit/build_angular/package.json b/packages/angular_devkit/build_angular/package.json index 11f6f4d05308..eb0643568d8f 100644 --- a/packages/angular_devkit/build_angular/package.json +++ b/packages/angular_devkit/build_angular/package.json @@ -33,7 +33,6 @@ "critters": "0.0.10", "css-loader": "6.3.0", "esbuild-wasm": "0.13.4", - "find-cache-dir": "3.3.2", "glob": "7.2.0", "https-proxy-agent": "5.0.0", "inquirer": "8.2.0", diff --git a/packages/angular_devkit/build_angular/src/builders/browser/index.ts b/packages/angular_devkit/build_angular/src/builders/browser/index.ts index 9ac277320148..93df213d6f25 100644 --- a/packages/angular_devkit/build_angular/src/builders/browser/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/browser/index.ts @@ -33,6 +33,7 @@ import { IndexHtmlGenerator, IndexHtmlTransform, } from '../../utils/index-file/index-html-generator'; +import { normalizeCacheOptions } from '../../utils/normalize-cache'; import { ensureOutputPaths } from '../../utils/output-paths'; import { generateEntryPoints } from '../../utils/package-chunk-sort'; import { augmentAppWithServiceWorker } from '../../utils/service-worker'; @@ -168,11 +169,14 @@ export function buildWebpackBrowser( checkInternetExplorerSupport(buildBrowserFeatures.supportedBrowsers, context.logger); - return initialize(options, context, transforms.webpackConfiguration); + return { + ...(await initialize(options, context, transforms.webpackConfiguration)), + cacheOptions: normalizeCacheOptions(projectMetadata, context.workspaceRoot), + }; }), switchMap( // eslint-disable-next-line max-lines-per-function - ({ config, projectRoot, projectSourceRoot, i18n, target }) => { + ({ config, projectRoot, projectSourceRoot, i18n, target, cacheOptions }) => { const normalizedOptimization = normalizeOptimization(options.optimization); return runWebpack(config, context, { @@ -293,6 +297,7 @@ export function buildWebpackBrowser( }); const indexHtmlGenerator = new IndexHtmlGenerator({ + cache: cacheOptions, indexPath: path.join(context.workspaceRoot, getIndexInputFile(options.index)), entrypoints, deployUrl: options.deployUrl, diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/index.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/index.ts index f89b5c5158f9..e942460f098d 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/index.ts @@ -21,11 +21,11 @@ import webpack from 'webpack'; import webpackDevServer from 'webpack-dev-server'; import { ExecutionTransformer } from '../../transforms'; import { normalizeOptimization } from '../../utils'; -import { findCachePath } from '../../utils/cache-path'; import { checkPort } from '../../utils/check-port'; import { colors } from '../../utils/color'; import { I18nOptions } from '../../utils/i18n-options'; import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator'; +import { NormalizedCachedOptions, normalizeCacheOptions } from '../../utils/normalize-cache'; import { generateEntryPoints } from '../../utils/package-chunk-sort'; import { assertCompatibleAngularVersion } from '../../utils/version'; import { @@ -86,8 +86,12 @@ export function serveWebpackBrowser( browserOptions: json.JsonObject & BrowserBuilderSchema; webpackConfig: webpack.Configuration; projectRoot: string; - locale: string | undefined; }> { + const projectName = context.target?.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + options.port = await checkPort(options.port ?? 4200, options.host || 'localhost'); if (options.hmr) { @@ -130,6 +134,9 @@ export function serveWebpackBrowser( logger.warn(`Warning: 'outputHashing' option is disabled when using the dev-server.`); } + const metadata = await context.getProjectMetadata(projectName); + const cacheOptions = normalizeCacheOptions(metadata, context.workspaceRoot); + const browserName = await context.getBuilderNameForTarget(browserTarget); const browserOptions = (await context.validateOptions( { @@ -193,51 +200,51 @@ export function serveWebpackBrowser( ); } - await setupLocalize(locale, i18n, browserOptions, webpackConfig); + await setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions); } if (transforms.webpackConfiguration) { webpackConfig = await transforms.webpackConfiguration(webpackConfig); } + if (browserOptions.index) { + const { scripts = [], styles = [], baseHref } = browserOptions; + const entrypoints = generateEntryPoints({ + scripts, + styles, + // The below is needed as otherwise HMR for CSS will break. + // styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null. + // https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39 + isHMREnabled: !!webpackConfig.devServer?.hot, + }); + + webpackConfig.plugins ??= []; + webpackConfig.plugins.push( + new IndexHtmlWebpackPlugin({ + indexPath: path.resolve(workspaceRoot, getIndexInputFile(browserOptions.index)), + outputPath: getIndexOutputFile(browserOptions.index), + baseHref, + entrypoints, + deployUrl: browserOptions.deployUrl, + sri: browserOptions.subresourceIntegrity, + cache: cacheOptions, + postTransform: transforms.indexHtml, + optimization: normalizeOptimization(browserOptions.optimization), + crossOrigin: browserOptions.crossOrigin, + lang: locale, + }), + ); + } + return { browserOptions, webpackConfig, projectRoot, - locale, }; } return from(setup()).pipe( - switchMap(({ browserOptions, webpackConfig, locale }) => { - if (browserOptions.index) { - const { scripts = [], styles = [], baseHref } = browserOptions; - const entrypoints = generateEntryPoints({ - scripts, - styles, - // The below is needed as otherwise HMR for CSS will break. - // styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null. - // https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39 - isHMREnabled: !!webpackConfig.devServer?.hot, - }); - - webpackConfig.plugins ??= []; - webpackConfig.plugins.push( - new IndexHtmlWebpackPlugin({ - indexPath: path.resolve(workspaceRoot, getIndexInputFile(browserOptions.index)), - outputPath: getIndexOutputFile(browserOptions.index), - baseHref, - entrypoints, - deployUrl: browserOptions.deployUrl, - sri: browserOptions.subresourceIntegrity, - postTransform: transforms.indexHtml, - optimization: normalizeOptimization(browserOptions.optimization), - crossOrigin: browserOptions.crossOrigin, - lang: locale, - }), - ); - } - + switchMap(({ browserOptions, webpackConfig }) => { return runWebpackDevServer(webpackConfig, context, { logging: transforms.logging || createWebpackLoggingCallback(browserOptions, logger), webpackFactory: require('webpack') as typeof webpack, @@ -286,6 +293,7 @@ async function setupLocalize( i18n: I18nOptions, browserOptions: BrowserBuilderSchema, webpackConfig: webpack.Configuration, + cacheOptions: NormalizedCachedOptions, ) { const localeDescription = i18n.locales[locale]; @@ -327,7 +335,9 @@ async function setupLocalize( { loader: require.resolve('../../babel/webpack-loader'), options: { - cacheDirectory: findCachePath('babel-dev-server-i18n'), + cacheDirectory: + (cacheOptions.enabled && path.join(cacheOptions.path, 'babel-dev-server-i18n')) || + false, cacheIdentifier: JSON.stringify({ locale, translationIntegrity: localeDescription?.files.map((file) => file.integrity), diff --git a/packages/angular_devkit/build_angular/src/builders/ng-packagr/index.ts b/packages/angular_devkit/build_angular/src/builders/ng-packagr/index.ts index bfd465dbb2a5..27c379a1582b 100644 --- a/packages/angular_devkit/build_angular/src/builders/ng-packagr/index.ts +++ b/packages/angular_devkit/build_angular/src/builders/ng-packagr/index.ts @@ -7,26 +7,12 @@ */ import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; -import { resolve } from 'path'; +import { join, resolve } from 'path'; import { Observable, from, of } from 'rxjs'; import { catchError, mapTo, switchMap } from 'rxjs/operators'; +import { normalizeCacheOptions } from '../../utils/normalize-cache'; import { Schema as NgPackagrBuilderOptions } from './schema'; -async function initialize( - options: NgPackagrBuilderOptions, - root: string, -): Promise { - const packager = (await import('ng-packagr')).ngPackagr(); - - packager.forProject(resolve(root, options.project)); - - if (options.tsConfig) { - packager.withTsConfig(resolve(root, options.tsConfig)); - } - - return packager; -} - /** * @experimental Direct usage of this function is considered experimental. */ @@ -34,8 +20,39 @@ export function execute( options: NgPackagrBuilderOptions, context: BuilderContext, ): Observable { - return from(initialize(options, context.workspaceRoot)).pipe( - switchMap((packager) => (options.watch ? packager.watch() : packager.build())), + return from( + (async () => { + const root = context.workspaceRoot; + const packager = (await import('ng-packagr')).ngPackagr(); + + packager.forProject(resolve(root, options.project)); + + if (options.tsConfig) { + packager.withTsConfig(resolve(root, options.tsConfig)); + } + + const projectName = context.target?.project; + if (!projectName) { + throw new Error('The builder requires a target.'); + } + + const metadata = await context.getProjectMetadata(projectName); + const { enabled: cacheEnabled, path: cacheDirectory } = normalizeCacheOptions( + metadata, + context.workspaceRoot, + ); + + const ngPackagrOptions = { + cacheEnabled, + cacheDirectory: join(cacheDirectory, 'ng-packagr'), + }; + + return { packager, ngPackagrOptions }; + })(), + ).pipe( + switchMap(({ packager, ngPackagrOptions }) => + options.watch ? packager.watch(ngPackagrOptions) : packager.build(ngPackagrOptions), + ), mapTo({ success: true }), catchError((err) => of({ success: false, error: err.message })), ); diff --git a/packages/angular_devkit/build_angular/src/utils/build-options.ts b/packages/angular_devkit/build_angular/src/utils/build-options.ts index 05d8a1c51d2c..6e5803bfcec2 100644 --- a/packages/angular_devkit/build_angular/src/utils/build-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/build-options.ts @@ -20,6 +20,7 @@ import { SourceMapClass, } from '../builders/browser/schema'; import { Schema as DevServerSchema } from '../builders/dev-server/schema'; +import { NormalizedCachedOptions } from './normalize-cache'; import { NormalizedFileReplacement } from './normalize-file-replacements'; import { NormalizedOptimizationOptions } from './normalize-optimization'; @@ -66,8 +67,8 @@ export interface BuildOptions { platform?: 'browser' | 'server'; fileReplacements: NormalizedFileReplacement[]; inlineStyleLanguage?: InlineStyleLanguage; - allowedCommonJsDependencies?: string[]; + cache: NormalizedCachedOptions; } export interface WebpackTestOptions extends BuildOptions { diff --git a/packages/angular_devkit/build_angular/src/utils/cache-path.ts b/packages/angular_devkit/build_angular/src/utils/cache-path.ts deleted file mode 100644 index 26dfd13ac7fa..000000000000 --- a/packages/angular_devkit/build_angular/src/utils/cache-path.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @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 findCacheDirectory from 'find-cache-dir'; -import { tmpdir } from 'os'; -import { resolve } from 'path'; -import { cachingBasePath } from './environment-options'; - -export function findCachePath(name: string): string { - if (cachingBasePath) { - return resolve(cachingBasePath, name); - } - - return findCacheDirectory({ name }) || tmpdir(); -} diff --git a/packages/angular_devkit/build_angular/src/utils/environment-options.ts b/packages/angular_devkit/build_angular/src/utils/environment-options.ts index 1b019c4cc96d..dcd1bb84723b 100644 --- a/packages/angular_devkit/build_angular/src/utils/environment-options.ts +++ b/packages/angular_devkit/build_angular/src/utils/environment-options.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import * as path from 'path'; +import { colors } from './color'; function isDisabled(variable: string): boolean { return variable === '0' || variable.toLowerCase() === 'false'; @@ -66,27 +66,6 @@ export const allowMangle = isPresent(mangleVariable) export const shouldBeautify = debugOptimize.beautify; export const allowMinify = debugOptimize.minify; -// Build cache -const cacheVariable = process.env['NG_BUILD_CACHE']; -export const cachingDisabled = isPresent(cacheVariable) && isDisabled(cacheVariable); -export const cachingBasePath = (() => { - if (cachingDisabled || !isPresent(cacheVariable) || isEnabled(cacheVariable)) { - return null; - } - if (!path.isAbsolute(cacheVariable)) { - throw new Error('NG_BUILD_CACHE path value must be absolute.'); - } - - return cacheVariable; -})(); - -// Persistent build cache -const persistentBuildCacheVariable = process.env['NG_PERSISTENT_BUILD_CACHE']; -export const persistentBuildCacheEnabled = - !cachingDisabled && - isPresent(persistentBuildCacheVariable) && - isEnabled(persistentBuildCacheVariable); - // Build profiling const profilingVariable = process.env['NG_BUILD_PROFILING']; export const profilingEnabled = isPresent(profilingVariable) && isEnabled(profilingVariable); @@ -102,3 +81,21 @@ export const profilingEnabled = isPresent(profilingVariable) && isEnabled(profil */ const maxWorkersVariable = process.env['NG_BUILD_MAX_WORKERS']; export const maxWorkers = isPresent(maxWorkersVariable) ? +maxWorkersVariable : 4; + +// Build cache +const cacheVariable = process.env['NG_BUILD_CACHE']; +export const cachingDisabled = (() => { + if (!isPresent(cacheVariable)) { + return null; + } + + // eslint-disable-next-line no-console + console.warn( + colors.yellow( + `Warning: 'NG_BUILD_CACHE' environment variable support will be removed in version 14.\n` + + `Configure 'cli.cache' in the workspace configuration instead.`, + ), + ); + + return isDisabled(cacheVariable); +})(); diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts b/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts index af63fd04c446..a49651df6127 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/index-html-generator.ts @@ -8,6 +8,7 @@ import * as fs from 'fs'; import { join } from 'path'; +import { NormalizedCachedOptions } from '../normalize-cache'; import { NormalizedOptimizationOptions } from '../normalize-optimization'; import { stripBom } from '../strip-bom'; import { CrossOriginValue, Entrypoint, FileInfo, augmentIndexHtml } from './augment-index-html'; @@ -34,6 +35,7 @@ export interface IndexHtmlGeneratorOptions { postTransform?: IndexHtmlTransform; crossOrigin?: CrossOriginValue; optimization?: NormalizedOptimizationOptions; + cache?: NormalizedCachedOptions; } export type IndexHtmlTransform = (content: string) => Promise; diff --git a/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts b/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts index b1ccad425585..6d2a25263f8b 100644 --- a/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts +++ b/packages/angular_devkit/build_angular/src/utils/index-file/inline-fonts.ts @@ -10,14 +10,11 @@ import * as cacache from 'cacache'; import * as fs from 'fs'; import * as https from 'https'; import proxyAgent from 'https-proxy-agent'; +import { join } from 'path'; import { URL } from 'url'; -import { findCachePath } from '../cache-path'; -import { cachingDisabled } from '../environment-options'; +import { NormalizedCachedOptions } from '../normalize-cache'; import { htmlRewritingStream } from './html-rewriting-stream'; -const cacheFontsPath: string | undefined = cachingDisabled - ? undefined - : findCachePath('angular-build-fonts'); const packageVersion = require('../../../package.json').version; interface FontProviderDetails { @@ -26,6 +23,7 @@ interface FontProviderDetails { export interface InlineFontsOptions { minify?: boolean; + cache?: NormalizedCachedOptions; } const SUPPORTED_PROVIDERS: Record = { @@ -38,7 +36,13 @@ const SUPPORTED_PROVIDERS: Record = { }; export class InlineFontsProcessor { - constructor(private options: InlineFontsOptions) {} + private readonly cachePath: string | undefined; + constructor(private options: InlineFontsOptions) { + const { path: cacheDirectory, enabled } = this.options.cache || {}; + if (cacheDirectory && enabled) { + this.cachePath = join(cacheDirectory, 'angular-build-fonts'); + } + } async process(content: string): Promise { const hrefList: string[] = []; @@ -154,8 +158,8 @@ export class InlineFontsProcessor { private async getResponse(url: URL): Promise { const key = `${packageVersion}|${url}`; - if (cacheFontsPath) { - const entry = await cacache.get.info(cacheFontsPath, key); + if (this.cachePath) { + const entry = await cacache.get.info(this.cachePath, key); if (entry) { return fs.promises.readFile(entry.path, 'utf8'); } @@ -205,8 +209,8 @@ export class InlineFontsProcessor { ); }); - if (cacheFontsPath) { - await cacache.put(cacheFontsPath, key, data); + if (this.cachePath) { + await cacache.put(this.cachePath, key, data); } return data; diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts b/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts index e62985245b74..66ad154be17b 100644 --- a/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts +++ b/packages/angular_devkit/build_angular/src/utils/normalize-builder-schema.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import { Path } from '@angular-devkit/core'; +import { Path, getSystemPath, json } from '@angular-devkit/core'; import { AssetPatternClass, Schema as BrowserBuilderSchema, @@ -14,6 +14,7 @@ import { } from '../builders/browser/schema'; import { BuildOptions } from './build-options'; import { normalizeAssetPatterns } from './normalize-asset-patterns'; +import { normalizeCacheOptions } from './normalize-cache'; import { NormalizedFileReplacement, normalizeFileReplacements, @@ -37,11 +38,13 @@ export function normalizeBrowserSchema( projectRoot: Path, sourceRoot: Path | undefined, options: BrowserBuilderSchema, + metadata: json.JsonObject, ): NormalizedBrowserBuilderSchema { const normalizedSourceMapOptions = normalizeSourceMaps(options.sourceMap || false); return { ...options, + cache: normalizeCacheOptions(metadata, getSystemPath(root)), assets: normalizeAssetPatterns(options.assets || [], root, projectRoot, sourceRoot), fileReplacements: normalizeFileReplacements(options.fileReplacements || [], root), optimization: normalizeOptimization(options.optimization), diff --git a/packages/angular_devkit/build_angular/src/utils/normalize-cache.ts b/packages/angular_devkit/build_angular/src/utils/normalize-cache.ts new file mode 100644 index 000000000000..cf3f9fc46f43 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/normalize-cache.ts @@ -0,0 +1,56 @@ +/** + * @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 { json } from '@angular-devkit/core'; +import { resolve } from 'path'; +import { cachingDisabled } from './environment-options'; + +export interface NormalizedCachedOptions { + enabled: boolean; + path: string; +} + +interface CacheMetadata { + enabled?: boolean; + environment?: 'local' | 'ci' | 'all'; + path?: string; +} + +export function normalizeCacheOptions( + metadata: json.JsonObject, + worspaceRoot: string, +): NormalizedCachedOptions { + const cacheMetadata: CacheMetadata = + json.isJsonObject(metadata.cli) && json.isJsonObject(metadata.cli.cache) + ? metadata.cli.cache + : {}; + + const { enabled = true, environment = 'local', path = '.angular/cache' } = cacheMetadata; + const isCI = process.env['CI'] === '1' || process.env['CI']?.toLowerCase() === 'true'; + + let cacheEnabled = enabled; + if (cachingDisabled !== null) { + cacheEnabled = !cachingDisabled; + } + + if (cacheEnabled) { + switch (environment) { + case 'ci': + cacheEnabled = isCI; + break; + case 'local': + cacheEnabled = !isCI; + break; + } + } + + return { + enabled: cacheEnabled, + path: resolve(worspaceRoot, path), + }; +} diff --git a/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts b/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts index 1d00e6a0bf56..87610285b605 100644 --- a/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts +++ b/packages/angular_devkit/build_angular/src/utils/webpack-browser-config.ts @@ -7,7 +7,7 @@ */ import { BuilderContext } from '@angular-devkit/architect'; -import { getSystemPath, logging, normalize, resolve } from '@angular-devkit/core'; +import { getSystemPath, json, logging, normalize, resolve } from '@angular-devkit/core'; import * as path from 'path'; import { ScriptTarget } from 'typescript'; import { Configuration, javascript } from 'webpack'; @@ -152,7 +152,13 @@ export async function generateBrowserWebpackConfigFromContext( ? resolve(workspaceRoot, normalize(projectSourceRoot)) : undefined; - const normalizedOptions = normalizeBrowserSchema(workspaceRoot, projectRoot, sourceRoot, options); + const normalizedOptions = normalizeBrowserSchema( + workspaceRoot, + projectRoot, + sourceRoot, + options, + projectMetadata, + ); const config = await generateWebpackConfig( getSystemPath(workspaceRoot), diff --git a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts index 6a3dcf3de3e0..51686b72802e 100644 --- a/packages/angular_devkit/build_angular/src/webpack/configs/common.ts +++ b/packages/angular_devkit/build_angular/src/webpack/configs/common.ts @@ -23,13 +23,7 @@ import { import { AssetPatternClass } from '../../builders/browser/schema'; import { BuildBrowserFeatures } from '../../utils'; import { WebpackConfigOptions } from '../../utils/build-options'; -import { findCachePath } from '../../utils/cache-path'; -import { - allowMangle, - cachingDisabled, - persistentBuildCacheEnabled, - profilingEnabled, -} from '../../utils/environment-options'; +import { allowMangle, profilingEnabled } from '../../utils/environment-options'; import { loadEsmModule } from '../../utils/load-esm'; import { Spinner } from '../../utils/spinner'; import { addError } from '../../utils/webpack-diagnostics'; @@ -41,6 +35,7 @@ import { getOutputHashFormat, getWatchOptions, normalizeExtraEntryPoints } from export async function getCommonConfig(wco: WebpackConfigOptions): Promise { const { root, projectRoot, buildOptions, tsConfig } = wco; const { + cache, platform = 'browser', sourceMap: { styles: stylesSourceMap, scripts: scriptsSourceMap, vendor: vendorSourceMap }, optimization: { styles: stylesOptimization, scripts: scriptsOptimization }, @@ -380,7 +375,7 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise { + await Promise.all([ + rimraf(overriddenCachePath), + rimraf(defaultCachePath), + updateJsonFile('angular.json', (config) => { + config.cli ??= {}; + config.cli.cache = cacheOptions; + }), + ]); + + await ng('build'); +}