Skip to content

Commit 3a44611

Browse files
clydinmgechev
authored andcommitted
refactor(@angular-devkit/build-angular): initial copy-on-write asset processing support (#15788)
This is currently only supported when performing a differential loading build (no watch mode). This will eventually be expanded to cover watch mode and non-differential loading builds.
1 parent 665f6e0 commit 3a44611

File tree

4 files changed

+114
-9
lines changed

4 files changed

+114
-9
lines changed

packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
194194
}
195195

196196
// process asset entries
197-
if (buildOptions.assets) {
197+
if (
198+
buildOptions.assets &&
199+
(fullDifferential || buildOptions.watch || !differentialLoadingNeeded)
200+
) {
198201
const copyWebpackPluginPatterns = buildOptions.assets.map((asset: AssetPatternClass) => {
199202
// Resolve input paths relative to workspace root and add slash at the end.
200203
asset.input = path.resolve(root, asset.input).replace(/\\/g, '/');

packages/angular_devkit/build_angular/src/browser/index.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ import {
6161
BuildBrowserFeatures,
6262
deleteOutputDir,
6363
fullDifferential,
64+
normalizeAssetPatterns,
6465
normalizeOptimization,
6566
normalizeSourceMaps,
6667
} from '../utils';
68+
import { copyAssets } from '../utils/copy-assets';
6769
import { I18nOptions, createI18nOptions } from '../utils/i18n-options';
6870
import { manglingDisabled } from '../utils/mangle-options';
6971
import {
@@ -216,13 +218,14 @@ export function buildWebpackBrowser(
216218
): Observable<BrowserBuilderOutput> {
217219
const host = new NodeJsSyncHost();
218220
const root = normalize(context.workspaceRoot);
221+
const baseOutputPath = path.resolve(context.workspaceRoot, options.outputPath);
219222

220223
// Check Angular version.
221224
assertCompatibleAngularVersion(context.workspaceRoot, context.logger);
222225

223226
return from(initialize(options, context, host, transforms.webpackConfiguration)).pipe(
224227
// tslint:disable-next-line: no-big-function
225-
switchMap(({ config: configs, projectRoot, i18n }) => {
228+
switchMap(({ config: configs, projectRoot, projectSourceRoot, i18n }) => {
226229
const tsConfig = readTsconfig(options.tsConfig, context.workspaceRoot);
227230
const target = tsConfig.options.target || ScriptTarget.ES5;
228231
const buildBrowserFeatures = new BuildBrowserFeatures(projectRoot, target);
@@ -355,7 +358,7 @@ export function buildWebpackBrowser(
355358

356359
// Retrieve the content/map for the file
357360
// NOTE: Additional future optimizations will read directly from memory
358-
let filename = path.resolve(getSystemPath(root), options.outputPath, file.file);
361+
let filename = path.join(baseOutputPath, file.file);
359362
const code = fs.readFileSync(filename, 'utf8');
360363
let map;
361364
if (actionOptions.sourceMaps) {
@@ -628,6 +631,27 @@ export function buildWebpackBrowser(
628631

629632
context.logger.info('ES5 bundle generation complete.');
630633

634+
// Copy assets
635+
if (options.assets) {
636+
try {
637+
await copyAssets(
638+
normalizeAssetPatterns(
639+
options.assets,
640+
new virtualFs.SyncDelegateHost(host),
641+
root,
642+
normalize(projectRoot),
643+
projectSourceRoot === undefined ? undefined : normalize(projectSourceRoot),
644+
),
645+
[baseOutputPath],
646+
context.workspaceRoot,
647+
);
648+
} catch (err) {
649+
context.logger.error('Unable to copy assets: ' + err.message);
650+
651+
return { success: false };
652+
}
653+
}
654+
631655
type ArrayElement<A> = A extends ReadonlyArray<infer T> ? T : never;
632656
function generateBundleInfoStats(
633657
id: string | number,
@@ -703,10 +727,7 @@ export function buildWebpackBrowser(
703727
if (options.index) {
704728
return writeIndexHtml({
705729
host,
706-
outputPath: resolve(
707-
root,
708-
join(normalize(options.outputPath), getIndexOutputFile(options)),
709-
),
730+
outputPath: join(normalize(baseOutputPath), getIndexOutputFile(options)),
710731
indexPath: join(root, getIndexInputFile(options)),
711732
files,
712733
noModuleFiles,
@@ -739,7 +760,7 @@ export function buildWebpackBrowser(
739760
host,
740761
root,
741762
normalize(projectRoot),
742-
resolve(root, normalize(options.outputPath)),
763+
normalize(baseOutputPath),
743764
options.baseHref || '/',
744765
options.ngswConfigPath,
745766
).then(
@@ -756,7 +777,7 @@ export function buildWebpackBrowser(
756777
({
757778
...event,
758779
// If we use differential loading, both configs have the same outputs
759-
outputPath: path.resolve(context.workspaceRoot, options.outputPath),
780+
outputPath: baseOutputPath,
760781
} as BrowserBuilderOutput),
761782
),
762783
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import * as fs from 'fs';
9+
import * as glob from 'glob';
10+
import * as path from 'path';
11+
import { copyFile } from './copy-file';
12+
13+
function globAsync(pattern: string, options: glob.IOptions) {
14+
return new Promise<string[]>((resolve, reject) =>
15+
glob(pattern, options, (e, m) => (e ? reject(e) : resolve(m))),
16+
);
17+
}
18+
19+
export async function copyAssets(
20+
entries: { glob: string; ignore?: string[]; input: string; output: string; flatten?: boolean }[],
21+
basePaths: Iterable<string>,
22+
root: string,
23+
changed?: Set<string>,
24+
) {
25+
const defaultIgnore = ['.gitkeep', '**/.DS_Store', '**/Thumbs.db'];
26+
27+
for (const entry of entries) {
28+
const cwd = path.resolve(root, entry.input);
29+
const files = await globAsync(entry.glob, {
30+
cwd,
31+
dot: true,
32+
ignore: entry.ignore ? defaultIgnore.concat(entry.ignore) : defaultIgnore,
33+
});
34+
35+
for (const file of files) {
36+
const src = path.join(cwd, file);
37+
if (changed && !changed.has(src)) {
38+
continue;
39+
}
40+
41+
const filePath = entry.flatten ? path.basename(file) : file;
42+
for (const base of basePaths) {
43+
const dest = path.join(base, entry.output, filePath);
44+
const dir = path.dirname(dest);
45+
if (!fs.existsSync(dir)) {
46+
// tslint:disable-next-line: no-any
47+
fs.mkdirSync(dir, { recursive: true } as any);
48+
}
49+
copyFile(src, dest);
50+
}
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import * as fs from 'fs';
9+
10+
// Workaround Node.js issue prior to 10.16 with copyFile on macOS
11+
// https://github.com/angular/angular-cli/issues/15544 & https://github.com/nodejs/node/pull/27241
12+
let copyFileWorkaround = false;
13+
if (process.platform === 'darwin') {
14+
const version = process.versions.node.split('.').map(part => Number(part));
15+
if (version[0] < 10 || version[0] === 11 || (version[0] === 10 && version[1] < 16)) {
16+
copyFileWorkaround = true;
17+
}
18+
}
19+
20+
export function copyFile(src: fs.PathLike, dest: fs.PathLike): void {
21+
if (copyFileWorkaround) {
22+
try {
23+
fs.unlinkSync(dest);
24+
} catch {}
25+
}
26+
27+
fs.copyFileSync(src, dest, fs.constants.COPYFILE_FICLONE);
28+
}

0 commit comments

Comments
 (0)