phosphor-icons / vue

A flexible icon family for Vue
https://phosphoricons.com
MIT License
199 stars 20 forks source link

Imported components used with `<component :is="">` ignored in production #6

Open arpadgabor opened 3 years ago

arpadgabor commented 3 years ago

I'm using a custom component to render all my icons:

<template>
  <component :is="name" :weight="weight" />
</template>

<script lang="ts">
import { defineComponent } from 'vue'

import {
  PhCaretUp as Up,
} from 'phosphor-vue'

export default defineComponent({
  name: 'Icon',
  props: {
    name: { type: String, required: true },
    weight: { default: 'regular' },
  },
  components: {
    Up,
  },
})
</script>

It's all fine in development, but the production bundle ignores all icons. I've just switched from another icon pack that does not cause this (but the bundle size is huge) and right now this problem is pretty annoying.

I've also tried registering the global component in main.ts, to no avail.

Any suggestions are welcome.

rektdeckard commented 3 years ago

I'm noticing that even regular use of icons with v4.1.3 are not rendering in production builds. Gonna take a look this afternoon. @brlodi maybe you can weigh in on this?

brlodi commented 3 years ago

I’ll take a look this afternoon.

brlodi commented 3 years ago

The gist of the problem seems to be that the way the SFCs are being built by Rollup, the template is added as a side-effect, e.g. (the same structure exists in the bundled files, but it's easier to read the ESM version):

// dist/esm/components/PhActivity.vue.js
import component from './PhActivity.vue_vue&type=script&lang.js';
export { default } from './PhActivity.vue_vue&type=script&lang.js';
import { render } from './PhActivity.vue_vue&type=template&id=7b9995bc&lang.js';

component.render = render;

The problem is the library is marked as entirely side-effect free (that was my mistake) and so Vue CLI/Webpack is stripping out everything except the pass-through re-export when the consumer builds the project for production. The templates-turned-render-functions are just being entirely omitted, and trying to render a Vue component with no render function defined creates the empty HTML comment nodes you can see in the browser.

I'll need to do some testing to figure out the right combo of Rollup settings and sideEffects to keep everything tree-shaking without over-shaking. In the meantime, simply removing the "sideEffects": false in the Phosphor-Vue package.json will fix the missing icon problem, but it will also totally disable any meaningful tree-shaking. It's up to you @rektdeckard on whether to use that nuclear option for now.

Side-note, this appears to be a change in the Vue SFC compiler in Vue 3, and something I missed in testing. That's on me.

brlodi commented 3 years ago

Upon further digging, I believe that removing the erroneous "sideEffects": false is all that we need to do. Unfortunately I can't then reintroduce tree-shaking the "right" way, because based on vuejs/rollup-plugin-vue/issues/401 it seems like it might be currently impossible to publish a tree-shakable Vue 3 component library—considering the author of that issue report is as far as I can tell the preeminent authority on Vue library bundling.

RyanPrentiss commented 3 years ago

Using Vue3 and having the same issue -- icons are missing from production build. I tried removing the "sideEffects": false from the phosphor-vue package.json as mentioned above, however no change. I am implementing as <ph-instagram-logo />.

brlodi commented 3 years ago

Just wanted to check in and say I'm still watching this, but a proper fix with working tree-shaking is blocked upstream (actually in the Vue 3 core rather than the Rollup plugin, see https://github.com/vuejs/vue-next/issues/2860).

It's frustrating that Vue 3 launched with full tree-shaking advertised as a first-class feature, but seven months in it's still half-broken when it comes to anything more complex than the sample apps 😞

dnlsndr commented 1 year ago

@brlodi I've found that if you build a library with the preserveModules option set to true in Rollup, vue is able to treeshake the components. So it seems vue has trouble treeshaking as soon as we bundle all components into one dist file. This is implemented for the next major version release in the next branch.

Delaylaph commented 9 months ago

I have the same issue even in dev mode using nuxt 3.9. To fix this, I use an array with the components themselves instead of their names. And it's work fine.

<template>
  <div v-for="icon in icons">
    <component :is="icon" size="36"></component>
  </div>
</template>

<script setup>
import { PhFloppyDisk, PhMoonStars, PhListPlus } from "@phosphor-icons/vue";

let icons = [
  PhFloppyDisk,
  PhListPlus,
  PhMoonStars,
];
</script>