Lettable operators: scope hoisting prevents dead code removal
See original GitHub issueRxJS version: 5.5.0
Code to reproduce:
webpack.config.js
:
const rxPaths = require("rxjs/_esm5/path-mapping");
const webpack = require("webpack");
const path = require("path");
const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
module.exports = {
entry: "./entry.js",
output: {
path: path.resolve("./dist"),
filename: "[name].bundle.js"
},
resolve: {
alias: rxPaths("./node_modules")
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(), // enable scope hoisting
new UglifyJSPlugin({
parallel: true,
uglifyOptions: {
ecma: 5,
compress: {
passes: 3
}
}
})
]
};
entry.js
:
import { map } from "rxjs/operators";
// do something with `map`
The above is a variation of the simple config in the lettable operators docs, modified to use webpack’s new UglifyJS plugin.
When you use this config with code that imports from rxjs/operators
, it will result in every operator being included in the final bundle whether or not it’s being used. But if you comment out the ModuleConcatenationPlugin
line, the unused operators will be properly removed from the output bundle.
This is happening because the ModuleConcatenationPlugin
will move the re-exports in rxjs/operators/index.js
into the same scope as the actual operator definitions, which prevents webpack from identifying whether those re-exports are actually being used.
Bundle output snippet with scope hoisting disabled:
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__audit__ = __webpack_require__(40);
/* unused harmony reexport audit */
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1__auditTime__ = __webpack_require__(75);
/* unused harmony reexport auditTime */
Bundle output snippet with scope hoisting enabled:
/* unused concated harmony import audit */
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, false, function() { return audit; });
/* unused concated harmony import auditTime */
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, false, function() { return auditTime; });
Note the difference in unused harmony reexport audit
vs concated harmony reexport
; it’s this difference that prevents UglifyJS from removing the unused operators in the latter case.
Recommendation:
This is not an rxjs
issue, but I thought it might be useful to document the behaviour here and suggest removing ModuleConcatenationPlugin
from the example webpack config. Or maybe acknowledging that scope hoisting implies a tradeoff of larger bundle size (when importing from rxjs/operators
) vs. smaller parsing/resolving time.
Issue Analytics
- State:
- Created 6 years ago
- Reactions:2
- Comments:7 (1 by maintainers)
I don’t believe this is an issue any longer with newer bundlers. Closing for now.
I followed the great example of @jgoz and @ptitjes, but in my particular use case, I had trouble; I would get errors where webpack would not be able to resolve the transformed operator, even though it was present in the resolve alias.
I think its because
import { mapTo } from 'rxjs/operators';
(transformed toimport { mapTo } from 'rxjs/operators/mapTo';
matched bothrxjs/operators
andrxjs/operators/mapTo
. I just did something like this to make it be an exact match (see webpack resolve documentation):I’m not really sure why this is not the default/what comes out of the path mapping itself.