From 3e0e4fdb1f5dd1d1564eef03c6e62101382c59f9 Mon Sep 17 00:00:00 2001 From: Aron Griffis Date: Mon, 27 Dec 2021 10:36:33 -0500 Subject: [PATCH 1/2] fix(plugin-legacy): don't force terser on non-legacy (fix #6266) This fixes two misbehaviors of the legacy plugin: 1. Respect {minify: false} for legacy assets. 2. Don't inflict es2019/terser on non-legacy chunks. For the first problem, we could have fixed by checking for false in viteLegacyPlugin.config(). Unfortunately that would have left the second problem unsolved. Without adding significant complexity to the config, there's no easy way to use different minifiers in the build depending on the individual chunk. So instead we include terserPlugin() whenever minify is enabled, even true or 'esbuild', then check the actual configuration in the plugin. This allows the legacy plugin to inject its special override, leaving all the non-legacy stuff intact and uncomplicated. See also, previous attempts: #5157 #5168 --- .../legacy/__tests__/legacy.spec.ts | 22 ++++++++++++++++++- packages/plugin-legacy/index.js | 22 +++++-------------- packages/plugin-legacy/package.json | 2 +- packages/vite/src/node/build.ts | 2 +- packages/vite/src/node/plugins/terser.ts | 11 ++++++++++ 5 files changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/playground/legacy/__tests__/legacy.spec.ts b/packages/playground/legacy/__tests__/legacy.spec.ts index f1e317246ee852..6a29965b10bd40 100644 --- a/packages/playground/legacy/__tests__/legacy.spec.ts +++ b/packages/playground/legacy/__tests__/legacy.spec.ts @@ -1,4 +1,9 @@ -import { isBuild, readManifest, untilUpdated } from '../../testUtils' +import { + findAssetFile, + isBuild, + readManifest, + untilUpdated +} from '../../testUtils' test('should work', async () => { expect(await page.textContent('#app')).toMatch('Hello') @@ -53,4 +58,19 @@ if (isBuild) { '../../../vite/legacy-polyfills' ) }) + + test('should minify legacy chunks with terser', async () => { + // This is a ghetto heuristic, but terser output seems to reliably start + // with one of the following, and non-terser output (including unminified or + // ebuild-minified) does not! + const terserPatt = /^(?:!function|System.register)/ + + expect(findAssetFile(/chunk-async-legacy/)).toMatch(terserPatt) + expect(findAssetFile(/chunk-async\./)).not.toMatch(terserPatt) + expect(findAssetFile(/immutable-chunk-legacy/)).toMatch(terserPatt) + expect(findAssetFile(/immutable-chunk\./)).not.toMatch(terserPatt) + expect(findAssetFile(/index-legacy/)).toMatch(terserPatt) + expect(findAssetFile(/index\./)).not.toMatch(terserPatt) + expect(findAssetFile(/polyfills-legacy/)).toMatch(terserPatt) + }) } diff --git a/packages/plugin-legacy/index.js b/packages/plugin-legacy/index.js index 2267cd9cf82b42..d030d7fc43344e 100644 --- a/packages/plugin-legacy/index.js +++ b/packages/plugin-legacy/index.js @@ -105,23 +105,6 @@ function viteLegacyPlugin(options = {}) { name: 'vite:legacy-generate-polyfill-chunk', apply: 'build', - config() { - return { - build: { - minify: 'terser' - } - } - }, - - configResolved(config) { - if (!config.build.ssr && genLegacy && config.build.minify === 'esbuild') { - throw new Error( - `Can't use esbuild as the minifier when targeting legacy browsers ` + - `because esbuild minification is not legacy safe.` - ) - } - }, - async generateBundle(opts, bundle) { if (config.build.ssr) { return @@ -297,6 +280,11 @@ function viteLegacyPlugin(options = {}) { // legacy-unsafe code - e.g. rewriting object properties into shorthands opts.__vite_skip_esbuild__ = true + // @ts-ignore force terser for legacy chunks. This only takes effect if + // minification isn't disabled, because that leaves out the terser plugin + // entirely. + opts.__vite_force_terser__ = true + const needPolyfills = options.polyfills !== false && !Array.isArray(options.polyfills) diff --git a/packages/plugin-legacy/package.json b/packages/plugin-legacy/package.json index e0556f1ddaf59c..9caeeb00ceb7f9 100644 --- a/packages/plugin-legacy/package.json +++ b/packages/plugin-legacy/package.json @@ -33,6 +33,6 @@ "systemjs": "^6.11.0" }, "peerDependencies": { - "vite": "^2.0.0" + "vite": "^2.7.8" } } diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index e64cc618135bb0..b22af65e07e895 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -365,7 +365,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): { post: [ buildImportAnalysisPlugin(config), buildEsbuildPlugin(config), - ...(options.minify === 'terser' ? [terserPlugin(config)] : []), + ...(options.minify ? [terserPlugin(config)] : []), ...(options.manifest ? [manifestPlugin(config)] : []), ...(options.ssrManifest ? [ssrManifestPlugin(config)] : []), buildReporterPlugin(config), diff --git a/packages/vite/src/node/plugins/terser.ts b/packages/vite/src/node/plugins/terser.ts index eb8864a2e00001..e51f63a3e7544a 100644 --- a/packages/vite/src/node/plugins/terser.ts +++ b/packages/vite/src/node/plugins/terser.ts @@ -21,6 +21,17 @@ export function terserPlugin(config: ResolvedConfig): Plugin { name: 'vite:terser', async renderChunk(code, _chunk, outputOptions) { + // This plugin is included for any non-false value of config.build.minify, + // so that normal chunks can use the preferred minifier, and legacy chunks + // can use terser. + if ( + config.build.minify !== 'terser' && + // @ts-ignore injected by @vitejs/plugin-legacy + !outputOptions.__vite_force_terser__ + ) { + return null + } + // Do not minify ES lib output since that would remove pure annotations // and break tree-shaking if (config.build.lib && outputOptions.format === 'es') { From 25f7a1fce1265e6c31ff9f0803bb05a4976dbbd3 Mon Sep 17 00:00:00 2001 From: Aron Griffis Date: Mon, 27 Dec 2021 20:30:40 -0500 Subject: [PATCH 2/2] feat: lazy load terser worker --- packages/vite/src/node/plugins/terser.ts | 34 ++++++++++++++---------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/vite/src/node/plugins/terser.ts b/packages/vite/src/node/plugins/terser.ts index e51f63a3e7544a..29f4d5c172ce67 100644 --- a/packages/vite/src/node/plugins/terser.ts +++ b/packages/vite/src/node/plugins/terser.ts @@ -4,18 +4,21 @@ import type { Terser } from 'types/terser' import type { ResolvedConfig } from '..' export function terserPlugin(config: ResolvedConfig): Plugin { - const worker = new Worker( - (basedir: string, code: string, options: Terser.MinifyOptions) => { - // when vite is linked, the worker thread won't share the same resolve - // root with vite itself, so we have to pass in the basedir and resolve - // terser first. - // eslint-disable-next-line node/no-restricted-require - const terserPath = require.resolve('terser', { - paths: [basedir] - }) - return require(terserPath).minify(code, options) as Terser.MinifyOutput - } - ) + const makeWorker = () => + new Worker( + (basedir: string, code: string, options: Terser.MinifyOptions) => { + // when vite is linked, the worker thread won't share the same resolve + // root with vite itself, so we have to pass in the basedir and resolve + // terser first. + // eslint-disable-next-line node/no-restricted-require + const terserPath = require.resolve('terser', { + paths: [basedir] + }) + return require(terserPath).minify(code, options) as Terser.MinifyOutput + } + ) + + let worker: ReturnType return { name: 'vite:terser', @@ -33,11 +36,14 @@ export function terserPlugin(config: ResolvedConfig): Plugin { } // Do not minify ES lib output since that would remove pure annotations - // and break tree-shaking + // and break tree-shaking. if (config.build.lib && outputOptions.format === 'es') { return null } + // Lazy load worker. + worker ||= makeWorker() + const res = await worker.run(__dirname, code, { safari10: true, ...config.build.terserOptions, @@ -52,7 +58,7 @@ export function terserPlugin(config: ResolvedConfig): Plugin { }, closeBundle() { - worker.stop() + worker?.stop() } } }