diff --git a/goldens/size-test.yaml b/goldens/size-test.yaml index 74acb5d334f8..d9e29c2b69ae 100644 --- a/goldens/size-test.yaml +++ b/goldens/size-test.yaml @@ -1,18 +1,18 @@ -cdk/drag-drop/all-directives: 160859 -cdk/drag-drop/basic: 158225 -material-experimental/mdc-chips/basic: 385551 -material-experimental/mdc-form-field/advanced: 417584 -material-experimental/mdc-form-field/basic: 416339 -material/autocomplete/without-optgroup: 392028 -material/button-toggle/standalone: 124412 -material/chips/basic: 320073 -material/datepicker/range-picker/without-form-field: 505044 -material/expansion/without-accordion: 330526 -material/form-field/advanced: 377468 -material/form-field/basic: 376144 -material/list/nav-list: 328072 -material/menu/without-lazy-content: 398590 -material/radio/without-group: 127571 -material/select/basic: 437305 -material/tabs/advanced: 369608 -material/tabs/basic: 368747 +cdk/drag-drop/all-directives: 155091 +cdk/drag-drop/basic: 152522 +material-experimental/mdc-chips/basic: 249660 +material-experimental/mdc-form-field/advanced: 296409 +material-experimental/mdc-form-field/basic: 294855 +material/autocomplete/without-optgroup: 281544 +material/button-toggle/standalone: 186619 +material/chips/basic: 228157 +material/datepicker/range-picker/without-form-field: 398783 +material/expansion/without-accordion: 200184 +material/form-field/advanced: 247973 +material/form-field/basic: 246355 +material/list/nav-list: 194468 +material/menu/without-lazy-content: 286831 +material/radio/without-group: 189803 +material/select/basic: 329893 +material/tabs/advanced: 248653 +material/tabs/basic: 247787 diff --git a/integration/size-test/BUILD.bazel b/integration/size-test/BUILD.bazel index ecd3817d7448..b8252c3f4cbf 100644 --- a/integration/size-test/BUILD.bazel +++ b/integration/size-test/BUILD.bazel @@ -1,3 +1,4 @@ +load("@npm//@bazel/esbuild:index.bzl", "esbuild_config") load("//tools:defaults.bzl", "ts_library") package(default_visibility = ["//visibility:public"]) @@ -8,6 +9,16 @@ exports_files([ "index-tmpl.ts", ]) +esbuild_config( + name = "esbuild_config", + config_file = "esbuild.config.mjs", + deps = [ + "@npm//@angular-devkit/build-angular", + "@npm//@angular/compiler-cli", + "@npm//@babel/core", + ], +) + ts_library( name = "check-size", srcs = ["check-size.ts"], diff --git a/integration/size-test/esbuild.config.mjs b/integration/size-test/esbuild.config.mjs new file mode 100644 index 000000000000..cbfef364c0f5 --- /dev/null +++ b/integration/size-test/esbuild.config.mjs @@ -0,0 +1,90 @@ +/** + * @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 + */ + +import babel from '@babel/core'; +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'; +import adjustStaticClassMembersPlugin from '@angular-devkit/build-angular/src/babel/plugins/adjust-static-class-members.js'; +import elideAngularMetadataPlugin from '@angular-devkit/build-angular/src/babel/plugins/elide-angular-metadata.js'; +import adjustTypeScriptEnumsPlugin from '@angular-devkit/build-angular/src/babel/plugins/adjust-typescript-enums.js'; +import pureToplevelFunctionsPlugin from '@angular-devkit/build-angular/src/babel/plugins/pure-toplevel-functions.js'; +import fs from 'fs'; + +/** Babel plugin running the Angular linker. */ +const linkerBabelPlugin = createEs2015LinkerPlugin({ + fileSystem: new NodeJSFileSystem(), + logger: new ConsoleLogger(LogLevel.warn), + linkerJitMode: false, +}); + +/** + * ESBuild plugin configuring various optimization Babel plugins. The Babel plugins + * configured as part of this plugin run in the Angular CLI compilation pipeline as well. + */ +const esbuildBabelOptimizePlugin = { + name: 'ng-babel-optimize-esbuild', + setup: build => { + build.onLoad({filter: /.*/}, async args => { + const filePath = args.path; + const content = await fs.promises.readFile(filePath, 'utf8'); + const plugins = [ + linkerBabelPlugin, + adjustStaticClassMembersPlugin, + elideAngularMetadataPlugin, + adjustTypeScriptEnumsPlugin, + ]; + + // All files except for the auto-generated module entry-point are considered side-effect + // free. For these we can add the pure-top level Babel plugin. This matches conceptually + // with what is done in the Angular CLI compilation pipeline, with respect to everything + // in this repo being an official side-effect free APF package. + if (!args.path.includes('autogenerated_module_index.mjs')) { + plugins.push(pureToplevelFunctionsPlugin); + } + + const {code} = await babel.transformAsync(content, { + filename: filePath, + filenameRelative: filePath, + plugins: plugins, + // Sourcemaps are generated inline so that ESBuild can process them. + sourceMaps: 'inline', + compact: false, + }); + + return {contents: code}; + }); + }, +}; + +export default { + // Note: We prefer `.mjs` here as this is the extension used by Angular APF packages. + resolveExtensions: ['.mjs', '.js'], + conditions: ['es2020', 'es2015'], + mainFields: ['fesm2020', 'es2020', 'es2015', 'module'], + format: 'iife', + // The majority of these options match with the ones the CLI sets: + // https://github.com/angular/angular-cli/blob/0d76bf04bca6e083865972b5398a32bbe9396e14/packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-worker.ts#L133. + treeShaking: true, + minifyIdentifiers: true, + minifySyntax: true, + minifyWhitespace: false, + pure: ['forwardRef'], + legalComments: 'none', + // ESBuild requires the `define` option to take a string-based dictionary. + define: convertObjectToStringDictionary(GLOBAL_DEFS_FOR_TERSER_WITH_AOT), + plugins: [esbuildBabelOptimizePlugin], +}; + +/** Converts an object to a string dictionary. */ +function convertObjectToStringDictionary(value) { + return Object.entries(value).reduce((result, [propName, value]) => { + result[propName] = String(value); + return result; + }, {}); +} diff --git a/integration/size-test/index.bzl b/integration/size-test/index.bzl index 54ff065ed190..2dc225c946c3 100644 --- a/integration/size-test/index.bzl +++ b/integration/size-test/index.bzl @@ -1,13 +1,13 @@ load("@npm//@angular/dev-infra-private/bazel:expand_template.bzl", "expand_template") load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "nodejs_test") load("@bazel_skylib//lib:paths.bzl", "paths") -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") +load("@npm//@bazel/esbuild:index.bzl", "esbuild") load("@npm//@bazel/terser:index.bzl", "terser_minified") load("//tools:defaults.bzl", "ng_module") """ Performs size measurements for the specified file. The file will be built as part - of a `ng_module` and then will be optimized with build-optimizer, rollup and Terser. + of a `ng_module` and then will be optimized with esbuild, babel and terser. The resulting size will be validated against a golden file to ensure that we don't regress in payload size, or that we can improvements to payload size. @@ -35,31 +35,30 @@ def size_test(name, file, deps): testonly = True, deps = [ "@npm//@angular/core", - "@npm//@angular/platform-browser-dynamic", + "@npm//@angular/platform-browser", ] + deps, ) - rollup_bundle( + esbuild( name = "%s_bundle" % name, - config_file = "//integration/size-test:rollup.config.js", + config = "//integration/size-test:esbuild_config", testonly = True, - entry_points = { - (index_file): "%s_bundled" % name, - }, + minify = True, + entry_point = index_file, deps = [ ":%s_lib" % name, - "@npm//@rollup/plugin-node-resolve", - "@npm//@angular-devkit/build-optimizer", ], + target = "es2020", + platform = "browser", # Link the workspace root so that files can be loaded from the workspace. link_workspace_root = True, - sourcemap = "false", + sourcemap = "external", ) terser_minified( testonly = True, name = "%s_bundle_min" % name, - src = ":%s_bundle" % name, + src = "%s_bundle" % name, config_file = "//integration/size-test:terser-config.json", sourcemap = False, ) diff --git a/integration/size-test/material-experimental/mdc-chips/basic.ts b/integration/size-test/material-experimental/mdc-chips/basic.ts index bf44c255ee35..53c77cbb5d60 100644 --- a/integration/size-test/material-experimental/mdc-chips/basic.ts +++ b/integration/size-test/material-experimental/mdc-chips/basic.ts @@ -1,5 +1,4 @@ import {Component, NgModule} from '@angular/core'; -import {platformBrowser} from '@angular/platform-browser'; import {MatChipsModule} from '@angular/material-experimental/mdc-chips'; /** @@ -21,5 +20,3 @@ export class TestComponent {} bootstrap: [TestComponent], }) export class AppModule {} - -platformBrowser().bootstrapModule(AppModule); diff --git a/integration/size-test/material/chips/basic.ts b/integration/size-test/material/chips/basic.ts index 41cd96ab7096..8dc2673f5968 100644 --- a/integration/size-test/material/chips/basic.ts +++ b/integration/size-test/material/chips/basic.ts @@ -1,5 +1,4 @@ import {Component, NgModule} from '@angular/core'; -import {platformBrowser} from '@angular/platform-browser'; import {MatChipsModule} from '@angular/material/chips'; /** @@ -21,5 +20,3 @@ export class TestComponent {} bootstrap: [TestComponent], }) export class AppModule {} - -platformBrowser().bootstrapModule(AppModule); diff --git a/integration/size-test/rollup.config.js b/integration/size-test/rollup.config.js deleted file mode 100644 index a3e283a6958b..000000000000 --- a/integration/size-test/rollup.config.js +++ /dev/null @@ -1,35 +0,0 @@ -const { - buildOptimizer, -} = require('@angular-devkit/build-optimizer/src/build-optimizer/build-optimizer'); -const {nodeResolve} = require('@rollup/plugin-node-resolve'); - -const buildOptimizerPlugin = { - name: 'build-optimizer', - transform: (content, id) => { - const {content: code, sourceMap: map} = buildOptimizer({ - content, - inputFilePath: id, - emitSourceMap: true, - // Always assume side-effect free source files, except for the autogenerated - // module index file. The bootstrap module call should not be eliminated. - isSideEffectFree: !id.endsWith('_autogenerated_module_index.mjs'), - isAngularCoreFile: false, - }); - if (!code) { - return null; - } - if (!map) { - throw new Error('No sourcemap produced by build optimizer'); - } - return {code, map}; - }, -}; - -module.exports = { - plugins: [ - buildOptimizerPlugin, - nodeResolve({ - mainFields: ['es2020', 'module'], - }), - ], -}; diff --git a/integration/size-test/terser-config.json b/integration/size-test/terser-config.json index 000fc5b4ef43..d0af1569ea37 100644 --- a/integration/size-test/terser-config.json +++ b/integration/size-test/terser-config.json @@ -1,17 +1,12 @@ { - "output": { - "ecma": "es2015", - "comments": false - }, + "ecma": "es2020", "compress": { - "global_defs": { - "ngDevMode": false, - "ngI18nClosureMode": false, - "ngJitMode": false - }, - "passes": 3, + "passes": 2, "pure_getters": true }, - "toplevel": true, - "mangle": true + "format": { + "ascii_only": true, + "wrap_func_args": false + }, + "mangle": false } diff --git a/package.json b/package.json index 2b9d24835792..76c4853811a5 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,6 @@ }, "devDependencies": { "@angular-devkit/build-angular": "13.0.0-next.7", - "@angular-devkit/build-optimizer": "0.1300.0-next.7", "@angular-devkit/core": "13.0.0-next.7", "@angular-devkit/schematics": "13.0.0-next.7", "@angular/bazel": "13.0.0-next.15", diff --git a/tools/angular/esbuild.config.mjs b/tools/angular/esbuild.config.mjs index 694075b696e3..99a2bd9d8e9b 100644 --- a/tools/angular/esbuild.config.mjs +++ b/tools/angular/esbuild.config.mjs @@ -9,6 +9,7 @@ import {createLinkerEsbuildPlugin} from './create_linker_esbuild_plugin.mjs'; export default { + // Note: We support `.mjs` here as this is the extension used by Angular APF packages. resolveExtensions: ['.mjs', '.js'], format: 'esm', plugins: [