Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: respect esbuild minify config (#8754)
Co-authored-by: 翠 / green <green@sapphi.red>
  • Loading branch information
bluwy and sapphi-red committed Jun 25, 2022
1 parent 8108b1b commit 8b77695
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 20 deletions.
2 changes: 1 addition & 1 deletion docs/config/build-options.md
Expand Up @@ -145,7 +145,7 @@ Produce SSR-oriented build. The value can be a string to directly specify the SS

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)

Note the `build.minify` option is not available when using the `'es'` format in lib mode.
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.

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

Expand Down
2 changes: 2 additions & 0 deletions docs/config/shared-options.md
Expand Up @@ -280,6 +280,8 @@ export default defineConfig({
})
```

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`.

Set to `false` to disable esbuild transforms.

## assetsInclude
Expand Down
184 changes: 184 additions & 0 deletions packages/vite/src/node/__tests__/plugins/esbuild.spec.ts
@@ -0,0 +1,184 @@
import { describe, expect, test } from 'vitest'
import type { ResolvedConfig, UserConfig } from '../../config'
import { resolveEsbuildTranspileOptions } from '../../plugins/esbuild'

describe('resolveEsbuildTranspileOptions', () => {
test('resolve default', () => {
const options = resolveEsbuildTranspileOptions(
defineResolvedConfig({
build: {
target: 'es2020',
minify: 'esbuild'
},
esbuild: {
keepNames: true
}
}),
'es'
)
expect(options).toEqual({
target: 'es2020',
format: 'esm',
keepNames: true,
minify: true,
treeShaking: true
})
})

test('resolve esnext no minify', () => {
const options = resolveEsbuildTranspileOptions(
defineResolvedConfig({
build: {
target: 'esnext',
minify: false
},
esbuild: {
keepNames: true
}
}),
'es'
)
expect(options).toEqual(null)
})

test('resolve no minify', () => {
const options = resolveEsbuildTranspileOptions(
defineResolvedConfig({
build: {
target: 'es2020',
minify: false
},
esbuild: {
keepNames: true
}
}),
'es'
)
expect(options).toEqual({
target: 'es2020',
format: 'esm',
keepNames: true,
minify: false,
minifyIdentifiers: false,
minifySyntax: false,
minifyWhitespace: false,
treeShaking: false
})
})

test('resolve es lib', () => {
const options = resolveEsbuildTranspileOptions(
defineResolvedConfig({
build: {
minify: 'esbuild',
lib: {
entry: './somewhere.js'
}
},
esbuild: {
keepNames: true
}
}),
'es'
)
expect(options).toEqual({
target: undefined,
format: 'esm',
keepNames: true,
minify: false,
minifyIdentifiers: true,
minifySyntax: true,
minifyWhitespace: false,
treeShaking: true
})
})

test('resolve cjs lib', () => {
const options = resolveEsbuildTranspileOptions(
defineResolvedConfig({
build: {
minify: 'esbuild',
lib: {
entry: './somewhere.js'
}
},
esbuild: {
keepNames: true
}
}),
'cjs'
)
expect(options).toEqual({
target: undefined,
format: 'cjs',
keepNames: true,
minify: true,
treeShaking: true
})
})

test('resolve es lib with specific minify options', () => {
const options = resolveEsbuildTranspileOptions(
defineResolvedConfig({
build: {
minify: 'esbuild',
lib: {
entry: './somewhere.js'
}
},
esbuild: {
keepNames: true,
minifyIdentifiers: true,
minifyWhitespace: true
}
}),
'es'
)
expect(options).toEqual({
target: undefined,
format: 'esm',
keepNames: true,
minify: false,
minifyIdentifiers: true,
minifyWhitespace: false,
treeShaking: true
})
})

test('resolve cjs lib with specific minify options', () => {
const options = resolveEsbuildTranspileOptions(
defineResolvedConfig({
build: {
minify: 'esbuild',
lib: {
entry: './somewhere.js'
}
},
esbuild: {
keepNames: true,
minifyIdentifiers: true,
minifyWhitespace: true,
treeShaking: true
}
}),
'cjs'
)
expect(options).toEqual({
target: undefined,
format: 'cjs',
keepNames: true,
minify: false,
minifyIdentifiers: true,
minifyWhitespace: true,
treeShaking: true
})
})
})

/**
* Helper for `resolveEsbuildTranspileOptions` to created resolved config with types.
* Note: The function only uses `build.target`, `build.minify` and `esbuild` options.
*/
function defineResolvedConfig(config: UserConfig): ResolvedConfig {
return config as any
}
Expand Up @@ -57,6 +57,7 @@ export const parent = Object.assign({
export const rootMixedRelative = Object.assign({
\\"/css.spec.ts\\": () => import(\\"../../css.spec.ts?url\\").then(m => m[\\"default\\"]),
\\"/define.spec.ts\\": () => import(\\"../../define.spec.ts?url\\").then(m => m[\\"default\\"]),
\\"/esbuild.spec.ts\\": () => import(\\"../../esbuild.spec.ts?url\\").then(m => m[\\"default\\"]),
\\"/import.spec.ts\\": () => import(\\"../../import.spec.ts?url\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url\\").then(m => m[\\"default\\"]),
Expand Down Expand Up @@ -131,6 +132,7 @@ export const parent = Object.assign({
export const rootMixedRelative = Object.assign({
\\"/css.spec.ts\\": () => import(\\"../../css.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/define.spec.ts\\": () => import(\\"../../define.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/esbuild.spec.ts\\": () => import(\\"../../esbuild.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/import.spec.ts\\": () => import(\\"../../import.spec.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/a.ts\\": () => import(\\"../fixture-b/a.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
\\"/importGlob/fixture-b/b.ts\\": () => import(\\"../fixture-b/b.ts?url&lang.ts\\").then(m => m[\\"default\\"]),
Expand Down
115 changes: 96 additions & 19 deletions packages/vite/src/node/plugins/esbuild.ts
Expand Up @@ -8,7 +8,7 @@ import type {
} from 'esbuild'
import { transform } from 'esbuild'
import type { RawSourceMap } from '@ampproject/remapping'
import type { SourceMap } from 'rollup'
import type { InternalModuleFormat, SourceMap } from 'rollup'
import type { TSConfckParseOptions, TSConfckParseResult } from 'tsconfck'
import { TSConfckParseError, findAll, parse } from 'tsconfck'
import {
Expand Down Expand Up @@ -37,6 +37,10 @@ export interface ESBuildOptions extends TransformOptions {
include?: string | RegExp | string[] | RegExp[]
exclude?: string | RegExp | string[] | RegExp[]
jsxInject?: string
/**
* This option is not respected. Use `build.minify` instead.
*/
minify?: never
}

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

// Remove optimization options for dev as we only need to transpile them,
// and for build as the final optimization is in `buildEsbuildPlugin`
const transformOptions: TransformOptions = {
...options,
minify: false,
minifyIdentifiers: false,
minifySyntax: false,
minifyWhitespace: false,
treeShaking: false
}

return {
name: 'vite:esbuild',
configureServer(_server) {
Expand All @@ -188,7 +203,7 @@ export function esbuildPlugin(options: ESBuildOptions = {}): Plugin {
},
async transform(code, id) {
if (filter(id) || filter(cleanUrl(id))) {
const result = await transformWithEsbuild(code, id, options)
const result = await transformWithEsbuild(code, id, transformOptions)
if (result.warnings.length) {
result.warnings.forEach((m) => {
this.warn(prettifyMessage(m, code))
Expand Down Expand Up @@ -236,27 +251,13 @@ export const buildEsbuildPlugin = (config: ResolvedConfig): Plugin => {
return null
}

const target = config.build.target
const minify = config.build.minify === 'esbuild'
const options = resolveEsbuildTranspileOptions(config, opts.format)

if ((!target || target === 'esnext') && !minify) {
if (!options) {
return null
}

const res = await transformWithEsbuild(code, chunk.fileName, {
...config.esbuild,
target: target || undefined,
...(minify
? {
// Do not minify ES lib output since that would remove pure annotations
// and break tree-shaking
// https://github.com/vuejs/core/issues/2860#issuecomment-926882793
minify: !(config.build.lib && opts.format === 'es'),
treeShaking: true,
format: rollupToEsbuildFormatMap[opts.format]
}
: undefined)
})
const res = await transformWithEsbuild(code, chunk.fileName, options)

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

export function resolveEsbuildTranspileOptions(
config: ResolvedConfig,
format: InternalModuleFormat
): TransformOptions | null {
const target = config.build.target
const minify = config.build.minify === 'esbuild'

if ((!target || target === 'esnext') && !minify) {
return null
}

// Do not minify whitespace for ES lib output since that would remove
// pure annotations and break tree-shaking
// https://github.com/vuejs/core/issues/2860#issuecomment-926882793
const isEsLibBuild = config.build.lib && format === 'es'
const options: TransformOptions = {
...config.esbuild,
target: target || undefined,
format: rollupToEsbuildFormatMap[format]
}

// If no minify, disable all minify options
if (!minify) {
return {
...options,
minify: false,
minifyIdentifiers: false,
minifySyntax: false,
minifyWhitespace: false,
treeShaking: false
}
}

// If user enable fine-grain minify options, minify with their options instead
if (
options.minifyIdentifiers ||
options.minifySyntax ||
options.minifyWhitespace
) {
if (isEsLibBuild) {
// Disable minify whitespace as it breaks tree-shaking
return {
...options,
minify: false,
minifyWhitespace: false,
treeShaking: true
}
} else {
return {
...options,
minify: false,
treeShaking: true
}
}
}

// Else apply default minify options
if (isEsLibBuild) {
// Minify all except whitespace as it breaks tree-shaking
return {
...options,
minify: false,
minifyIdentifiers: true,
minifySyntax: true,
minifyWhitespace: false,
treeShaking: true
}
} else {
return {
...options,
minify: true,
treeShaking: true
}
}
}

function prettifyMessage(m: Message, code: string): string {
let res = colors.yellow(m.text)
if (m.location) {
Expand Down

0 comments on commit 8b77695

Please sign in to comment.