Skip to content

Commit 8e82263

Browse files
clydinalan-agius4
authored andcommitted
perf(@angular-devkit/build-angular): use esbuild/terser combination to optimize global scripts
`esbuild` and `terser` are now used to optimize global scripts in addition to the previous performance enhancement of optimizing application bundles. This change removes the need for the `terser-webpack-plugin` as a direct dependency and provides further production build time improvements for applications which use global scripts (`scripts` option in the `angular.json` file). Since `esbuild` does not support optimizing a script with ES2015+ syntax when targetting ES5, the JavaScript optimizer will fallback to only using terser in the event that such a situation occurs. This will only happen if ES5 is the output target for an application and a global script contains ES2015+ syntax. In that case, the global script is technically already invalid for the target environment and may fail at runtime but this is and has been considered a configuration issue. Global scripts must be compatible with the target environment/browsers.
1 parent 1341ad6 commit 8e82263

File tree

7 files changed

+75
-47
lines changed

7 files changed

+75
-47
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,6 @@
216216
"tar": "^6.1.6",
217217
"temp": "^0.9.0",
218218
"terser": "5.7.1",
219-
"terser-webpack-plugin": "5.1.4",
220219
"text-table": "0.2.0",
221220
"tree-kill": "1.2.2",
222221
"ts-node": "^10.0.0",

packages/angular_devkit/build_angular/BUILD.bazel

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ ts_library(
174174
"@npm//stylus",
175175
"@npm//stylus-loader",
176176
"@npm//terser",
177-
"@npm//terser-webpack-plugin",
178177
"@npm//text-table",
179178
"@npm//tree-kill",
180179
"@npm//tslib",

packages/angular_devkit/build_angular/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
"stylus": "0.54.8",
6464
"stylus-loader": "6.1.0",
6565
"terser": "5.7.1",
66-
"terser-webpack-plugin": "5.1.4",
6766
"text-table": "0.2.0",
6867
"tree-kill": "1.2.2",
6968
"tslib": "2.3.0",

packages/angular_devkit/build_angular/src/builders/browser/tests/behavior/typescript-target_spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,43 @@ describeBuilder(buildWebpackBrowser, BROWSER_BUILDER_INFO, (harness) => {
160160
harness.expectFile('dist/main.js').content.not.toMatch(/\sawait\s/);
161161
harness.expectFile('dist/main.js').content.toContain('"for await...of"');
162162
});
163+
164+
it('allows optimizing global scripts with ES2015+ syntax when targetting ES5', async () => {
165+
await harness.modifyFile('src/tsconfig.app.json', (content) => {
166+
const tsconfig = JSON.parse(content);
167+
if (!tsconfig.compilerOptions) {
168+
tsconfig.compilerOptions = {};
169+
}
170+
tsconfig.compilerOptions.target = 'es5';
171+
172+
return JSON.stringify(tsconfig);
173+
});
174+
175+
// Add global script with ES2015+ syntax to the project
176+
await harness.writeFile(
177+
'src/es2015-syntax.js',
178+
`
179+
class foo {
180+
bar() {
181+
console.log('baz');
182+
}
183+
}
184+
185+
(new foo()).bar();
186+
`,
187+
);
188+
189+
harness.useTarget('build', {
190+
...BASE_OPTIONS,
191+
optimization: {
192+
scripts: true,
193+
},
194+
scripts: ['src/es2015-syntax.js'],
195+
});
196+
197+
const { result } = await harness.executeOnce();
198+
199+
expect(result?.success).toBe(true);
200+
});
163201
});
164202
});

packages/angular_devkit/build_angular/src/webpack/configs/common.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ import { WebpackConfigOptions } from '../../utils/build-options';
3131
import { findCachePath } from '../../utils/cache-path';
3232
import {
3333
allowMangle,
34-
allowMinify,
3534
cachingDisabled,
36-
maxWorkers,
3735
persistentBuildCacheEnabled,
3836
profilingEnabled,
3937
} from '../../utils/environment-options';
@@ -298,34 +296,7 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
298296
}
299297

300298
const extraMinimizers = [];
301-
302299
if (scriptsOptimization) {
303-
const globalScriptsNames = globalScriptsByBundleName.map((s) => s.bundleName);
304-
305-
if (globalScriptsNames.length > 0) {
306-
// Script bundles are fully optimized here in one step since they are never downleveled.
307-
// They are shared between ES2015 & ES5 outputs so must support ES5.
308-
// The `terser-webpack-plugin` will add the minified flag to the asset which will prevent
309-
// additional optimizations by the next plugin.
310-
const TerserPlugin = require('terser-webpack-plugin');
311-
extraMinimizers.push(
312-
new TerserPlugin({
313-
parallel: maxWorkers,
314-
extractComments: false,
315-
include: globalScriptsNames,
316-
terserOptions: {
317-
ecma: 5,
318-
compress: allowMinify,
319-
output: {
320-
ascii_only: true,
321-
wrap_func_args: false,
322-
},
323-
mangle: allowMangle && platform !== 'server',
324-
},
325-
}),
326-
);
327-
}
328-
329300
extraMinimizers.push(
330301
new JavaScriptOptimizerPlugin({
331302
define: buildOptions.aot ? GLOBAL_DEFS_FOR_TERSER_WITH_AOT : GLOBAL_DEFS_FOR_TERSER,

packages/angular_devkit/build_angular/src/webpack/plugins/javascript-optimizer-worker.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import remapping from '@ampproject/remapping';
10-
import { transform } from 'esbuild';
10+
import { TransformFailure, transform } from 'esbuild';
1111
import { minify } from 'terser';
1212

1313
/**
@@ -38,19 +38,41 @@ interface OptimizeRequest {
3838

3939
export default async function ({ asset, options }: OptimizeRequest) {
4040
// esbuild is used as a first pass
41-
const esbuildResult = await transform(asset.code, {
42-
minifyIdentifiers: !options.keepNames,
43-
minifySyntax: true,
44-
// NOTE: Disabling whitespace ensures unused pure annotations are kept
45-
minifyWhitespace: false,
46-
pure: ['forwardRef'],
47-
legalComments: options.removeLicenses ? 'none' : 'inline',
48-
sourcefile: asset.name,
49-
sourcemap: options.sourcemap && 'external',
50-
define: options.define,
51-
keepNames: options.keepNames,
52-
target: `es${options.target}`,
53-
});
41+
let esbuildResult;
42+
try {
43+
esbuildResult = await transform(asset.code, {
44+
minifyIdentifiers: !options.keepNames,
45+
minifySyntax: true,
46+
// NOTE: Disabling whitespace ensures unused pure annotations are kept
47+
minifyWhitespace: false,
48+
pure: ['forwardRef'],
49+
legalComments: options.removeLicenses ? 'none' : 'inline',
50+
sourcefile: asset.name,
51+
sourcemap: options.sourcemap && 'external',
52+
define: options.define,
53+
keepNames: options.keepNames,
54+
target: `es${options.target}`,
55+
});
56+
} catch (error) {
57+
const failure = error as TransformFailure;
58+
59+
// If esbuild fails with only ES5 support errors, fallback to just terser.
60+
// This will only happen if ES5 is the output target and a global script contains ES2015+ syntax.
61+
// In that case, the global script is technically already invalid for the target environment but
62+
// this is and has been considered a configuration issue. Global scripts must be compatible with
63+
// the target environment.
64+
if (
65+
failure.errors?.every((error) =>
66+
error.text.includes('to the configured target environment ("es5") is not supported yet'),
67+
)
68+
) {
69+
esbuildResult = {
70+
code: asset.code,
71+
};
72+
} else {
73+
throw error;
74+
}
75+
}
5476

5577
// terser is used as a second pass
5678
const terserResult = await optimizeWithTerser(

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10369,7 +10369,7 @@ temp@^0.9.0:
1036910369
mkdirp "^0.5.1"
1037010370
rimraf "~2.6.2"
1037110371

10372-
terser-webpack-plugin@5.1.4, terser-webpack-plugin@^5.1.3:
10372+
terser-webpack-plugin@^5.1.3:
1037310373
version "5.1.4"
1037410374
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz#c369cf8a47aa9922bd0d8a94fe3d3da11a7678a1"
1037510375
integrity sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==

0 commit comments

Comments
 (0)