Skip to content

Commit

Permalink
fix(bazel): downlevel async generators along with async/await
Browse files Browse the repository at this point in the history
This is necessary as ESBuild does not support it. It is also
needed to bring our pipeline in sync with the Angular CLI.

Note that I combined both linker & optimization into a single
plugin so that we do not have to run slow Babel compilations
more than once. This matches with the Angular CLI too.

Long-term we might be able to leverage a plugin from them
directly when the ESBuild builder is the default.
  • Loading branch information
devversion committed Dec 12, 2022
1 parent 9a7aea3 commit c154da1
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 173 deletions.
24 changes: 14 additions & 10 deletions bazel/app-bundling/esbuild.config-tmpl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import * as path from 'path';

import {createEsbuildAngularOptimizePlugin} from '@angular/build-tooling/shared-scripts/angular-optimization/esbuild-plugin.mjs';
import {createEs2015LinkerPlugin} from '@angular/compiler-cli/linker/babel';
import {ConsoleLogger, NodeJSFileSystem, LogLevel} from '@angular/compiler-cli';
import {GLOBAL_DEFS_FOR_TERSER_WITH_AOT} from '@angular/compiler-cli/private/tooling';

/** Root path pointing to the app bundle source entry-point file. */
Expand All @@ -31,13 +29,6 @@ function isFileSideEffectFree(filePath) {
return !filePath.includes(entryPointBasepath);
}

/** Babel plugin running the Angular linker. */
const linkerBabelPlugin = createEs2015LinkerPlugin({
fileSystem: new NodeJSFileSystem(),
logger: new ConsoleLogger(LogLevel.warn),
linkerJitMode: false,
});

export default {
// Note: We prefer `.mjs` here as this is the extension used by Angular APF packages.
resolveExtensions: ['.mjs', '.js'],
Expand All @@ -58,7 +49,20 @@ export default {
},
// ESBuild requires the `define` option to take a string-based dictionary.
define: convertObjectToStringDictionary(GLOBAL_DEFS_FOR_TERSER_WITH_AOT),
plugins: [createEsbuildAngularOptimizePlugin(isFileSideEffectFree, [linkerBabelPlugin])],
plugins: [
await createEsbuildAngularOptimizePlugin({
optimize: {
isFileSideEffectFree: isFileSideEffectFree,
},
downlevelAsyncGeneratorsIfPresent: true,
enableLinker: {
ensureNoPartialDeclaration: false,
linkerOptions: {
linkerJitMode: false,
},
},
}),
],
};

/** Converts an object to a string dictionary. */
Expand Down
41 changes: 20 additions & 21 deletions bazel/spec-bundling/esbuild.config-tmpl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,17 @@
* found in the LICENSE file at https://angular.io/license
*/

/**
* Loads and creates the ESBuild linker plugin.
*
* The plugin is not loaded at top-level as not all spec bundle targets rely
* on the linker and this would slow-down bundling.
*/
async function fetchAndCreateLinkerEsbuildPlugin() {
// Note: This needs to be a NPM module path as this ESBuild config is generated and can
// end up in arbitrary Bazel packages or differently-named consumer workspaces.
const {createLinkerEsbuildPlugin} = await import(
'@angular/build-tooling/shared-scripts/angular-linker/esbuild-plugin.mjs'
);
return await createLinkerEsbuildPlugin(/.*/, /* ensureNoPartialDeclaration */ true, {
unknownDeclarationVersionHandling: TMPL_LINKER_UNKNOWN_DECLARATION_HANDLING,
});
}

// Based on the Bazel action and its substitutions, we run the linker for all inputs.
const plugins = TMPL_RUN_LINKER ? [await fetchAndCreateLinkerEsbuildPlugin()] : [];
import {createEsbuildAngularOptimizePlugin} from '@angular/build-tooling/shared-scripts/angular-optimization/esbuild-plugin.mjs';

// List of supported features as per ESBuild. See:
// https://esbuild.github.io/api/#supported.
const supported = {};

const downlevelAsyncAwait = TMPL_DOWNLEVEL_ASYNC_AWAIT;

// Async/Await can be downleveled so that ZoneJS can intercept. See:
// https://github.com/angular/angular-cli/blob/afe9feaa45913/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts#L313-L318.
if (TMPL_DOWNLEVEL_ASYNC_AWAIT) {
if (downlevelAsyncAwait) {
supported['async-await'] = false;
}

Expand All @@ -44,5 +28,20 @@ export default {
// Addition of `.mjs` to the non-jsx defaults. https://esbuild.github.io/api/#resolve-extensions
resolveExtensions: ['.mjs', '.js', '.json'],
supported,
plugins,
plugins: [
await createEsbuildAngularOptimizePlugin({
optimize: undefined,
downlevelAsyncGeneratorsIfPresent: downlevelAsyncAwait,
enableLinker: TMPL_RUN_LINKER
? {
ensureNoPartialDeclaration: true,
linkerOptions: {
// JIT mode is needed for tests overriding components/modules etc.
linkerJitMode: true,
unknownDeclarationVersionHandling: TMPL_LINKER_UNKNOWN_DECLARATION_HANDLING,
},
}
: undefined,
}),
],
};
2 changes: 1 addition & 1 deletion bazel/spec-bundling/spec-bundle.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def spec_bundle(
name = "%s_config" % name,
config_file = ":%s_config_file" % name,
testonly = True,
deps = ["//shared-scripts/angular-linker:js_lib"],
deps = ["//shared-scripts/angular-optimization:js_lib"],
)

if is_browser_test and not workspace_name:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@angular/benchpress": "0.3.0",
"@babel/core": "^7.16.0",
"@babel/helper-annotate-as-pure": "^7.18.6",
"@babel/plugin-proposal-async-generator-functions": "^7.20.1",
"@bazel/buildifier": "5.1.0",
"@bazel/concatjs": "5.7.3",
"@bazel/esbuild": "5.7.3",
Expand Down
1 change: 0 additions & 1 deletion shared-scripts/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package(default_visibility = ["//visibility:public"])
filegroup(
name = "static_files",
srcs = glob(["*"]) + [
"//shared-scripts/angular-linker:static_files",
"//shared-scripts/angular-optimization:static_files",
],
)
25 changes: 0 additions & 25 deletions shared-scripts/angular-linker/BUILD.bazel

This file was deleted.

91 changes: 0 additions & 91 deletions shared-scripts/angular-linker/esbuild-plugin.mjs

This file was deleted.

4 changes: 4 additions & 0 deletions shared-scripts/angular-optimization/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ js_library(
copy_to_bin(
name = "js_lib_files",
srcs = [
"ensure-no-linker-decl.mjs",
"esbuild-plugin.d.ts",
"esbuild-plugin.mjs",
],
)
Expand All @@ -41,6 +43,8 @@ js_library(
srcs = [":js_lib_files"],
deps = [
":angular_devkit_plugins",
"@npm//@angular/compiler-cli",
"@npm//@babel/core",
"@npm//@babel/plugin-proposal-async-generator-functions",
],
)
36 changes: 36 additions & 0 deletions shared-scripts/angular-optimization/ensure-no-linker-decl.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* @license
* Copyright Google LLC 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
*/

/** Naively checks whether this node path resolves to an Angular declare invocation. */
function isNgDeclareCallExpression(nodePath) {
if (!nodePath.node.name.startsWith('ɵɵngDeclare')) {
return false;
}

// Expect the `ngDeclare` identifier to be used as part of a property access that
// is invoked within a call expression. e.g. `i0.ɵɵngDeclare<>`.
return (
nodePath.parentPath?.type === 'MemberExpression' &&
nodePath.parentPath.parentPath?.type === 'CallExpression'
);
}

/** Asserts that the given AST does not contain any Angular partial declaration. */
export async function assertNoPartialDeclaration(filePath, ast, traverseFn) {
// Naively check if there are any Angular declarations left that haven't been linked.
traverseFn(ast, {
Identifier: (astPath) => {
if (isNgDeclareCallExpression(astPath)) {
throw astPath.buildCodeFrameError(
`Found Angular declaration that has not been linked. ${filePath}`,
Error,
);
}
},
});
}
18 changes: 18 additions & 0 deletions shared-scripts/angular-optimization/esbuild-plugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @license
* Copyright Google LLC 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
*/

export interface OptimizationOptions {
enableLinker?: {
ensureNoPartialDeclaration: boolean;
linkerOptions?: object;
};
optimize?: {
isSideEffectFree?: (absoluteDiskPath: string) => boolean;
};
downlevelAsyncGeneratorsIfPresent?: boolean;
}
Loading

0 comments on commit c154da1

Please sign in to comment.