Skip to content

Commit

Permalink
refactor(@angular-devkit/build-angular): use fast-glob for file searc…
Browse files Browse the repository at this point in the history
…hing support

The file searching within the build system (both Webpack and esbuild) now use the
`fast-glob` package for globbing which provides a small performance improvement.
Since the assets option in particular is within the critical path of the buil pipeline,
the performance benefit from the switch will be most prevalent in asset heavy projects.
As an example, the Angular Material documentation site saw the asset discovery time
reduced by over half with the switch. `fast-glob` is also the package used by Vite
which provides additional benefit by ensuring that the Angular CLI behavior matches
that of the newly integrated Vite development server.
  • Loading branch information
clydin committed May 31, 2023
1 parent 50955db commit ee5763d
Show file tree
Hide file tree
Showing 17 changed files with 50 additions and 87 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@
"@types/browserslist": "^4.15.0",
"@types/cacache": "^15.0.0",
"@types/express": "^4.16.0",
"@types/glob": "^8.0.0",
"@types/http-proxy": "^1.17.4",
"@types/ini": "^1.3.31",
"@types/inquirer": "^8.0.0",
Expand Down Expand Up @@ -143,7 +142,7 @@
"eslint-plugin-header": "3.1.1",
"eslint-plugin-import": "2.27.5",
"express": "4.18.2",
"glob": "8.1.0",
"fast-glob": "3.2.12",
"http-proxy": "^1.18.1",
"https-proxy-agent": "5.0.1",
"husky": "8.0.3",
Expand Down
6 changes: 2 additions & 4 deletions packages/angular_devkit/build_angular/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ ts_library(
"@npm//@types/babel__template",
"@npm//@types/browserslist",
"@npm//@types/cacache",
"@npm//@types/glob",
"@npm//@types/inquirer",
"@npm//@types/karma",
"@npm//@types/less",
Expand All @@ -153,7 +152,7 @@ ts_library(
"@npm//css-loader",
"@npm//esbuild",
"@npm//esbuild-wasm",
"@npm//glob",
"@npm//fast-glob",
"@npm//https-proxy-agent",
"@npm//inquirer",
"@npm//jsonc-parser",
Expand Down Expand Up @@ -357,8 +356,7 @@ LARGE_SPECS = {
},
"jest": {
"extra_deps": [
"@npm//glob",
"@npm//@types/glob",
"@npm//fast-glob",
],
},
}
Expand Down
2 changes: 1 addition & 1 deletion packages/angular_devkit/build_angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"critters": "0.0.18",
"css-loader": "6.8.1",
"esbuild-wasm": "0.17.19",
"glob": "8.1.0",
"fast-glob": "3.2.12",
"https-proxy-agent": "5.0.1",
"inquirer": "8.2.4",
"jsonc-parser": "3.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import { hasMagic as isDynamicPattern } from 'glob';
import { isDynamicPattern } from 'fast-glob';
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { extname, resolve } from 'node:path';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

import { IOptions as GlobOptions, glob as globCb } from 'glob';
import { promisify } from 'util';
import fastGlob, { Options as GlobOptions } from 'fast-glob';
import { JestBuilderOptions } from './options';

const globAsync = promisify(globCb);

/**
* Finds all test files in the project.
*
Expand All @@ -24,15 +21,13 @@ const globAsync = promisify(globCb);
export async function findTestFiles(
options: JestBuilderOptions,
workspaceRoot: string,
glob: typeof globAsync = globAsync,
glob: typeof fastGlob = fastGlob,
): Promise<Set<string>> {
const globOptions: GlobOptions = {
cwd: workspaceRoot,
ignore: ['node_modules/**'].concat(options.exclude),
strict: true, // Fail on an "unusual error" when reading the file system.
nobrace: true, // Do not expand `a{b,c}` to `ab,ac`.
noext: true, // Disable "extglob" patterns.
nodir: true, // Match only files, don't care about directories.
braceExpansion: false, // Do not expand `a{b,c}` to `ab,ac`.
extglob: false, // Disable "extglob" patterns.
};

const included = await Promise.all(options.include.map((pattern) => glob(pattern, globOptions)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/

// eslint-disable-next-line import/no-extraneous-dependencies
import realGlob from 'fast-glob';
import { promises as fs } from 'fs';
import { glob as globCb } from 'glob';
import * as path from 'path';
import { promisify } from 'util';
import { findTestFiles } from '../test-files';
import { BASE_OPTIONS } from './options';

const realGlob = promisify(globCb);

describe('test-files', () => {
describe('findTestFiles()', () => {
let tempDir!: string;
Expand Down Expand Up @@ -118,7 +116,8 @@ describe('test-files', () => {
include: ['*.spec.ts', '*.stuff.ts', '*.test.ts'],
},
tempDir,
glob,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
glob as any,
),
).toBeRejectedWith(err);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
*/

import assert from 'assert';
import glob, { isDynamicPattern } from 'fast-glob';
import { PathLike, constants, promises as fs } from 'fs';
import glob, { hasMagic } from 'glob';
import { pluginName } from 'mini-css-extract-plugin';
import { basename, dirname, extname, join, relative } from 'path';
import { promisify } from 'util';
import type { Compilation, Compiler } from 'webpack';

const globPromise = promisify(glob);

/**
* The name of the plugin provided to Webpack when tapping Webpack compiler hooks.
*/
Expand Down Expand Up @@ -114,7 +111,7 @@ async function findMatchingTests(
}

// special logic when pattern does not look like a glob
if (!hasMagic(normalizedPattern)) {
if (!isDynamicPattern(normalizedPattern)) {
if (await isDirectory(join(projectSourceRoot, normalizedPattern))) {
normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`;
} else {
Expand All @@ -133,10 +130,8 @@ async function findMatchingTests(
}
}

return globPromise(normalizedPattern, {
return glob(normalizedPattern, {
cwd: projectSourceRoot,
root: projectSourceRoot,
nomount: true,
absolute: true,
ignore: ['**/node_modules/**', ...ignore],
});
Expand Down
16 changes: 5 additions & 11 deletions packages/angular_devkit/build_angular/src/utils/copy-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/

import * as fs from 'fs';
import glob from 'glob';
import * as path from 'path';
import { promisify } from 'util';

const globPromise = promisify(glob);
import glob from 'fast-glob';
import fs from 'node:fs';
import path from 'node:path';

export async function copyAssets(
entries: {
Expand All @@ -32,14 +29,11 @@ export async function copyAssets(

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

const directoryExists = new Set<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import type { ObjectPattern } from 'copy-webpack-plugin';
import { createHash } from 'crypto';
import glob from 'glob';
import glob from 'fast-glob';
import * as path from 'path';
import type { Configuration, WebpackOptionsNormalized } from 'webpack';
import {
Expand Down Expand Up @@ -125,9 +125,8 @@ export function getInstrumentationExcludedPaths(
const excluded = new Set<string>();

for (const excludeGlob of excludedPaths) {
glob
.sync(excludeGlob, { nodir: true, cwd: root, root, nomount: true })
.forEach((p) => excluded.add(path.join(root, p)));
const excludePath = excludeGlob[0] === '/' ? excludeGlob.slice(1) : excludeGlob;
glob.sync(excludePath, { cwd: root }).forEach((p) => excluded.add(path.join(root, p)));
}

return excluded;
Expand Down
4 changes: 1 addition & 3 deletions scripts/validate-user-analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@

import { logging } from '@angular-devkit/core';
import assert from 'assert';
import glob from 'fast-glob';
import * as fs from 'fs';
import { glob as globCb } from 'glob';
import * as path from 'path';
import { promisify } from 'util';
import { packages } from '../lib/packages';
import {
EventCustomDimension,
Expand Down Expand Up @@ -80,7 +79,6 @@ async function _checkDimensions(dimensionsTable: string, logger: logging.Logger)
}
};

const glob = promisify(globCb);
// Find all the schemas
const packagesPaths = Object.values(packages).map(({ root }) => root);
for (const packagePath of packagesPaths) {
Expand Down
2 changes: 1 addition & 1 deletion tests/legacy-cli/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ ts_library(
"//packages/angular_devkit/core",
"//packages/angular_devkit/core/node",
"//tests/legacy-cli/e2e/utils",
"@npm//@types/glob",
"@npm//@types/yargs-parser",
"@npm//ansi-colors",
"@npm//fast-glob",
"@npm//yargs-parser",
],
)
Expand Down
3 changes: 1 addition & 2 deletions tests/legacy-cli/e2e/tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ ts_library(
deps = [
"//tests/legacy-cli/e2e/utils",
"@npm//@types/express",
"@npm//@types/glob",
"@npm//@types/node-fetch",
"@npm//@types/semver",
"@npm//express",
"@npm//glob",
"@npm//fast-glob",
"@npm//node-fetch",
"@npm//semver",
],
Expand Down
7 changes: 2 additions & 5 deletions tests/legacy-cli/e2e/tests/misc/check-postinstalls.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import glob from 'glob';
import { promisify } from 'util';
import glob from 'fast-glob';
import { readFile } from '../../utils/fs';

const globAsync = promisify(glob);

const CURRENT_SCRIPT_PACKAGES: ReadonlySet<string> = new Set([
'esbuild (postinstall)',
'nice-napi (install)',
Expand All @@ -20,7 +17,7 @@ const FALSE_POSITIVE_PATHS: ReadonlySet<string> = new Set([
const INNER_NODE_MODULES_SEGMENT = '/node_modules/';

export default async function () {
const manifestPaths = await globAsync('node_modules/**/package.json');
const manifestPaths = await glob('node_modules/**/package.json');
const newPackages: string[] = [];

for (const manifestPath of manifestPaths) {
Expand Down
3 changes: 1 addition & 2 deletions tests/legacy-cli/e2e/utils/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ ts_library(
],
visibility = ["//visibility:public"],
deps = [
"@npm//@types/glob",
"@npm//@types/node-fetch",
"@npm//@types/semver",
"@npm//@types/tar",
"@npm//@types/yargs-parser",
"@npm//ansi-colors",
"@npm//glob",
"@npm//fast-glob",
"@npm//npm",
"@npm//protractor",
"@npm//rxjs",
Expand Down
4 changes: 2 additions & 2 deletions tests/legacy-cli/e2e/utils/assets.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { join } from 'path';
import { chmod } from 'fs/promises';
import glob from 'glob';
import glob from 'fast-glob';
import { getGlobalVariable } from './env';
import { resolve } from 'path';
import { copyFile } from './fs';
Expand All @@ -26,7 +26,7 @@ export function copyAssets(assetName: string, to?: string) {

return Promise.resolve()
.then(() => {
const allFiles = glob.sync('**/*', { dot: true, nodir: true, cwd: root });
const allFiles = glob.sync('**/*', { dot: true, cwd: root });

return allFiles.reduce((promise, filePath) => {
const toPath =
Expand Down
30 changes: 16 additions & 14 deletions tests/legacy-cli/e2e_runner.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createConsoleLogger } from '../../packages/angular_devkit/core/node';
import * as colors from 'ansi-colors';
import glob from 'glob';
import colors from 'ansi-colors';
import glob from 'fast-glob';
import yargsParser from 'yargs-parser';
import * as path from 'path';
import { getGlobalVariable, setGlobalVariable } from './e2e/utils/env';
Expand Down Expand Up @@ -120,11 +120,11 @@ const SRC_FILE_EXT_RE = /\.js$/;
const testGlob = argv.glob?.replace(/\.ts$/, '.js') || `tests/**/*.js`;

const e2eRoot = path.join(__dirname, 'e2e');
const allSetups = glob.sync(`setup/**/*.js`, { nodir: true, cwd: e2eRoot }).sort();
const allInitializers = glob.sync(`initialize/**/*.js`, { nodir: true, cwd: e2eRoot }).sort();
const allSetups = glob.sync(`setup/**/*.js`, { cwd: e2eRoot }).sort();
const allInitializers = glob.sync(`initialize/**/*.js`, { cwd: e2eRoot }).sort();

const allTests = glob
.sync(testGlob, { nodir: true, cwd: e2eRoot, ignore: argv.ignore })
.sync(testGlob, { cwd: e2eRoot, ignore: argv.ignore })
// Replace windows slashes.
.map((name) => name.replace(/\\/g, '/'))
.filter((name) => {
Expand Down Expand Up @@ -359,18 +359,20 @@ function printFooter(testName: string, type: 'setup' | 'initializer' | 'test', s
// Collect the packages passed as arguments and return as {package-name => pkg-path}
async function findPackageTars(): Promise<{ [pkg: string]: PkgInfo }> {
const pkgs: string[] = (getGlobalVariable('argv').package as string[]).flatMap((p) =>
glob.sync(p, { realpath: true }),
glob.sync(p),
);

const pkgJsons = await Promise.all(
pkgs.map(async (pkg) => {
try {
return await extractFile(pkg, './package/package.json');
} catch (e) {
// TODO(bazel): currently the bazel npm packaging does not contain the standard npm ./package directory
return await extractFile(pkg, './package.json');
}
}),
pkgs
.map((pkg) => realpathSync(pkg))
.map(async (pkg) => {
try {
return await extractFile(pkg, './package/package.json');
} catch (e) {
// TODO(bazel): currently the bazel npm packaging does not contain the standard npm ./package directory
return await extractFile(pkg, './package.json');
}
}),
);

return pkgs.reduce((all, pkg, i) => {
Expand Down
Loading

0 comments on commit ee5763d

Please sign in to comment.