SFC is not tree-shakeable: `lang=ts` and 2 `<script>` blocks
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
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'
});