mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
build: compile entry-points in parallel (#7094)
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import {main as ngc} from '@angular/tsc-wrapped';
|
||||
import {red} from 'chalk';
|
||||
import {spawn} from 'child_process';
|
||||
import {readFileSync, writeFileSync} from 'fs';
|
||||
import {sync as glob} from 'glob';
|
||||
import {join} from 'path';
|
||||
import {PackageBundler} from './build-bundles';
|
||||
import {buildConfig} from './build-config';
|
||||
import {getSecondaryEntryPointsForPackage} from './secondary-entry-points';
|
||||
import {PackageBundler} from './build-bundles';
|
||||
|
||||
|
||||
const {packagesDir, outputDir} = buildConfig;
|
||||
@@ -37,19 +38,23 @@ export class BuildPackage {
|
||||
/** Path to the tsconfig file, which will be used to build the tests. */
|
||||
private readonly tsconfigTests: string;
|
||||
|
||||
private _secondaryEntryPoints: string[];
|
||||
private _secondaryEntryPointsByDepth: string[][];
|
||||
|
||||
/** Secondary entry-points partitioned by their build depth. */
|
||||
private get secondaryEntryPointsByDepth(): string[][] {
|
||||
this.cacheSecondaryEntryPoints();
|
||||
return this._secondaryEntryPointsByDepth;
|
||||
}
|
||||
|
||||
private readonly bundler = new PackageBundler(this);
|
||||
|
||||
/** Secondary entry points for the package. */
|
||||
get secondaryEntryPoints(): string[] {
|
||||
if (!this._secondaryEntryPoints) {
|
||||
this._secondaryEntryPoints = getSecondaryEntryPointsForPackage(this);
|
||||
}
|
||||
|
||||
this.cacheSecondaryEntryPoints();
|
||||
return this._secondaryEntryPoints;
|
||||
}
|
||||
|
||||
private _secondaryEntryPoints: string[];
|
||||
|
||||
constructor(public readonly name: string, public readonly dependencies: BuildPackage[] = []) {
|
||||
this.sourceDir = join(packagesDir, name);
|
||||
this.outputDir = join(outputDir, 'packages', name);
|
||||
@@ -62,9 +67,13 @@ export class BuildPackage {
|
||||
|
||||
/** Compiles the package sources with all secondary entry points. */
|
||||
async compile() {
|
||||
// Walk through every secondary entry point and build the TypeScript sources sequentially.
|
||||
for (const entryPoint of this.secondaryEntryPoints) {
|
||||
await this._compileEntryPoint(buildTsconfigName, entryPoint);
|
||||
// Compile all secondary entry-points with the same depth in parallel, and each separate depth
|
||||
// group in sequence. This will look something like:
|
||||
// Depth 0: coercion, platform, keycodes, bidi
|
||||
// Depth 1: a11y, scrolling
|
||||
// Depth 2: overlay
|
||||
for (const entryPointGroup of this.secondaryEntryPointsByDepth) {
|
||||
await Promise.all(entryPointGroup.map(p => this._compileEntryPoint(buildTsconfigName, p)));
|
||||
}
|
||||
|
||||
// Compile the primary entry-point.
|
||||
@@ -86,8 +95,17 @@ export class BuildPackage {
|
||||
const entryPointPath = join(this.sourceDir, secondaryEntryPoint);
|
||||
const entryPointTsconfigPath = join(entryPointPath, tsconfigName);
|
||||
|
||||
await ngc(entryPointTsconfigPath, {basePath: entryPointPath});
|
||||
this.renamePrivateReExportsToBeUnique(secondaryEntryPoint);
|
||||
return new Promise((resolve, reject) => {
|
||||
const childProcess = spawn('./node_modules/.bin/ngc', ['-p', entryPointTsconfigPath]);
|
||||
|
||||
// Pipe stdout and stderr from the child process.
|
||||
childProcess.stdout.on('data', (data: any) => console.log(`${data}`));
|
||||
childProcess.stderr.on('data', (data: any) => console.error(red(`${data}`)));
|
||||
|
||||
childProcess.on('exit', (exitCode: number) => exitCode === 0 ? resolve() : reject());
|
||||
})
|
||||
.catch(() => console.error(red(`Failed to compile ${secondaryEntryPoint}`)))
|
||||
.then(() => this.renamePrivateReExportsToBeUnique(secondaryEntryPoint));
|
||||
}
|
||||
|
||||
/** Renames `ɵa`-style re-exports generated by Angular to be unique across compilation units. */
|
||||
@@ -107,4 +125,13 @@ export class BuildPackage {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Stores the secondary entry-points for this package if they haven't been computed already. */
|
||||
private cacheSecondaryEntryPoints() {
|
||||
if (!this._secondaryEntryPoints) {
|
||||
this._secondaryEntryPointsByDepth = getSecondaryEntryPointsForPackage(this);
|
||||
this._secondaryEntryPoints =
|
||||
this._secondaryEntryPointsByDepth.reduce((list, p) => list.concat(p), []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export function getSecondaryEntryPointsForPackage(pkg: BuildPackage) {
|
||||
.filter(d => existsSync(join(packageDir, d, 'tsconfig-build.json')));
|
||||
|
||||
// Create nodes that comprise the build graph.
|
||||
const buildNodes: BuildNode[] = entryPoints.map(p => ({name: p, deps: []}));
|
||||
const buildNodes: BuildNode[] = entryPoints.map(p => ({name: p, deps: [], depth: 0}));
|
||||
|
||||
// Create a lookup for name -> build graph node.
|
||||
const nodeLookup = buildNodes.reduce((lookup, node) => {
|
||||
@@ -54,24 +54,27 @@ export function getSecondaryEntryPointsForPackage(pkg: BuildPackage) {
|
||||
|
||||
// Concatenate the build order for each node into one global build order.
|
||||
// Duplicates are automatically omitted by getBuildOrder.
|
||||
return buildNodes.reduce((order: string[], node) => {
|
||||
const buildOrder = buildNodes.reduce((order: BuildNode[], node) => {
|
||||
return [...order, ...getBuildOrder(node)];
|
||||
}, []);
|
||||
|
||||
return partitionNodesByDepth(buildOrder).map(level => level.map(node => node.name));
|
||||
}
|
||||
|
||||
/** Gets the build order for a given node with DFS. */
|
||||
function getBuildOrder(node: BuildNode): string[] {
|
||||
/** Gets the build order for node with DFS. As a side-effect, sets the depth on visited nodes. */
|
||||
function getBuildOrder(node: BuildNode): BuildNode[] {
|
||||
if (node.visited) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let buildOrder: string[] = [];
|
||||
let buildOrder: BuildNode[] = [];
|
||||
for (const dep of node.deps) {
|
||||
buildOrder = [...buildOrder, ...getBuildOrder(dep)];
|
||||
node.depth = node.deps.reduce((maxDepth, d) => Math.max(d.depth + 1, maxDepth), -1);
|
||||
}
|
||||
|
||||
node.visited = true;
|
||||
return [...buildOrder, node.name];
|
||||
return [...buildOrder, node];
|
||||
}
|
||||
|
||||
/** Gets the names of all subdirectories for a given path. */
|
||||
@@ -84,9 +87,32 @@ interface BuildNode {
|
||||
name: string;
|
||||
deps: BuildNode[];
|
||||
visited?: boolean;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Partitions nodes into groups by depth. For example,
|
||||
* [{name: a11y, depth: 1}, {name: bidi, depth: 0}, {name: platform, depth: 0}]
|
||||
* =>
|
||||
* [ [{name: bidi, depth: 0}, {name: platform, depth: 0}], [{name: a11y, depth: 1}] ]
|
||||
*/
|
||||
function partitionNodesByDepth(nodes: BuildNode[]): BuildNode[][] {
|
||||
const result: BuildNode[][] = [[]];
|
||||
let lastDepth = 0;
|
||||
|
||||
nodes.sort((a, b) => a.depth - b.depth).forEach(node => {
|
||||
if (node.depth === lastDepth) {
|
||||
result[result.length - 1].push(node);
|
||||
} else {
|
||||
result.push([node]);
|
||||
lastDepth = node.depth;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Builds the command that will be executed to find all import statements for a package. */
|
||||
function buildPackageImportStatementFindCommand(searchDirectory: string, packageName: string) {
|
||||
if (platform() === 'win32') {
|
||||
|
||||
Reference in New Issue
Block a user