From de7f351e89d48af48a9c02c9b659fc944c499bab Mon Sep 17 00:00:00 2001 From: sapphi-red Date: Sat, 7 May 2022 01:25:49 +0900 Subject: [PATCH] chore: make terser an optional dependency BREAKING CHANGE: `terser` must be installed for minification with terser --- docs/config/index.md | 6 +++++ packages/plugin-legacy/README.md | 6 +++++ packages/vite/package.json | 7 ++++-- packages/vite/rollup.config.js | 31 +----------------------- packages/vite/src/node/plugins/css.ts | 8 +++--- packages/vite/src/node/plugins/terser.ts | 30 ++++++++++++++++------- packages/vite/src/node/utils.ts | 13 ++++++++++ playground/legacy/package.json | 3 ++- playground/preload/package.json | 3 ++- pnpm-lock.yaml | 6 +++-- 10 files changed, 63 insertions(+), 50 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 1c61f2bef5b7ac..bd039951081581 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -880,6 +880,12 @@ export default defineConfig({ 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) + terser must be installed when it is set to `'terser'`. + + ```sh + npm add -D terser + ``` + Note the `build.minify` option is not available when using the `'es'` format in lib mode. ### build.terserOptions diff --git a/packages/plugin-legacy/README.md b/packages/plugin-legacy/README.md index fc4167f4f1010f..bbfd3ac154b1ea 100644 --- a/packages/plugin-legacy/README.md +++ b/packages/plugin-legacy/README.md @@ -27,6 +27,12 @@ export default { } ``` +terser must be installed because plugin-legacy uses terser for minification. + +```sh +npm add -D terser +``` + ## Options ### `targets` diff --git a/packages/vite/package.json b/packages/vite/package.json index 7fa8f652b56729..cdecabe1ce0690 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -99,7 +99,6 @@ "source-map-support": "^0.5.21", "strip-ansi": "^6.0.1", "strip-literal": "^0.3.0", - "terser": "^5.13.1", "tsconfck": "^2.0.0", "tslib": "^2.4.0", "types": "link:./types", @@ -109,7 +108,8 @@ "peerDependencies": { "less": "*", "sass": "*", - "stylus": "*" + "stylus": "*", + "terser": "^5.13.1" }, "peerDependenciesMeta": { "sass": { @@ -120,6 +120,9 @@ }, "less": { "optional": true + }, + "terser": { + "optional": true } } } diff --git a/packages/vite/rollup.config.js b/packages/vite/rollup.config.js index 93f4f33bdec398..62e0167477914d 100644 --- a/packages/vite/rollup.config.js +++ b/packages/vite/rollup.config.js @@ -149,10 +149,6 @@ const createNodeConfig = (isProduction) => { // Shim them with eval() so rollup can skip these calls. isProduction && shimDepsPlugin({ - 'plugins/terser.ts': { - src: `require.resolve('terser'`, - replacement: `require.resolve('vite/dist/node/terser'` - }, // chokidar -> fsevents 'fsevents-handler.js': { src: `require('fsevents')`, @@ -192,26 +188,6 @@ const createNodeConfig = (isProduction) => { return nodeConfig } -/** - * Terser needs to be run inside a worker, so it cannot be part of the main - * bundle. We produce a separate bundle for it and shims plugin/terser.ts to - * use the production path during build. - * - * @type { import('rollup').RollupOptions } - */ -const terserConfig = { - ...sharedNodeOptions, - output: { - ...sharedNodeOptions.output, - exports: 'default', - sourcemap: false - }, - input: { - terser: require.resolve('terser') - }, - plugins: [nodeResolve(), commonjs()] -} - /** * @type { (deps: Record) => import('rollup').Plugin } */ @@ -388,10 +364,5 @@ export default (commandLineArgs) => { const isDev = commandLineArgs.watch const isProduction = !isDev - return [ - envConfig, - clientConfig, - createNodeConfig(isProduction), - ...(isProduction ? [terserConfig] : []) - ] + return [envConfig, clientConfig, createNodeConfig(isProduction)] } diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 1f58de158fdea3..207f0466a96c99 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -39,7 +39,8 @@ import { isObject, normalizePath, parseRequest, - processSrcSet + processSrcSet, + requireResolveFromRootWithFallback } from '../utils' import { emptyCssComments } from '../utils' import { addToHTMLProxyTransformResult } from './html' @@ -1237,10 +1238,7 @@ function loadPreprocessor(lang: PreprocessLang, root: string): any { return loadedPreprocessors[lang] } try { - // Search for the preprocessor in the root directory first, and fall back - // to the default require paths. - const fallbackPaths = require.resolve.paths?.(lang) || [] - const resolved = require.resolve(lang, { paths: [root, ...fallbackPaths] }) + const resolved = requireResolveFromRootWithFallback(root, lang) return (loadedPreprocessors[lang] = require(resolved)) } catch (e) { if (e.code === 'MODULE_NOT_FOUND') { diff --git a/packages/vite/src/node/plugins/terser.ts b/packages/vite/src/node/plugins/terser.ts index 9dc546191d14f7..e022862a5dd87a 100644 --- a/packages/vite/src/node/plugins/terser.ts +++ b/packages/vite/src/node/plugins/terser.ts @@ -2,18 +2,29 @@ import { Worker } from 'okie' import type { Terser } from 'types/terser' import type { Plugin } from '../plugin' import type { ResolvedConfig } from '..' +import { requireResolveFromRootWithFallback } from '../utils' + +let terserPath: string | undefined +const loadTerserPath = (root: string) => { + if (terserPath) return terserPath + try { + terserPath = requireResolveFromRootWithFallback(root, 'terser') + } catch (e) { + if (e.code === 'MODULE_NOT_FOUND') { + throw new Error(`terser not found. Did you install it?`) + } else { + const message = new Error(`terser failed to load:\n${e.message}`) + message.stack = e.stack + '\n' + message.stack + throw message + } + } + return terserPath +} export function terserPlugin(config: ResolvedConfig): Plugin { const makeWorker = () => new Worker( - async (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] - }) + (terserPath: string, code: string, options: Terser.MinifyOptions) => { return require(terserPath).minify(code, options) as Terser.MinifyOutput } ) @@ -44,7 +55,8 @@ export function terserPlugin(config: ResolvedConfig): Plugin { // Lazy load worker. worker ||= makeWorker() - const res = await worker.run(__dirname, code, { + const terserPath = loadTerserPath(config.root) + const res = await worker.run(terserPath, code, { safari10: true, ...config.build.terserOptions, sourceMap: !!outputOptions.sourcemap, diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 3748aa3b465b2c..6c0887d959dd9a 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -774,6 +774,19 @@ export function getHash(text: Buffer | string): string { return createHash('sha256').update(text).digest('hex').substring(0, 8) } +export const requireResolveFromRootWithFallback = ( + root: string, + id: string +) => { + // Search in the root directory first, and fallback to the default require paths. + const fallbackPaths = require.resolve.paths?.(id) || [] + // eslint-disable-next-line node/no-missing-require + const path = require.resolve(id, { + paths: [root, ...fallbackPaths] + }) + return path +} + // Based on node-graceful-fs // The ISC License diff --git a/playground/legacy/package.json b/playground/legacy/package.json index 4f11c234573f40..347e410e2bf4d6 100644 --- a/playground/legacy/package.json +++ b/playground/legacy/package.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@vitejs/plugin-legacy": "workspace:*", - "express": "^4.18.1" + "express": "^4.18.1", + "terser": "^5.13.1" } } diff --git a/playground/preload/package.json b/playground/preload/package.json index f38267b0b8f8d7..617fdc04336b3f 100644 --- a/playground/preload/package.json +++ b/playground/preload/package.json @@ -13,6 +13,7 @@ "vue-router": "^4.0.15" }, "devDependencies": { - "@vitejs/plugin-vue": "workspace:*" + "@vitejs/plugin-vue": "workspace:*", + "terser": "^5.13.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00d6847a132bf4..81fa4ea8b34bbd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -261,7 +261,6 @@ importers: source-map-support: ^0.5.21 strip-ansi: ^6.0.1 strip-literal: ^0.3.0 - terser: ^5.13.1 tsconfck: ^2.0.0 tslib: ^2.4.0 types: link:./types @@ -323,7 +322,6 @@ importers: source-map-support: 0.5.21 strip-ansi: 6.0.1 strip-literal: 0.3.0 - terser: 5.13.1 tsconfck: 2.0.0 tslib: 2.4.0 types: link:types @@ -491,9 +489,11 @@ importers: specifiers: '@vitejs/plugin-legacy': workspace:* express: ^4.18.1 + terser: ^5.13.1 devDependencies: '@vitejs/plugin-legacy': link:../../packages/plugin-legacy express: 4.18.1 + terser: 5.13.1 playground/lib: specifiers: {} @@ -660,6 +660,7 @@ importers: playground/preload: specifiers: '@vitejs/plugin-vue': workspace:* + terser: ^5.13.1 vue: ^3.2.33 vue-router: ^4.0.15 dependencies: @@ -667,6 +668,7 @@ importers: vue-router: 4.0.15_vue@3.2.33 devDependencies: '@vitejs/plugin-vue': link:../../packages/plugin-vue + terser: 5.13.1 playground/preserve-symlinks: specifiers: