You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I know in the current webpack build process, dead code removal happens last, which relies on minifier like terser to drop the dead code on per chunk level thanks to @alexander-akait kind explanation.
However, this can lead to significantly bigger chunk sizes due to lack of information.
Take the below code for example, let's say we have two different builds, one for PC users and one for mobiles. Each of the files refers to different esm modules of common node modules packages like lodash-es and MUI.
This common chunk is spilt into a separate chunk by SplitChunkPlugin for better cache opportunity.
exportconsttreeShakingTestAsync=async()=>{console.log('treeShakingTestAsync');// Be replaced with 'false' by DefinePluginif(__MOBILE__){const{ Baz }=awaitimport(/* webpackChunkName: "bundle_mobile" */'./mobile');console.log(Baz);}else{const{ foo }=awaitimport(/* webpackChunkName: "bundle_pc" */'./pc');console.log(foo);}};
pc.ts and mobile.ts both use some of the code from a common module.
// pc.tsexport{bar,foo}from'./lib-modules';
// mobile.tsexport{Baz}from'./lib-modules';
lib-modules.ts mimics a large esm package index file.
For PC build, we won't want Baz to appear in the final bundle.
But If we perform dead code removal purely on chunks level, after the module graph is built, Baz is already marked as 'exported' and used. Terser or any other minifier would not know that it is never used, because this information happens across files, only webpack level's javascript compiler knows such kind of thing.
And in the above situation, webpack does pre-optimize dead code removal.
It prevents further dependency build processes in dead branches.
However, this case is just a simple example of what can pre dead code elimination can achieve.
There are still a lot of scenarios not covered.
Per File level optimization, like if-return.
exportconsttreeShakingTestAsync=async()=>{console.log('treeShakingTestAsync');const{ foo }=awaitimport(/* webpackChunkName: "bundle_pc" */'./pc');console.log(foo);// Code wont't be reached after if_return, but still get to be bundled, and Baz get to be bundled as well, due to splitchunk.if(false){return;}const{ Baz }=awaitimport(/* webpackChunkName: "bundle_mobile" */'./mobile');console.log(Baz);};
unused exports
exportconsttreeShakingTestAsync=async()=>{console.log('treeShakingTestAsync');const{ foo }=awaitimport(/* webpackChunkName: "bundle_pc" */'./pc');console.log(foo);};exportconstunusedExport=async()=>{console.log('unusedExport');// this never be refered from entry.const{ Baz }=awaitimport(/* webpackChunkName: "bundle_mobile" */'./mobile');console.log(Baz);};
What is the expected behavior?
dead code won't appear in module graph.
What is motivation or use case for adding/changing the behavior?
Much Smaller bundle size for giant project.
I work on a large private company SPA codebase with its bundle size over 70MB before gizpped, with many reasonable or unreasonable splitchunk rules. I found that most unused code can be avoided by simply just not letting the PC user download the Mobile version code.
Furthermore, more modules involved not only affect download size but also compile time. With 5K esm modules, the webpack_ requires process alone takes nearly one whole second.
Even concatenated modules won't help, because the file is so large, way above the 170 KB suggested best practice, so we have to spilt the initial chunk into several chunks.
But more split chunk rules lead to more unused code which ends up with huge code size bloat.
How should this be implemented in your opinion?
Webpack's level javascript parser code removal to avoid unnecessary modules in the module graph and chunks in the chunk graph.
If for some reason this cannot be achieved, would running terser as loader after ts loader in order to drop dead code in advance be a dumb idea? @sokra@alexander-akait Are you willing to work on this yourself?
yes
The text was updated successfully, but these errors were encountered:
For 1) it can be difficult to know whats shakeable if you encounter the scenario where the condition were at runtime value, not if(false) for instance. But like if(document) or something - don’t have a perfect example right now.
However statically inferred values should be possible.
How I typically have solved this is with a pitching loader where I’d call this.loadModule and break up the same file into multiple module ids.
on unused exports - if I understand correctly, you mean that by dynamic importing - webpack doesn’t tree shake unused modules you destruct off the dynamic import the same way it would if it were a static import?
I know you can manually tree shake import() with magic comments. I think it’s “usedExports” if I remember correctly
let's close in favor #14347, I added more scenarios, I agree we need to improve this, feel free to send a PR if you want to help, sorry for delay in answers
Feature request
I know in the current webpack build process, dead code removal happens last, which relies on minifier like terser to drop the dead code on per chunk level thanks to @alexander-akait kind explanation.
However, this can lead to significantly bigger chunk sizes due to lack of information.
Take the below code for example, let's say we have two different builds, one for PC users and one for mobiles. Each of the files refers to different esm modules of common node modules packages like lodash-es and MUI.
This common chunk is spilt into a separate chunk by SplitChunkPlugin for better cache opportunity.
pc.tsandmobile.tsboth use some of the code from a common module.lib-modules.tsmimics a large esm package index file.For PC build, we won't want Baz to appear in the final bundle.
But If we perform dead code removal purely on chunks level, after the module graph is built,
Bazis already marked as 'exported' and used. Terser or any other minifier would not know that it is never used, because this information happens across files, only webpack level's javascript compiler knows such kind of thing.And in the above situation, webpack does pre-optimize dead code removal.
It prevents further dependency build processes in dead branches.
However, this case is just a simple example of what can pre dead code elimination can achieve.
There are still a lot of scenarios not covered.
What is the expected behavior?
dead code won't appear in module graph.
What is motivation or use case for adding/changing the behavior?
Much Smaller bundle size for giant project.
I work on a large private company SPA codebase with its bundle size over 70MB before gizpped, with many reasonable or unreasonable splitchunk rules. I found that most unused code can be avoided by simply just not letting the PC user download the Mobile version code.
Furthermore, more modules involved not only affect download size but also compile time. With 5K esm modules, the webpack_ requires process alone takes nearly one whole second.
Even concatenated modules won't help, because the file is so large, way above the 170 KB suggested best practice, so we have to spilt the initial chunk into several chunks.
But more split chunk rules lead to more unused code which ends up with huge code size bloat.
How should this be implemented in your opinion?
Webpack's level javascript parser code removal to avoid unnecessary modules in the module graph and chunks in the chunk graph.
If for some reason this cannot be achieved, would running terser as loader after ts loader in order to drop dead code in advance be a dumb idea?
@sokra @alexander-akait
Are you willing to work on this yourself?
yes
The text was updated successfully, but these errors were encountered: