-
Notifications
You must be signed in to change notification settings - Fork 12k
/
Copy pathjavascript-optimizer-worker.ts
122 lines (108 loc) · 3.1 KB
/
javascript-optimizer-worker.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
* @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 remapping from '@ampproject/remapping';
import { transform } from 'esbuild';
import { minify } from 'terser';
/**
* A request to optimize JavaScript using the supplied options.
*/
interface OptimizeRequest {
/**
* The options to use when optimizing.
*/
options: {
advanced: boolean;
define?: Record<string, string>;
keepNames: boolean;
removeLicenses: boolean;
sourcemap: boolean;
target: 5 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020;
};
/**
* The JavaScript asset to optimize.
*/
asset: {
name: string;
code: string;
map: object;
};
}
export default async function ({ asset, options }: OptimizeRequest) {
// esbuild is used as a first pass
const esbuildResult = await transform(asset.code, {
minifyIdentifiers: !options.keepNames,
minifySyntax: true,
// NOTE: Disabling whitespace ensures unused pure annotations are kept
minifyWhitespace: false,
pure: ['forwardRef'],
legalComments: options.removeLicenses ? 'none' : 'inline',
sourcefile: asset.name,
sourcemap: options.sourcemap && 'external',
define: options.define,
keepNames: options.keepNames,
target: `es${options.target}`,
});
// terser is used as a second pass
const terserResult = await optimizeWithTerser(
asset.name,
esbuildResult.code,
options.sourcemap,
options.target,
options.advanced,
);
// Merge intermediate sourcemaps with input sourcemap if enabled
let fullSourcemap;
if (options.sourcemap) {
const partialSourcemaps = [];
if (esbuildResult.map) {
partialSourcemaps.unshift(JSON.parse(esbuildResult.map));
}
if (terserResult.map) {
partialSourcemaps.unshift(terserResult.map);
}
partialSourcemaps.push(asset.map);
fullSourcemap = remapping(partialSourcemaps, () => null);
}
return { name: asset.name, code: terserResult.code, map: fullSourcemap };
}
async function optimizeWithTerser(
name: string,
code: string,
sourcemaps: boolean,
target: OptimizeRequest['options']['target'],
advanced: boolean,
): Promise<{ code: string; map?: object }> {
const result = await minify(
{ [name]: code },
{
compress: {
passes: advanced ? 3 : 1,
pure_getters: advanced,
},
ecma: target,
// esbuild in the first pass is used to minify identifiers instead of mangle here
mangle: false,
format: {
// ASCII output is enabled here as well to prevent terser from converting back to UTF-8
ascii_only: true,
wrap_func_args: false,
},
sourceMap:
sourcemaps &&
({
asObject: true,
// typings don't include asObject option
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any),
},
);
if (!result.code) {
throw new Error('Terser failed for unknown reason.');
}
return { code: result.code, map: result.map as object };
}