Subscribe on changes!

SFC is not tree-shakeable: `lang=ts` and 2 `<script>` blocks

avatar
May 23rd 2022

Vue version

3.2+

Link to minimal reproduction

https://github.com/0x009922/vue-sfc-tree-shaking-issue

Steps to reproduce

  • Create a component:

    <script lang="ts">
    export default defineComponent({
        name: 'SampleComponent'
    })
    </script>
    
    <script setup lang="ts">
    import { ref } from 'vue'
    
    const counter = ref(0)
    </script>
    
    <template>
        <button @click="counter++">{{ counter }}</button>
    </template>
    
  • Re-export it from a module with other stuff:

    // FILE: lib.ts
    export { default as SampleComponent } from './SampleComponent.vue'
    
    export function someUtility(): number {
        return 42
    }
    
  • Import other stuff in a bundler entry:

    // FILE: entry.ts
    import { someUtility } from './lib'
    
    console.log(someUtility())
    
  • Bundle it (I use Vite's library mode)

What is expected?

Bundler output:

function someUtility() {
  return 42;
}

console.log(someUtility());

What is actually happening?

Bundled file weight is 42kb with the whole Vue.

System Info

System:
    OS: Linux 5.15 Manjaro Linux
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 25.44 GB / 31.17 GB
    Container: Yes
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.15.0 - ~/.nvm/versions/node/v16.15.0/bin/node
    npm: 8.5.5 - ~/.nvm/versions/node/v16.15.0/bin/npm
  Browsers:
    Firefox: 100.0
  npmPackages:
    vue: ^3.2.36 => 3.2.36

Any additional comments?

If you remove lang=ts and leave just 2 <script> blocks, then tree-shaking works. But even in that case the bundle output has a dirt:

defineComponent({
    name: 'SampleComponentJs'
});

function someUtility() {
  return 42;
}

console.log(someUtility());

Context: we have a component library, and we use lang=ts for components. Due to this bug, we can't tree-shake unused stuff from our library.

Maybe related? https://github.com/vuejs/core/issues/2860

avatar
May 23rd 2022

You are manually calling defineComponent(), which makes it look like it has side effects to the bundler. This is unnecessary if you are just defining a component name - just do export default { name: '...' }.

Also, since 3.2.24 SFCs using <script setup> automatically infers name option based on filename so it's not even necessary to use a separate <script> block.

If you do need defineComponent() for whatever reason, mark it as side-effect free with the pure annotation (this works in both webpack and vite):

/*#__PURE__*/ defineComponent({
    name: 'SampleComponentJs'
});
avatar
May 25th 2022

Thank you very much! By the way, neither /* @__PURE__ */ nor /*#__PURE__*/ works - these marks don't make any sense.