Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions packages/angular_devkit/build_angular/src/app-shell/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ async function _renderUniversal(

const { AppServerModule, renderModule } = await import(serverBundlePath);

const renderModuleFn:
| ((module: unknown, options: {}) => Promise<string>)
| undefined = renderModule;
const renderModuleFn: ((module: unknown, options: {}) => Promise<string>) | undefined =
renderModule;

if (!(renderModuleFn && AppServerModule)) {
throw new Error(
Expand Down Expand Up @@ -172,7 +171,7 @@ async function _appShellBuilder(
const browserTargetRun = await context.scheduleTarget(browserTarget, {
watch: false,
serviceWorker: false,
optimization: (optimization as unknown) as JsonObject,
optimization: optimization as unknown as JsonObject,
});
const serverTargetRun = await context.scheduleTarget(serverTarget, {
watch: false,
Expand All @@ -181,9 +180,10 @@ async function _appShellBuilder(
let spinner: Spinner | undefined;

try {
// Using `.result` instead of `.output` causes Webpack FS cache not to be created.
const [browserResult, serverResult] = await Promise.all([
(browserTargetRun.result as unknown) as BrowserBuilderOutput,
(serverTargetRun.result as unknown) as ServerBuilderOutput,
browserTargetRun.output.toPromise() as Promise<BrowserBuilderOutput>,
serverTargetRun.output.toPromise() as Promise<ServerBuilderOutput>,
]);

if (browserResult.success === false || browserResult.baseOutputPath === undefined) {
Expand All @@ -203,8 +203,8 @@ async function _appShellBuilder(

return { success: false, error: err.message };
} finally {
// Just be good citizens and stop those jobs.
await Promise.all([browserTargetRun.stop(), serverTargetRun.stop()]);
// workaround for [tsetse] All Promises in async functions must either be awaited or used in an expression.
const _ = Promise.all([browserTargetRun.stop(), serverTargetRun.stop()]);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ export const cachingBasePath = (() => {
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);
Expand Down
54 changes: 44 additions & 10 deletions packages/angular_devkit/build_angular/src/webpack/configs/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
BuildOptimizerWebpackPlugin,
buildOptimizerLoaderPath,
} from '@angular-devkit/build-optimizer';
import {
GLOBAL_DEFS_FOR_TERSER,
GLOBAL_DEFS_FOR_TERSER_WITH_AOT,
VERSION as NG_VERSION,
} from '@angular/compiler-cli';
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import { createHash } from 'crypto';
import { createWriteStream, existsSync, promises as fsPromises } from 'fs';
import * as path from 'path';
import { ScriptTarget } from 'typescript';
Expand All @@ -20,6 +26,7 @@ import {
ContextReplacementPlugin,
ProgressPlugin,
RuleSetRule,
WebpackOptionsNormalized,
debug,
} from 'webpack';
import { AssetPatternClass } from '../../browser/schema';
Expand All @@ -31,6 +38,7 @@ import {
allowMinify,
cachingDisabled,
maxWorkers,
persistentBuildCacheEnabled,
profilingEnabled,
shouldBeautify,
} from '../../utils/environment-options';
Expand Down Expand Up @@ -310,11 +318,6 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {

if (scriptsOptimization) {
const TerserPlugin = require('terser-webpack-plugin');
const {
GLOBAL_DEFS_FOR_TERSER,
GLOBAL_DEFS_FOR_TERSER_WITH_AOT,
} = require('@angular/compiler-cli');

const angularGlobalDefinitions = buildOptions.aot
? GLOBAL_DEFS_FOR_TERSER_WITH_AOT
: GLOBAL_DEFS_FOR_TERSER;
Expand Down Expand Up @@ -473,11 +476,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
syncWebAssembly: true,
asyncWebAssembly: true,
},
cache: !!buildOptions.watch &&
!cachingDisabled && {
type: 'memory',
maxGenerations: 1,
},
cache: getCacheSettings(wco, buildBrowserFeatures.supportedBrowsers),
optimization: {
minimizer: extraMinimizers,
moduleIds: 'deterministic',
Expand All @@ -497,3 +496,38 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
],
};
}

function getCacheSettings(
wco: WebpackConfigOptions,
supportedBrowsers: string[],
): WebpackOptionsNormalized['cache'] {
if (persistentBuildCacheEnabled) {
const packageVersion = require('../../../package.json').version;

return {
type: 'filesystem',
cacheDirectory: findCachePath('angular-webpack'),
maxMemoryGenerations: 1,
// We use the versions and build options as the cache name. The Webpack configurations are too
// dynamic and shared among different build types: test, build and serve.
// None of which are "named".
name: createHash('sha1')
.update(NG_VERSION.full)
.update(packageVersion)
.update(wco.projectRoot)
.update(JSON.stringify(wco.tsConfig))
.update(JSON.stringify(wco.buildOptions))
.update(supportedBrowsers.join(''))
.digest('base64'),
};
}

if (wco.buildOptions.watch && !cachingDisabled) {
return {
type: 'memory',
maxGenerations: 1,
};
}

return false;
}
14 changes: 13 additions & 1 deletion packages/angular_devkit/build_angular/src/webpack/utils/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,12 @@ function generateBuildStats(hash: string, time: number, colors: boolean): string
return `Build at: ${w(new Date().toISOString())} - Hash: ${w(hash)} - Time: ${w('' + time)}ms`;
}

// We use this cache because we can have multiple builders running in the same process,
// where each builder has different output path.

// Ideally, we should create the logging callback as a factory, but that would need a refactoring.
const runsCache = new Set<string>();

function statsToString(
json: StatsCompilation,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -176,8 +182,12 @@ function statsToString(
const changedChunksStats: BundleStats[] = bundleState ?? [];
let unchangedChunkNumber = 0;
if (!bundleState?.length) {
const isFirstRun = !runsCache.has(json.outputPath || '');

for (const chunk of json.chunks) {
if (!chunk.rendered) {
// During first build we want to display unchanged chunks
// but unchanged cached chunks are always marked as not rendered.
if (!isFirstRun && !chunk.rendered) {
continue;
}

Expand All @@ -188,6 +198,8 @@ function statsToString(
changedChunksStats.push(generateBundleStats({ ...chunk, size: summedSize }));
}
unchangedChunkNumber = json.chunks.length - changedChunksStats.length;

runsCache.add(json.outputPath || '');
}

// Sort chunks by size in descending order
Expand Down