Skip to content

Commit 164f528

Browse files
authored
refactor!: make terser an optional dependency (#8049)
1 parent 1f1ca5e commit 164f528

File tree

12 files changed

+76
-58
lines changed

12 files changed

+76
-58
lines changed

docs/config/build-options.md

+6
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ Set to `false` to disable minification, or specify the minifier to use. The defa
147147

148148
Note the `build.minify` option is not available when using the `'es'` format in lib mode.
149149

150+
Terser must be installed when it is set to `'terser'`.
151+
152+
```sh
153+
npm add -D terser
154+
```
155+
150156
## build.terserOptions
151157

152158
- **Type:** `TerserOptions`

packages/plugin-legacy/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export default {
2727
}
2828
```
2929

30+
Terser must be installed because plugin-legacy uses Terser for minification.
31+
32+
```sh
33+
npm add -D terser
34+
```
35+
3036
## Options
3137

3238
### `targets`

packages/plugin-legacy/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"systemjs": "^6.12.1"
4343
},
4444
"peerDependencies": {
45+
"terser": "^5.4.0",
4546
"vite": "^3.0.0-alpha"
4647
},
4748
"devDependencies": {

packages/vite/package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@
2020
"./client": {
2121
"types": "./client.d.ts"
2222
},
23-
"./dist/client/*": "./dist/client/*",
24-
"./terser": {
25-
"require": "./dist/node-cjs/terser.cjs"
26-
}
23+
"./dist/client/*": "./dist/client/*"
2724
},
2825
"files": [
2926
"bin",
@@ -118,7 +115,6 @@
118115
"source-map-support": "^0.5.21",
119116
"strip-ansi": "^6.0.1",
120117
"strip-literal": "^0.3.0",
121-
"terser": "^5.14.0",
122118
"tsconfck": "^2.0.1",
123119
"tslib": "^2.4.0",
124120
"types": "link:./types",
@@ -128,7 +124,8 @@
128124
"peerDependencies": {
129125
"less": "*",
130126
"sass": "*",
131-
"stylus": "*"
127+
"stylus": "*",
128+
"terser": "^5.4.0"
132129
},
133130
"peerDependenciesMeta": {
134131
"sass": {
@@ -139,6 +136,9 @@
139136
},
140137
"less": {
141138
"optional": true
139+
},
140+
"terser": {
141+
"optional": true
142142
}
143143
}
144144
}

packages/vite/rollup.config.ts

+1-27
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,6 @@ function createNodePlugins(
118118
// Shim them with eval() so rollup can skip these calls.
119119
isProduction &&
120120
shimDepsPlugin({
121-
'plugins/terser.ts': {
122-
src: `require.resolve('terser'`,
123-
replacement: `require.resolve('vite/terser'`
124-
},
125121
// chokidar -> fsevents
126122
'fsevents-handler.js': {
127123
src: `require('fsevents')`,
@@ -191,27 +187,6 @@ function createNodeConfig(isProduction: boolean) {
191187
})
192188
}
193189

194-
/**
195-
* Terser needs to be run inside a worker, so it cannot be part of the main
196-
* bundle. We produce a separate bundle for it and shims plugin/terser.ts to
197-
* use the production path during build.
198-
*/
199-
const terserConfig = defineConfig({
200-
...sharedNodeOptions,
201-
output: {
202-
...sharedNodeOptions.output,
203-
entryFileNames: `node-cjs/[name].cjs`,
204-
exports: 'default',
205-
format: 'cjs',
206-
sourcemap: false
207-
},
208-
input: {
209-
// eslint-disable-next-line node/no-restricted-require
210-
terser: require.resolve('terser')
211-
},
212-
plugins: [nodeResolve(), commonjs()]
213-
})
214-
215190
function createCjsConfig(isProduction: boolean) {
216191
return defineConfig({
217192
...sharedNodeOptions,
@@ -245,8 +220,7 @@ export default (commandLineArgs: any) => {
245220
envConfig,
246221
clientConfig,
247222
createNodeConfig(isProduction),
248-
createCjsConfig(isProduction),
249-
...(isProduction ? [terserConfig] : [])
223+
createCjsConfig(isProduction)
250224
])
251225
}
252226

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

+3-5
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ import {
4040
isRelativeBase,
4141
normalizePath,
4242
parseRequest,
43-
processSrcSet
43+
processSrcSet,
44+
requireResolveFromRootWithFallback
4445
} from '../utils'
4546
import type { Logger } from '../logger'
4647
import { addToHTMLProxyTransformResult } from './html'
@@ -1296,10 +1297,7 @@ function loadPreprocessor(lang: PreprocessLang, root: string): any {
12961297
return loadedPreprocessors[lang]
12971298
}
12981299
try {
1299-
// Search for the preprocessor in the root directory first, and fall back
1300-
// to the default require paths.
1301-
const fallbackPaths = _require.resolve.paths?.(lang) || []
1302-
const resolved = _require.resolve(lang, { paths: [root, ...fallbackPaths] })
1300+
const resolved = requireResolveFromRootWithFallback(root, lang)
13031301
return (loadedPreprocessors[lang] = _require(resolved))
13041302
} catch (e) {
13051303
if (e.code === 'MODULE_NOT_FOUND') {

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

+30-15
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
1-
import { dirname } from 'path'
2-
import { fileURLToPath } from 'url'
31
import { Worker } from 'okie'
42
import type { Terser } from 'types/terser'
53
import type { Plugin } from '../plugin'
64
import type { ResolvedConfig } from '..'
5+
import { requireResolveFromRootWithFallback } from '../utils'
76

8-
// TODO: use import()
9-
const _dirname = dirname(fileURLToPath(import.meta.url))
7+
let terserPath: string | undefined
8+
const loadTerserPath = (root: string) => {
9+
if (terserPath) return terserPath
10+
try {
11+
terserPath = requireResolveFromRootWithFallback(root, 'terser')
12+
} catch (e) {
13+
if (e.code === 'MODULE_NOT_FOUND') {
14+
throw new Error(
15+
'terser not found. Since Vite v3, terser has become an optional dependency. You need to install it.'
16+
)
17+
} else {
18+
const message = new Error(`terser failed to load:\n${e.message}`)
19+
message.stack = e.stack + '\n' + message.stack
20+
throw message
21+
}
22+
}
23+
return terserPath
24+
}
1025

1126
export function terserPlugin(config: ResolvedConfig): Plugin {
1227
const makeWorker = () =>
1328
new Worker(
14-
async (basedir: string, code: string, options: Terser.MinifyOptions) => {
15-
// when vite is linked, the worker thread won't share the same resolve
16-
// root with vite itself, so we have to pass in the basedir and resolve
17-
// terser first.
18-
// eslint-disable-next-line node/no-restricted-require, no-restricted-globals
19-
const terserPath = require.resolve('terser', {
20-
paths: [basedir]
21-
})
22-
// eslint-disable-next-line no-restricted-globals
23-
return require(terserPath).minify(code, options) as Terser.MinifyOutput
29+
async (
30+
terserPath: string,
31+
code: string,
32+
options: Terser.MinifyOptions
33+
) => {
34+
// test fails when using `import`. maybe related: https://github.com/nodejs/node/issues/43205
35+
// eslint-disable-next-line no-restricted-globals -- this function runs inside cjs
36+
const terser = require(terserPath)
37+
return terser.minify(code, options) as Terser.MinifyOutput
2438
}
2539
)
2640

@@ -50,7 +64,8 @@ export function terserPlugin(config: ResolvedConfig): Plugin {
5064
// Lazy load worker.
5165
worker ||= makeWorker()
5266

53-
const res = await worker.run(_dirname, code, {
67+
const terserPath = loadTerserPath(config.root)
68+
const res = await worker.run(terserPath, code, {
5469
safari10: true,
5570
...config.build.terserOptions,
5671
sourceMap: !!outputOptions.sourcemap,

packages/vite/src/node/utils.ts

+12
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,18 @@ export function getHash(text: Buffer | string): string {
791791
return createHash('sha256').update(text).digest('hex').substring(0, 8)
792792
}
793793

794+
export const requireResolveFromRootWithFallback = (
795+
root: string,
796+
id: string
797+
): string => {
798+
// Search in the root directory first, and fallback to the default require paths.
799+
const fallbackPaths = _require.resolve.paths?.(id) || []
800+
const path = _require.resolve(id, {
801+
paths: [root, ...fallbackPaths]
802+
})
803+
return path
804+
}
805+
794806
// Based on node-graceful-fs
795807

796808
// The ISC License

playground/legacy/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
},
1313
"devDependencies": {
1414
"@vitejs/plugin-legacy": "workspace:*",
15-
"express": "^4.18.1"
15+
"express": "^4.18.1",
16+
"terser": "^5.13.1"
1617
}
1718
}

playground/preload/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"vue-router": "^4.0.15"
1414
},
1515
"devDependencies": {
16-
"@vitejs/plugin-vue": "workspace:*"
16+
"@vitejs/plugin-vue": "workspace:*",
17+
"terser": "^5.13.1"
1718
}
1819
}

playground/vitestSetup.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,9 @@ export async function startDefaultServe() {
182182
build: {
183183
// esbuild do not minify ES lib output since that would remove pure annotations and break tree-shaking
184184
// skip transpilation during tests to make it faster
185-
target: 'esnext'
185+
target: 'esnext',
186+
// tests are flaky when `emptyOutDir` is `true`
187+
emptyOutDir: false
186188
},
187189
customLogger: createInMemoryLogger(serverLogs)
188190
}

pnpm-lock.yaml

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)