Skip to content

Commit 8b77695

Browse files
bluwysapphi-red
andauthored
feat: respect esbuild minify config (#8754)
Co-authored-by: 翠 / green <[email protected]>
1 parent 8108b1b commit 8b77695

File tree

5 files changed

+285
-20
lines changed

5 files changed

+285
-20
lines changed

docs/config/build-options.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Produce SSR-oriented build. The value can be a string to directly specify the SS
145145

146146
Set to `false` to disable minification, or specify the minifier to use. The default is [esbuild](https://github.com/evanw/esbuild) which is 20 ~ 40x faster than terser and only 1 ~ 2% worse compression. [Benchmarks](https://github.com/privatenumber/minification-benchmarks)
147147

148-
Note the `build.minify` option is not available when using the `'es'` format in lib mode.
148+
Note the `build.minify` option does not minify whitespaces when using the `'es'` format in lib mode, as it removes pure annotations and break tree-shaking.
149149

150150
Terser must be installed when it is set to `'terser'`.
151151

docs/config/shared-options.md

+2
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ export default defineConfig({
280280
})
281281
```
282282

283+
When [`build.minify`](./build-options.md#build-minify) is `true`, you can configure to only minify [certain aspects](https://esbuild.github.io/api/#minify) of the code by setting either of `esbuild.minifyIdentifiers`, `esbuild.minifySyntax`, and `esbuild.minifyWhitespace` to `true`. Note the `esbuild.minify` option can't be used to override `build.minify`.
284+
283285
Set to `false` to disable esbuild transforms.
284286

285287
## assetsInclude
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import { describe, expect, test } from 'vitest'
2+
import type { ResolvedConfig, UserConfig } from '../../config'
3+
import { resolveEsbuildTranspileOptions } from '../../plugins/esbuild'
4+
5+
describe('resolveEsbuildTranspileOptions', () => {
6+
test('resolve default', () => {
7+
const options = resolveEsbuildTranspileOptions(
8+
defineResolvedConfig({
9+
build: {
10+
target: 'es2020',
11+
minify: 'esbuild'
12+
},
13+
esbuild: {
14+
keepNames: true
15+
}
16+
}),
17+
'es'
18+
)
19+
expect(options).toEqual({
20+
target: 'es2020',
21+
format: 'esm',
22+
keepNames: true,
23+
minify: true,
24+
treeShaking: true
25+
})
26+
})
27+
28+
test('resolve esnext no minify', () => {
29+
const options = resolveEsbuildTranspileOptions(
30+
defineResolvedConfig({
31+
build: {
32+
target: 'esnext',
33+
minify: false
34+
},
35+
esbuild: {
36+
keepNames: true
37+
}
38+
}),
39+
'es'
40+
)
41+
expect(options).toEqual(null)
42+
})
43+
44+
test('resolve no minify', () => {
45+
const options = resolveEsbuildTranspileOptions(
46+
defineResolvedConfig({
47+
build: {
48+
target: 'es2020',
49+
minify: false
50+
},
51+
esbuild: {
52+
keepNames: true
53+
}
54+
}),
55+
'es'
56+
)
57+
expect(options).toEqual({
58+
target: 'es2020',
59+
format: 'esm',
60+
keepNames: true,
61+
minify: false,
62+
minifyIdentifiers: false,
63+
minifySyntax: false,
64+
minifyWhitespace: false,
65+
treeShaking: false
66+
})
67+
})
68+
69+
test('resolve es lib', () => {
70+
const options = resolveEsbuildTranspileOptions(
71+
defineResolvedConfig({
72+
build: {
73+
minify: 'esbuild',
74+
lib: {
75+
entry: './somewhere.js'
76+
}
77+
},
78+
esbuild: {
79+
keepNames: true
80+
}
81+
}),
82+
'es'
83+
)
84+
expect(options).toEqual({
85+
target: undefined,
86+
format: 'esm',
87+
keepNames: true,
88+
minify: false,
89+
minifyIdentifiers: true,
90+
minifySyntax: true,
91+
minifyWhitespace: false,
92+
treeShaking: true
93+
})
94+
})
95+
96+
test('resolve cjs lib', () => {
97+
const options = resolveEsbuildTranspileOptions(
98+
defineResolvedConfig({
99+
build: {
100+
minify: 'esbuild',
101+
lib: {
102+
entry: './somewhere.js'
103+
}
104+
},
105+
esbuild: {
106+
keepNames: true
107+
}
108+
}),
109+
'cjs'
110+
)
111+
expect(options).toEqual({
112+
target: undefined,
113+
format: 'cjs',
114+
keepNames: true,
115+
minify: true,
116+
treeShaking: true
117+
})
118+
})
119+
120+
test('resolve es lib with specific minify options', () => {
121+
const options = resolveEsbuildTranspileOptions(
122+
defineResolvedConfig({
123+
build: {
124+
minify: 'esbuild',
125+
lib: {
126+
entry: './somewhere.js'
127+
}
128+
},
129+
esbuild: {
130+
keepNames: true,
131+
minifyIdentifiers: true,
132+
minifyWhitespace: true
133+
}
134+
}),
135+
'es'
136+
)
137+
expect(options).toEqual({
138+
target: undefined,
139+
format: 'esm',
140+
keepNames: true,
141+
minify: false,
142+
minifyIdentifiers: true,
143+
minifyWhitespace: false,
144+
treeShaking: true
145+
})
146+
})
147+
148+
test('resolve cjs lib with specific minify options', () => {
149+
const options = resolveEsbuildTranspileOptions(
150+
defineResolvedConfig({
151+
build: {
152+
minify: 'esbuild',
153+
lib: {
154+
entry: './somewhere.js'
155+
}
156+
},
157+
esbuild: {
158+
keepNames: true,
159+
minifyIdentifiers: true,
160+
minifyWhitespace: true,
161+
treeShaking: true
162+
}
163+
}),
164+
'cjs'
165+
)
166+
expect(options).toEqual({
167+
target: undefined,
168+
format: 'cjs',
169+
keepNames: true,
170+
minify: false,
171+
minifyIdentifiers: true,
172+
minifyWhitespace: true,
173+
treeShaking: true
174+
})
175+
})
176+
})
177+
178+
/**
179+
* Helper for `resolveEsbuildTranspileOptions` to created resolved config with types.
180+
* Note: The function only uses `build.target`, `build.minify` and `esbuild` options.
181+
*/
182+
function defineResolvedConfig(config: UserConfig): ResolvedConfig {
183+
return config as any
184+
}

packages/vite/src/node/__tests__/plugins/importGlob/__snapshots__/fixture.test.ts.snap

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const parent = Object.assign({
5757
export const rootMixedRelative = Object.assign({
5858
\\"/css.spec.ts\\": () => import(\\"../../css.spec.ts?url\\").then(m => m[\\"default\\"]),
5959
\\"/define.spec.ts\\": () => import(\\"../../define.spec.ts?url\\").then(m => m[\\"default\\"]),
60+
\\"/esbuild.spec.ts\\": () => import(\\"../../esbuild.spec.ts?url\\").then(m => m[\\"default\\"]),
6061
\\"/import.spec.ts\\": () => import(\\"../../import.spec.ts?url\\").then(m => m[\\"default\\"]),
6162
\\"/importGlob/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url\\").then(m => m[\\"default\\"]),
6263
\\"/importGlob/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url\\").then(m => m[\\"default\\"]),
@@ -131,6 +132,7 @@ export const parent = Object.assign({
131132
export const rootMixedRelative = Object.assign({
132133
\\"/css.spec.ts\\": () => import(\\"../../css.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
133134
\\"/define.spec.ts\\": () => import(\\"../../define.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
135+
\\"/esbuild.spec.ts\\": () => import(\\"../../esbuild.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
134136
\\"/import.spec.ts\\": () => import(\\"../../import.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
135137
\\"/importGlob/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
136138
\\"/importGlob/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url&lang.ts\\").then(m => m[\\"default\\"]),

packages/vite/src/node/plugins/esbuild.ts

+96-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
} from 'esbuild'
99
import { transform } from 'esbuild'
1010
import type { RawSourceMap } from '@ampproject/remapping'
11-
import type { SourceMap } from 'rollup'
11+
import type { InternalModuleFormat, SourceMap } from 'rollup'
1212
import type { TSConfckParseOptions, TSConfckParseResult } from 'tsconfck'
1313
import { TSConfckParseError, findAll, parse } from 'tsconfck'
1414
import {
@@ -37,6 +37,10 @@ export interface ESBuildOptions extends TransformOptions {
3737
include?: string | RegExp | string[] | RegExp[]
3838
exclude?: string | RegExp | string[] | RegExp[]
3939
jsxInject?: string
40+
/**
41+
* This option is not respected. Use `build.minify` instead.
42+
*/
43+
minify?: never
4044
}
4145

4246
export type ESBuildTransformResult = Omit<TransformResult, 'map'> & {
@@ -170,6 +174,17 @@ export function esbuildPlugin(options: ESBuildOptions = {}): Plugin {
170174
options.exclude || /\.js$/
171175
)
172176

177+
// Remove optimization options for dev as we only need to transpile them,
178+
// and for build as the final optimization is in `buildEsbuildPlugin`
179+
const transformOptions: TransformOptions = {
180+
...options,
181+
minify: false,
182+
minifyIdentifiers: false,
183+
minifySyntax: false,
184+
minifyWhitespace: false,
185+
treeShaking: false
186+
}
187+
173188
return {
174189
name: 'vite:esbuild',
175190
configureServer(_server) {
@@ -188,7 +203,7 @@ export function esbuildPlugin(options: ESBuildOptions = {}): Plugin {
188203
},
189204
async transform(code, id) {
190205
if (filter(id) || filter(cleanUrl(id))) {
191-
const result = await transformWithEsbuild(code, id, options)
206+
const result = await transformWithEsbuild(code, id, transformOptions)
192207
if (result.warnings.length) {
193208
result.warnings.forEach((m) => {
194209
this.warn(prettifyMessage(m, code))
@@ -236,27 +251,13 @@ export const buildEsbuildPlugin = (config: ResolvedConfig): Plugin => {
236251
return null
237252
}
238253

239-
const target = config.build.target
240-
const minify = config.build.minify === 'esbuild'
254+
const options = resolveEsbuildTranspileOptions(config, opts.format)
241255

242-
if ((!target || target === 'esnext') && !minify) {
256+
if (!options) {
243257
return null
244258
}
245259

246-
const res = await transformWithEsbuild(code, chunk.fileName, {
247-
...config.esbuild,
248-
target: target || undefined,
249-
...(minify
250-
? {
251-
// Do not minify ES lib output since that would remove pure annotations
252-
// and break tree-shaking
253-
// https://github.com/vuejs/core/issues/2860#issuecomment-926882793
254-
minify: !(config.build.lib && opts.format === 'es'),
255-
treeShaking: true,
256-
format: rollupToEsbuildFormatMap[opts.format]
257-
}
258-
: undefined)
259-
})
260+
const res = await transformWithEsbuild(code, chunk.fileName, options)
260261

261262
if (config.build.lib) {
262263
// #7188, esbuild adds helpers out of the UMD and IIFE wrappers, and the
@@ -282,6 +283,82 @@ export const buildEsbuildPlugin = (config: ResolvedConfig): Plugin => {
282283
}
283284
}
284285

286+
export function resolveEsbuildTranspileOptions(
287+
config: ResolvedConfig,
288+
format: InternalModuleFormat
289+
): TransformOptions | null {
290+
const target = config.build.target
291+
const minify = config.build.minify === 'esbuild'
292+
293+
if ((!target || target === 'esnext') && !minify) {
294+
return null
295+
}
296+
297+
// Do not minify whitespace for ES lib output since that would remove
298+
// pure annotations and break tree-shaking
299+
// https://github.com/vuejs/core/issues/2860#issuecomment-926882793
300+
const isEsLibBuild = config.build.lib && format === 'es'
301+
const options: TransformOptions = {
302+
...config.esbuild,
303+
target: target || undefined,
304+
format: rollupToEsbuildFormatMap[format]
305+
}
306+
307+
// If no minify, disable all minify options
308+
if (!minify) {
309+
return {
310+
...options,
311+
minify: false,
312+
minifyIdentifiers: false,
313+
minifySyntax: false,
314+
minifyWhitespace: false,
315+
treeShaking: false
316+
}
317+
}
318+
319+
// If user enable fine-grain minify options, minify with their options instead
320+
if (
321+
options.minifyIdentifiers ||
322+
options.minifySyntax ||
323+
options.minifyWhitespace
324+
) {
325+
if (isEsLibBuild) {
326+
// Disable minify whitespace as it breaks tree-shaking
327+
return {
328+
...options,
329+
minify: false,
330+
minifyWhitespace: false,
331+
treeShaking: true
332+
}
333+
} else {
334+
return {
335+
...options,
336+
minify: false,
337+
treeShaking: true
338+
}
339+
}
340+
}
341+
342+
// Else apply default minify options
343+
if (isEsLibBuild) {
344+
// Minify all except whitespace as it breaks tree-shaking
345+
return {
346+
...options,
347+
minify: false,
348+
minifyIdentifiers: true,
349+
minifySyntax: true,
350+
minifyWhitespace: false,
351+
treeShaking: true
352+
}
353+
} else {
354+
return {
355+
...options,
356+
minify: true,
357+
treeShaking: true
358+
}
359+
}
360+
}
361+
285362
function prettifyMessage(m: Message, code: string): string {
286363
let res = colors.yellow(m.text)
287364
if (m.location) {

0 commit comments

Comments
 (0)