New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
code after 'break' statement is not removed, before terser plugin. #16480
Comments
I think the best place for it is terser repo, but it is hard to implement (dce is the most diffucult part), that is why you faced with it and looks like terser is not implement it, try to rewrite: async function foo() {
const { Baz } = await import(/* webpackChunkName: "bundle_mobile" */'./mobile');
console.log(Baz);
}
async function bar() {
const { foo } = await import(/* webpackChunkName: "bundle_pc" */ './pc');
console.log(foo);
}
export const treeShakingTestAsync = async () => {
console.log('treeShakingTestAsync');
// Be replaced with 'false' by DefinePlugin
if (__MOBILE__) {
await foo();
} else {
await bar();
}
}; |
Thanks for you reply.@alexander-akait . I test async import before. It seems it could not be shaken #16271 #14800. |
Because it is DCE (dead code elimination), it is not a part of webpack, webpack just says |
#14800 is for import destrucation and remove unsed |
I changed the demo into the code above var treeShakingTestAsync = /*#__PURE__*/(/* unused pure expression or super */ null && (function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log('treeShakingTestAsync');
if (true) {
_context.next = 6;
break;
}
_context.next = 4;
return foo();
case 4:
_context.next = 8;
break;
case 6:
_context.next = 8;
return bar();
case 8:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return function treeShakingTestAsync() {
return _ref.apply(this, arguments);
}; As you can see, both foo and bar still remains return _foo.apply(this, arguments);
}
function _foo() {
_foo = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() {
var _yield$import, Baz;
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return Promise.all(/* import() | bundle_mobile */[__webpack_require__.e(910), __webpack_require__.e(593)]).then(__webpack_require__.bind(__webpack_require__, 657));
case 2:
_yield$import = _context2.sent;
Baz = _yield$import.Baz;
console.log(Baz);
case 5:
case "end":
return _context2.stop();
}
}
}, _callee2);
}));
return _foo.apply(this, arguments);
}
function bar() {
return _bar.apply(this, arguments);
}
function _bar() {
_bar = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() {
var _yield$import2, foo;
return _regeneratorRuntime().wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
_context3.next = 2;
return Promise.all(/* import() | bundle_pc */[__webpack_require__.e(910), __webpack_require__.e(941)]).then(__webpack_require__.bind(__webpack_require__, 395));
case 2:
_yield$import2 = _context3.sent;
foo = _yield$import2.foo;
console.log(foo);
case 5:
case "end":
return _context3.stop();
}
}
}, _callee3);
}));
return _bar.apply(this, arguments);
} which lead to |
Yeah, code is complex, seems like it is impossible, can you try to switch on |
Sorry for not being able to express myself more clearly(not a native speaker).
"use strict";
(self["webpackChunkgetting_started_using_a_configuration"] = self["webpackChunkgetting_started_using_a_configuration"] || []).push([[910],{
/***/ 253:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "Kb": () => (/* binding */ bar),
/* harmony export */ "Mu": () => (/* binding */ Baz),
/* harmony export */ "RN": () => (/* binding */ foo)
/* harmony export */ });
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function foo() {
console.log('FuncFoo');
}
;
function bar() {
console.log('FuncFoo');
}
;
var Baz = /*#__PURE__*/_createClass(function Baz() {
_classCallCheck(this, Baz);
this.c = 'ClassBaz';
});
/***/ })
}]); |
@icy0307 Do you use |
https://github.com/icy0307/webpack-mini-repo @alexander-akait |
You can see in my repo , I turned off 'minimize' completely, anything in the comes after the 'false' if statement, is already been removed before. (I even add a debugger in terser's entry , it has not been called if minimizer has been turned off) var __webpack_exports__ = {};
/* unused harmony export treeShakingTestAsync */
const treeShakingTestAsync = async () => {
console.log('treeShakingTestAsync');
// @ts-ignore
if (false) {}
else {
const { foo } = await Promise.all(/* import() | bundle_pc */[__webpack_require__.e(910), __webpack_require__.e(941)]).then(__webpack_require__.bind(__webpack_require__, 422));
console.log(foo);
}
}; So I guess this removal part is not performed by minimizer. Am I misunderstanding something? @alexander-akait |
Sorry it is out of scope webpack at all, I said above, after babel you have: var treeShakingTestAsync = /*#__PURE__*/(/* unused pure expression or super */ null && (function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
var _yield$import, Baz, _yield$import2, _foo;
return _regeneratorRuntime().wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log('treeShakingTestAsync');
// @ts-ignore
if (!foo) {
_context.next = 9;
break;
}
_context.next = 4;
return Promise.all(/* import() | bundle_mobile */[__webpack_require__.e(910), __webpack_require__.e(593)]).then(__webpack_require__.bind(__webpack_require__, 657));
case 4:
_yield$import = _context.sent;
Baz = _yield$import.Baz;
console.log(Baz);
_context.next = 14;
break;
case 9:
_context.next = 11;
return Promise.all(/* import() | bundle_pc */[__webpack_require__.e(910), __webpack_require__.e(941)]).then(__webpack_require__.bind(__webpack_require__, 395));
case 11:
_yield$import2 = _context.sent;
_foo = _yield$import2.foo;
console.log(_foo);
case 14:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return function treeShakingTestAsync() {
return _ref.apply(this, arguments);
};
}())); i.e. // @ts-ignore
if (!__MOBILE__) {
_context.next = 9;
break;
} if you set We handle code after loaders, and code after Solutions:
export const treeShakingTestAsync = () => {
console.log('treeShakingTestAsync');
// @ts-ignore
if (__MOBILE__) {
Promise.resolve().then(() => import(/* webpackChunkName: "bundle_mobile" */'./mobile')).then((loadded) => {
console.log(loadded.Baz);
})
} else {
Promise.resolve().then(() => import(/* webpackChunkName: "bundle_pc" */ './pc')).then((loadded) => {
console.log(loadded.foo);
})
}
}; Sorry we can't do something here. Babel output non statical analizable code, so we can't drop more branches as you want, anyway I think babel can optimize own output and avoid generate weird |
Thanks for the detailed explanation!
Would it be possible and rational to run a custom loader before babel loader , simply to drop this code(trunoff mangle just compress)?
|
No it is impossible, but you can do it, you can keep code in ES latest and use ESM modues and when you need old browser support run babel on webpack generated code, it is good solutions when you need really old browser suppport (or you need something special after bundling), you can create plugin or just use So tree-shaking will work and you will have support of old browsers. Sorry, we are statical analizator, you want to handle dynamic case, it is out of scope webpack and require a lot of complex logic (not sure bundle should do it at all). |
Thank you! @alexander-akait |
What I am suggesting is to run terser twice. The root cause if this problem is webpack do eliminate code before chunks are created(via 'ConstPlugin'?), but some loaders make the optimization volatile. Why can't run code elimination before all javascript loader one more time as well? |
Because loader generates extra code and we need handle it - loader can add more |
Feel free to feedback |
Feature request
Thx for this awesome library.
First of all, I don't know is this a bug or just not implemented yet.
This is my mini repo to reproduce this problem. The webpack version I used is 5.38.1
In
index.ts
, I'm trying to load the dependencies dynamically in order to get small bundle size.pc.ts
andmobile.ts
both use some of the code from a common module.lib-modules.ts
mimics a large esm package index file.In the webpack config , everything is default, except turning minimizer off to see what webpack does before passing to terser.
and bundle the pseudo package
lib-modules
in to one bundle.The result is perfect, webpack removed the dead code block in bracket after "false" before involvement of SplitchunkPlugin or TerserPlugin.
Baz
is not in the output.But when I add a babel-loader after ts-loader for polyfills
the code get transpiled to
Although,
case 4
can never be reached, it still remains in the source code, cause modulemobile
and its dependencies being bundled, which leads to huge bundle size in real world.What is the expected behavior?
Baz is not in the output
What is motivation or use case for adding/changing the behavior?
smaller bundle size
How should this be implemented in your opinion?
There is an export part in the vendors bundle.
Clearly, dead code removal would not being working. webpack seems removed all code block after the 'false' statement before compilation 'sealed'. Could you do the same for 'break' statement that never could be reached?
@sokra @alexander-akait
Are you willing to work on this yourself?
yes
But I am not familiar with webpack's compilation process, is there any docs could help?
Also I also wondering , there seems be a nuance in 'tree-shaking'(things not be in included, or 'rolled up') and 'dead code removal', Is there any doc elaborate on this subject?
The text was updated successfully, but these errors were encountered: