Skip to content

refactor(@angular-devkit/build-angular): initial copy-on-write asset processing support #15788

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

Merged
merged 1 commit into from
Oct 9, 2019
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
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
}

// process asset entries
if (buildOptions.assets) {
if (
buildOptions.assets &&
(fullDifferential || buildOptions.watch || !differentialLoadingNeeded)
) {
const copyWebpackPluginPatterns = buildOptions.assets.map((asset: AssetPatternClass) => {
// Resolve input paths relative to workspace root and add slash at the end.
asset.input = path.resolve(root, asset.input).replace(/\\/g, '/');
Expand Down
37 changes: 29 additions & 8 deletions packages/angular_devkit/build_angular/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ import {
BuildBrowserFeatures,
deleteOutputDir,
fullDifferential,
normalizeAssetPatterns,
normalizeOptimization,
normalizeSourceMaps,
} from '../utils';
import { copyAssets } from '../utils/copy-assets';
import { I18nOptions, createI18nOptions } from '../utils/i18n-options';
import { manglingDisabled } from '../utils/mangle-options';
import {
Expand Down Expand Up @@ -216,13 +218,14 @@ export function buildWebpackBrowser(
): Observable<BrowserBuilderOutput> {
const host = new NodeJsSyncHost();
const root = normalize(context.workspaceRoot);
const baseOutputPath = path.resolve(context.workspaceRoot, options.outputPath);

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

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

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

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

// Copy assets
if (options.assets) {
try {
await copyAssets(
normalizeAssetPatterns(
options.assets,
new virtualFs.SyncDelegateHost(host),
root,
normalize(projectRoot),
projectSourceRoot === undefined ? undefined : normalize(projectSourceRoot),
),
[baseOutputPath],
context.workspaceRoot,
);
} catch (err) {
context.logger.error('Unable to copy assets: ' + err.message);

return { success: false };
}
}

type ArrayElement<A> = A extends ReadonlyArray<infer T> ? T : never;
function generateBundleInfoStats(
id: string | number,
Expand Down Expand Up @@ -703,10 +727,7 @@ export function buildWebpackBrowser(
if (options.index) {
return writeIndexHtml({
host,
outputPath: resolve(
root,
join(normalize(options.outputPath), getIndexOutputFile(options)),
),
outputPath: join(normalize(baseOutputPath), getIndexOutputFile(options)),
indexPath: join(root, getIndexInputFile(options)),
files,
noModuleFiles,
Expand Down Expand Up @@ -739,7 +760,7 @@ export function buildWebpackBrowser(
host,
root,
normalize(projectRoot),
resolve(root, normalize(options.outputPath)),
normalize(baseOutputPath),
options.baseHref || '/',
options.ngswConfigPath,
).then(
Expand All @@ -756,7 +777,7 @@ export function buildWebpackBrowser(
({
...event,
// If we use differential loading, both configs have the same outputs
outputPath: path.resolve(context.workspaceRoot, options.outputPath),
outputPath: baseOutputPath,
} as BrowserBuilderOutput),
),
);
Expand Down
53 changes: 53 additions & 0 deletions packages/angular_devkit/build_angular/src/utils/copy-assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @license
* Copyright Google Inc. 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 * as fs from 'fs';
import * as glob from 'glob';
import * as path from 'path';
import { copyFile } from './copy-file';

function globAsync(pattern: string, options: glob.IOptions) {
return new Promise<string[]>((resolve, reject) =>
glob(pattern, options, (e, m) => (e ? reject(e) : resolve(m))),
);
}

export async function copyAssets(
entries: { glob: string; ignore?: string[]; input: string; output: string; flatten?: boolean }[],
basePaths: Iterable<string>,
root: string,
changed?: Set<string>,
) {
const defaultIgnore = ['.gitkeep', '**/.DS_Store', '**/Thumbs.db'];

for (const entry of entries) {
const cwd = path.resolve(root, entry.input);
const files = await globAsync(entry.glob, {
cwd,
dot: true,
ignore: entry.ignore ? defaultIgnore.concat(entry.ignore) : defaultIgnore,
});

for (const file of files) {
const src = path.join(cwd, file);
if (changed && !changed.has(src)) {
continue;
}

const filePath = entry.flatten ? path.basename(file) : file;
for (const base of basePaths) {
const dest = path.join(base, entry.output, filePath);
const dir = path.dirname(dest);
if (!fs.existsSync(dir)) {
// tslint:disable-next-line: no-any
fs.mkdirSync(dir, { recursive: true } as any);
}
copyFile(src, dest);
}
}
}
}
28 changes: 28 additions & 0 deletions packages/angular_devkit/build_angular/src/utils/copy-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @license
* Copyright Google Inc. 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 * as fs from 'fs';

// Workaround Node.js issue prior to 10.16 with copyFile on macOS
// https://github.com/angular/angular-cli/issues/15544 & https://github.com/nodejs/node/pull/27241
let copyFileWorkaround = false;
if (process.platform === 'darwin') {
const version = process.versions.node.split('.').map(part => Number(part));
if (version[0] < 10 || version[0] === 11 || (version[0] === 10 && version[1] < 16)) {
copyFileWorkaround = true;
}
}

export function copyFile(src: fs.PathLike, dest: fs.PathLike): void {
if (copyFileWorkaround) {
try {
fs.unlinkSync(dest);
} catch {}
}

fs.copyFileSync(src, dest, fs.constants.COPYFILE_FICLONE);
}