vitejs / vite

Next generation frontend tooling. It's fast!
http://vitejs.dev
MIT License
67.08k stars 6.02k forks source link

Error using build.terserOptions mangle nth_identifier #17409

Open farseekers opened 3 months ago

farseekers commented 3 months ago

Describe the bug

I'm attempting to prefix mangled variable names.

https://vitejs.dev/config/build-options#build-terseroptions told me that under build.terserOptions, I could add

Additional minify options to pass on to Terser.

https://terser.org/docs/api-reference/#minify-options

mangle.properties (default false) — a subcategory of the mangle option. Pass an object to specify custom mangle property options.

https://terser.org/docs/options/#mangle-properties-options

nth_identifer (default: an internal mangler that weights based on character frequency analysis) -- Pass an object with a get(n) function that converts an ordinal into the nth most favored (usually shortest) identifier. Optionally also provide reset(), sort(), and consider(chars, delta) to use character frequency analysis of the source code.

So following that, I constructed the following (a minimal reproduction of it, anyway).

export default {
  build: {
    minify: 'terser',
    terserOptions: {
      mangle: {
        properties: {
          nth_identifier: {
            get: n => {
              return 'prefix_'+n.toString()
            }
          }
        }
      }
    }
  }
}

However, it fails to work, raising an error.

> vite v5.2.12 building for production...
> ✓ 1 modules transformed.
> x Build failed in 24ms
> error during build:
> Cannot set property code of  which has only a getter
>     at logPluginError (file:///reproduction/node_modules/rollup/dist/es/shared/parseAst.js:894:16)
>     at file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:19800:26
>     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
>     at async transformChunk (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:17934:16)
>     at async file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:18008:17
>     at async Promise.all (index 0)
>     at async transformChunksAndGenerateContentHashes (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:18003:5)
>     at async renderChunks (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:17912:137)
>     at async Bundle.generate (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:18145:13)
>     at async file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:20692:27

After removing two catches in the node modules to find the origin of the problem, I got

error during build:
DataCloneError: (n) => {
              return "prefix_" + n.toString();
            } could not be cloned.
    at new DOMException (node:internal/per_context/domexception:53:5)
    at Worker.postMessage (node:internal/worker:366:5)
    at file:///reproduction/node_modules/vite/dist/node/chunks/dep-BKbDVx1T.js:15135:14
    at new Promise (<anonymous>)
    at Worker.run (file:///reproduction/node_modules/vite/dist/node/chunks/dep-BKbDVx1T.js:15132:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.renderChunk (file:///reproduction/node_modules/vite/dist/node/chunks/dep-BKbDVx1T.js:15382:25)

Please let me know if I've missed something, as it would seem that the inability to clone a function is preventing this feature from working.

Reproduction

https://github.com/farseekers/terserOptions

Steps to reproduce

  1. npm install
  2. Terser works by itself - node index.js
  3. vite does not - ./node_modules/.bin/vite build, raising an error

System Info

System:
    OS: macOS 14.5
    CPU: (8) arm64 Apple M1
    Memory: 137.39 MB / 8.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.20.3 - /opt/local/bin/node
    Yarn: 1.22.22 - /opt/local/bin/yarn
    npm: 8.19.4 - /opt/local/bin/npm
    pnpm: 9.0.5 - /opt/local/bin/pnpm
  Browsers:
    Brave Browser: 125.1.66.118
    Chrome: 125.0.6422.142
    Safari: 17.5

Used Package Manager

npm

Logs

vite v5.2.12 building for production... ✓ 1 modules transformed. x Build failed in 24ms error during build: Cannot set property code of which has only a getter at logPluginError (file:///reproduction/node_modules/rollup/dist/es/shared/parseAst.js:894:16) at file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:19800:26 at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async transformChunk (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:17934:16) at async file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:18008:17 at async Promise.all (index 0) at async transformChunksAndGenerateContentHashes (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:18003:5) at async renderChunks (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:17912:137) at async Bundle.generate (file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:18145:13) at async file:///reproduction/node_modules/rollup/dist/es/shared/node-entry.js:20692:27

Validations

bluwy commented 2 months ago

Seems like it's because we run terser with a worker, and the options can't be properly serialized. We should definitely fix and provide a better error message, but for the serialization it's not always possible to assume that a function is isolated for serializing. I guess the solution would be either:

  1. Like CSS preprocessing, maybe we could have a fallback mode that runs on the current thread if there's no serializable values, but that may make things slower.
  2. Or force serialize functions and require the definition to always be isolated.
  3. Or some sort of custom serializer option.
farseekers commented 2 months ago

Thanks very much for the confirmation that I wasn't missing something.

wmertens commented 2 months ago

@bluwy this option is needed to have consistent names between builds so that chunks stay the same as much as possible, thereby reducing traffic.

Can't you assume that if a function is passed, it is self-contained?

Failing that, how about a special option type where you can provide source code to eval?