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 :/
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).