Skip to content

Commit

Permalink
chore: make terser an optional dependency
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `terser` must be installed for minification with terser
  • Loading branch information
sapphi-red committed May 9, 2022
1 parent 875fc11 commit 55c4d94
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 50 deletions.
6 changes: 6 additions & 0 deletions docs/config/index.md
Expand Up @@ -861,6 +861,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
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-legacy/README.md
Expand Up @@ -27,6 +27,12 @@ export default {
}
```

terser must be installed because plugin-legacy uses terser for minification.

```sh
npm add -D terser
```

## Options

### `targets`
Expand Down
7 changes: 5 additions & 2 deletions packages/vite/package.json
Expand Up @@ -113,7 +113,6 @@
"source-map-support": "^0.5.21",
"strip-ansi": "^6.0.1",
"strip-literal": "^0.3.0",
"terser": "^5.13.1",
"tsconfck": "^1.2.2",
"tslib": "^2.4.0",
"types": "link:./types",
Expand All @@ -123,7 +122,8 @@
"peerDependencies": {
"less": "*",
"sass": "*",
"stylus": "*"
"stylus": "*",
"terser": "^5.13.1"
},
"peerDependenciesMeta": {
"sass": {
Expand All @@ -134,6 +134,9 @@
},
"less": {
"optional": true
},
"terser": {
"optional": true
}
}
}
31 changes: 1 addition & 30 deletions packages/vite/rollup.config.js
Expand Up @@ -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')`,
Expand Down Expand Up @@ -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<string, { src?: string, replacement: string, pattern?: RegExp }>) => import('rollup').Plugin }
*/
Expand Down Expand Up @@ -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)]
}
8 changes: 3 additions & 5 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -12,7 +12,8 @@ import {
normalizePath,
processSrcSet,
parseRequest,
combineSourcemaps
combineSourcemaps,
requireResolveFromRootWithFallback
} from '../utils'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
Expand Down Expand Up @@ -1227,10 +1228,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') {
Expand Down
30 changes: 21 additions & 9 deletions packages/vite/src/node/plugins/terser.ts
Expand Up @@ -2,18 +2,29 @@ import type { Plugin } from '../plugin'
import { Worker } from 'okie'
import type { Terser } from 'types/terser'
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(
(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
}
)
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions packages/vite/src/node/utils.ts
Expand Up @@ -748,6 +748,19 @@ export function parseRequest(id: string): Record<string, string> | null {

export const blankReplacer = (match: string) => ' '.repeat(match.length)

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
Expand Down
3 changes: 2 additions & 1 deletion playground/legacy/package.json
Expand Up @@ -11,6 +11,7 @@
},
"devDependencies": {
"@vitejs/plugin-legacy": "workspace:*",
"express": "^4.17.1"
"express": "^4.17.1",
"terser": "^5.13.1"
}
}
3 changes: 2 additions & 1 deletion playground/preload/package.json
Expand Up @@ -13,6 +13,7 @@
"vue-router": "^4.0.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "workspace:*"
"@vitejs/plugin-vue": "workspace:*",
"terser": "^5.13.1"
}
}
6 changes: 4 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 55c4d94

Please sign in to comment.