Search code examples
reactjsviterollupdead-code

React is not being used, yet is still pulled in into a bundle


tldr; what is preventing React from being dead-code eliminated in my project? It is not being used, apart in an unused utility in a library I am consuming.


I recently created a little library for retrying failing dynamic imports. I made a non-react-dependant module and another one that just handled the react specifics. This was to ensure a consumer would not pull in React if not using the react utils. I then created an index module that pulled the two in and exported named constants

import _createDynamicImportWithRetry from "./dynamic-import-with-retry";
import _reactLazy from "./react-lazy";

export const reactLazyWithRetry = _reactLazy;
export const createDynamicImportWithRetry = _createDynamicImportWithRetry;

This is the main field in package.json.

I assumed from previous reading that when Vite consumes this library it should be able to gather that just importing the named export createDynamicImportWithRetry would only require pulling in "./dynamic-import-with-retry", not the other import. Unfortunately, that does not seem to be the case: I published a project that shows it not to be the case: the transitive React dependency is being bundled, no matter if it is unused.

I would have assumed that dead-code analysis during bundling for production would have pruned the unused imports of the tree. How can I ensure pruning of dead branches take place?

Reproduction:

mkdir tmp-proj && cd tmp-proj && mkdir src

echo '<script type="module" src="./src/index.js"></script>' > index.html

echo "import * as o from '@fatso83/retry-dynamic-import'
o.reactLazyWithRetry( () => Promise.resolve())" > src/index.js

npm i @fatso83/retry-dynamic-import; npm i vite

npx vite build 

vite v4.3.5 building for production...
✓ 13 modules transformed.
dist/index.html                0.08 kB │ gzip: 0.09 kB
dist/assets/index-4e07d3ed.js  9.27 kB │ gzip: 3.89 kB

The 9KB there is the problem. It should be closer to 500 bytes, but it pulls in all of the React production build :/


Solution

  • I posted this on the Vite discussion board and it turns out that Rollup cannot track assignments yet (per May 2023).

    Rollup cannot track assignments (yet), so we deoptimize all properties of the right-hand side on assignments. Also, Rollup deoptimization does not know the execution flow, so it assumes that lazy might be a setter in require$$0.lazy=()=>{}, and that is the side effect. Probably not too easy to fix.

    Since Vite relies on Rollup for bundling, there is nothing to do, apart from moving the export out of the root module and exporting it as its own specific export in package.json (which is what I did for version 2).