-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
Description
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.
export const treeShakingTestAsync = async () => {
console.log('treeShakingTestAsync');
// Be replaced with 'false' by DefinePlugin
if (__MOBILE__) {
const { Baz } = await import(/* webpackChunkName: "bundle_mobile" */'./mobile');
console.log(Baz);
} else {
const { foo } = await import(/* webpackChunkName: "bundle_pc" */ './pc');
console.log(foo);
}
};pc.ts and mobile.ts both use some of the code from a common module.
// pc.ts
export { bar , foo } from './lib-modules';// mobile.ts
export { Baz } from './lib-modules';lib-modules.ts mimics a large esm package index file.
// lib-modules
export function foo() { console.log('FuncFoo') };
export function bar() {
console.log('FuncFoo')
};
export class Baz {
private c = 'ClassBaz'
}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.
export const treeShakingTestAsync = async () => {
console.log('treeShakingTestAsync');
const { foo } = await import(/* 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 } = await import(/* webpackChunkName: "bundle_mobile" */'./mobile');
console.log(Baz);
};- unused exports
export const treeShakingTestAsync = async () => {
console.log('treeShakingTestAsync');
const { foo } = await import(/* webpackChunkName: "bundle_pc" */ './pc');
console.log(foo);
};
export const unusedExport = async () => {
console.log('unusedExport');
// this never be refered from entry.
const { Baz } = await import(/* 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