diff --git a/packages/vite/package.json b/packages/vite/package.json index e9fc4939f18776..bc72d79b141be1 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -148,6 +148,7 @@ "source-map-support": "^0.5.21", "strip-ansi": "^7.1.0", "strip-literal": "^2.1.0", + "terser": "^5.31.6", "tsconfck": "^3.1.3", "tslib": "^2.7.0", "types": "link:./types", diff --git a/packages/vite/src/node/__tests__/plugins/terser.spec.ts b/packages/vite/src/node/__tests__/plugins/terser.spec.ts new file mode 100644 index 00000000000000..6a70c3ce8e50a0 --- /dev/null +++ b/packages/vite/src/node/__tests__/plugins/terser.spec.ts @@ -0,0 +1,45 @@ +import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { describe, expect, test } from 'vitest' +import { build } from 'vite' +import type { RollupOutput } from 'rollup' + +const __dirname = resolve(fileURLToPath(import.meta.url), '..') + +describe('terser', () => { + test('nth', async () => { + const result = (await build({ + root: resolve(__dirname, '../packages/build-project'), + logLevel: 'silent', + build: { + write: false, + minify: 'terser', + terserOptions: { + mangle: { + nth_identifier: { + get: (n) => { + return 'prefix_' + n.toString() + }, + }, + }, + }, + }, + plugins: [ + { + name: 'test', + resolveId(id) { + if (id === 'entry.js') { + return '\0' + id + } + }, + load(id) { + if (id === '\0entry.js') { + return `const foo = 1;console.log(foo)` + } + }, + }, + ], + })) as RollupOutput + expect(result.output[0].code).toContain('prefix_') + }) +}) diff --git a/packages/vite/src/node/plugins/terser.ts b/packages/vite/src/node/plugins/terser.ts index 9c254dee1043af..b53a5239f484c3 100644 --- a/packages/vite/src/node/plugins/terser.ts +++ b/packages/vite/src/node/plugins/terser.ts @@ -33,6 +33,12 @@ const loadTerserPath = (root: string) => { return terserPath } +const toString = (obj: Record, name: string) => { + if (name in obj && typeof obj[name] === 'function') { + obj[name] = obj[name].toString() + } +} + export function terserPlugin(config: ResolvedConfig): Plugin { const { maxWorkers, ...terserOptions } = config.build.terserOptions @@ -47,6 +53,27 @@ export function terserPlugin(config: ResolvedConfig): Plugin { // test fails when using `import`. maybe related: https://github.com/nodejs/node/issues/43205 // eslint-disable-next-line no-restricted-globals -- this function runs inside cjs const terser = require(terserPath) + const nth: + | Terser.SimpleIdentifierMangler + | Terser.WeightedIdentifierMangler + | undefined = (options.mangle as any)?.nth_identifier + if (nth && typeof nth === 'object') { + const toFunction = (obj: Record, name: string) => { + if (name in obj && typeof obj[name] === 'string') { + const fn = eval(obj[name]) + if (typeof fn !== 'function') { + throw new Error( + `Failed to eval nth_identifier.${name}: not a function`, + ) + } + obj[name] = fn + } + } + toFunction(nth, 'get') + toFunction(nth, 'consider') + toFunction(nth, 'sort') + } + return terser.minify(code, options) as Terser.MinifyOutput }, { @@ -87,12 +114,25 @@ export function terserPlugin(config: ResolvedConfig): Plugin { worker ||= makeWorker() const terserPath = loadTerserPath(config.root) + const nth = + typeof terserOptions.mangle === 'object' + ? { ...terserOptions.mangle.nth_identifier } + : undefined + if (nth) { + toString(nth, 'get') + toString(nth, 'consider') + toString(nth, 'sort') + } const res = await worker.run(terserPath, code, { safari10: true, ...terserOptions, sourceMap: !!outputOptions.sourcemap, module: outputOptions.format.startsWith('es'), toplevel: outputOptions.format === 'cjs', + mangle: + typeof terserOptions.mangle === 'object' + ? { ...terserOptions.mangle, nth_identifier: nth as any } + : terserOptions.mangle, }) return { code: res.code!, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9229565822bbe..4c72d288787d88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -403,6 +403,9 @@ importers: strip-literal: specifier: ^2.1.0 version: 2.1.0 + terser: + specifier: ^5.31.6 + version: 5.31.6 tsconfck: specifier: ^3.1.3 version: 3.1.3(typescript@5.5.3)