Open thebanjomatic opened 1 year ago
I've been trying to find a good way to demonstrate the issue, and the best I've come up with is to past the following into https://try.terser.org/ and then swap the comments on the last two lines.
The same behavior reproduces when using esbuild as the minifier: link
function getExports(component) {
return component.exports
}
function normalizeComponent (
scriptExports,
render,
staticRenderFns,
functionalTemplate,
injectStyles,
scopeId,
moduleIdentifier, /* server only */
shadowMode /* vue-cli only */
) {
// Vue.extend constructor export interop
var options = typeof scriptExports === 'function'
? scriptExports.options
: scriptExports
// render functions
if (render) {
options.render = render
options.staticRenderFns = staticRenderFns
options._compiled = true
}
// functional template
if (functionalTemplate) {
options.functional = true
}
// scopedId
if (scopeId) {
options._scopeId = 'data-v-' + scopeId
}
var hook
if (moduleIdentifier) { // server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__
}
// inject component styles
if (injectStyles) {
injectStyles.call(this, context)
}
// register component module identifier for async chunk inference
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier)
}
}
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook
} else if (injectStyles) {
hook = shadowMode
? function () {
injectStyles.call(
this,
(options.functional ? this.parent : this).$root.$options.shadowRoot
)
}
: injectStyles
}
if (hook) {
if (options.functional) {
// for template-only hot-reload because in that case the render fn doesn't
// go through the normalizer
options._injectStyles = hook
// register for functional component in vue file
var originalRender = options.render
options.render = function renderWithStyleInjection (h, context) {
hook.call(context)
return originalRender(h, context)
}
} else {
// inject component registration as beforeCreate hook
var existing = options.beforeCreate
options.beforeCreate = existing
? [].concat(existing, hook)
: [hook]
}
}
return {
exports: scriptExports,
options: options
}
}
const icon = "_icon_5y8ds_1";
const style0 = {
icon,
};
const _sfc_main = /* @__PURE__ */ Vue.extend({
computed: {
value() {
return Date.now();
},
},
});
var _sfc_render = function render() {
var _vm = this, _c = _vm._self._c;
_vm._self._setupProxy;
return _c("div", { class: _vm.iconClasses }, [_vm._t("default")], 2);
};
var _sfc_staticRenderFns = [];
const __cssModules = {
"$style": style0
};
function _sfc_injectStyles(ctx) {
for (var key in __cssModules) {
this[key] = __cssModules[key];
}
}
var __component__ = /*#__PURE__*/ normalizeComponent(
_sfc_main,
_sfc_render,
_sfc_staticRenderFns,
false,
_sfc_injectStyles,
null,
null,
null
)
// swap between the commented lines below:
var Foo = __component__.exports
// var Foo = /*#__PURE__*/ getExports(__component__)
The version of this code represented by this fix (var Foo = /*#__PURE__*/ getExports(__component__)
) results in zero remaining code, and the existing behavior results in 1020 byte of code left behind.
You can also reproduce the fully tree-shaken result with the existing code by using the following terser options to assert that all getters are pure, but it isn't generally correct or safe to do so:
{
"compress": {
"pure_getters": true
}
}
You are absolutely right. I also encountered the same issue, and it seems that this merge request will have no negative impact. please look about this merge request @sodatea
The code currently generated by this plugin looks something like the following when building a library:
src/index.js
In the event you aren't actually using the Foo export, using a minifier like Terser, this code is unable to be dropped. This is because Terser assumes that the property property access (
__component__.exports
) is not side-effect free, and as a result it decides it can not drop it, and by extension it can not drop the rest of the component code. For more context, see thepure_getters
documentation here: https://terser.org/docs/api-reference#compress-optionsTo work around this, I have wrapped the
__component__.exports
statement in a function so that we can mark the function invokation as#__PURE__
and thus allow for unused components bundled into a single library to be dropped fully.The resulting code looks like:
Additional Context about the problem
I am using vite & vite-plugin-vue2 to build a component library. In this library there are a couple hundred icons which are each vue components. The library is shipped as a single .js file with named exports for each icon component.
When consuming this library from a vite application with the default minification settings (esbuild), tree-shaking works correctly and only the icons that are used are present in the bundled application code. I believe rollup may be just handling things better here since terser and esbuild both seem to behave identically in this regard. (see comment below for reproducers)
However, when consuming this library from webpack and using terser as the minification engine, the bundled application has the code for every component is being included even though only a couple are being used. When I make the change in this PR, I am no longer seeing the unused components in the resulting bundle.