Open zhangyx1998 opened 1 year ago
I further investigated the problem with the help of vite-plugin-inspect. I have identified the exact cause of this problem:
@vitejs/plugin-vue intentionally adds a /*#__PURE__*/
mark for the rewritten default!
The actual code that did this can be found in packages/plugin-vue/src/main.ts
.
Transformed Vue SFC (raw file available at reproduction):
import { defineComponent } from 'vue'; export const component = defineComponent({ setup(message) { return { message }; } }); export const getComponentB = (message) => { const { setup, ...others } = component; return defineComponent({ setup() { return { ...setup(message) }; }, ...others }); }; // Injected by vite-plugin-vue transform const _sfc_main = component; import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue" function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return (_openBlock(), _createElementBlock("span", null, _toDisplayString(_ctx.message), 1)) } import _export_sfc from 'plugin-vue:export-helper' export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render]])
/*#__PURE__*/
mark, the issue disappears!I did not close this issue because the implementation in @vitejs/plugin-vue is somehow consistent with the behavior of the 'facade module' explanation presented in (compiler-sfc/README).
I did a lot of search and found out that there has not been any complaints on this issue, so I am not sure if my use case should actually be supported at all.
Please leave some comment on this, thanks!
I found that removing /*#__PURE__*/
will fail many tests, since everything in attachedProps
will be considered as referenced and will not shake away.
Take the above code (Transformed Vue SFC) for example, my objective is to allow tree shaking when and only when both component
and _sfc_main
are not referenced. Whatever tag we add to _sfc_main
will not be able to take effect on component
.
That said, one feasible approach would be directly manipulating the AST to rewrite defineComponent(...args)
to defineComponent(...args, ...attachedProps)
.
To cover those who do not wrap their component in defineComponent()
, a compile-time flag could be used to indicate if defineComponent()
exists in context, and fall back to rewriting default export if not.
Again, please leave some comments!
A commit (ed6d312) is pushed to my forked of "vite-plugin-vue". In this commit, I demonstrated the idea that I described above.
Although the changes are made in the plugin repository, I believe they actually belongs to the compileScript
module in compiler-sfc
package. Which is why I keep updating this issue. Since '@babel/parse' and AST traverse should not be exposed to the plugin.
<template>
<span>{{ message }}</span>
</template>
<script>
import { defineComponent } from 'vue';
export const component = defineComponent({
setup(message) {
return { message };
}
});
export const getComponentB = (message) => {
const { setup, ...others } = component;
return {
setup() {
return { ...setup(message) };
},
...others
};
};
export default component;
</script>
Take a look at const component = IIFE
!
import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("span", null, _toDisplayString(_ctx.message), 1 /* TEXT */))
}
import.meta.hot.accept(mod => {
if (!mod) return
const { default: updated, _rerender_only } = mod
if (_rerender_only) {
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})
import { defineComponent } from 'vue';
export const component = (() => {
const sfc_main = /*#__PURE__*/ Object.assign(defineComponent({
setup(message) {
return { message };
}
}), {
"render": _sfc_render,
"__file": "/Users/Yuxuan/Lab/issue-reproduction/src/components/B.vue",
"__hmrId": "f6858c4e"
})
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(sfc_main.__hmrId, sfc_main)
return sfc_main
})();
export const getComponentB = (message) => {
// ...unchanged code
};
export default component;
import { defineComponent } from 'vue';
export const component = defineComponent({
setup(message) {
return { message };
}
});
export const getComponentB = (message) => {
// ...unchanged code
};
const _sfc_main = component;
import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("span", null, _toDisplayString(_ctx.message), 1 /* TEXT */))
}
_sfc_main.__hmrId = "f6858c4e"
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
import.meta.hot.accept(mod => {
if (!mod) return
const { default: updated, _rerender_only } = mod
if (_rerender_only) {
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})
import _export_sfc from 'plugin-vue:export-helper'
export default /*#__PURE__*/_export_sfc(_sfc_main, [['render', _sfc_render], ['__file', "/Users/Yuxuan/Lab/issue-reproduction/src/components/B.vue"]])
Besides the factory function usage I mentioned above, this change can potentially enable multiple component implementation sharing the same template and styleSheet. Since export default
is no longer required to inject props such as render functions (but will still be working as a fall back option).
This might be especially useful when user want to make serval components that look alike but function slightly differently.
<template> ... </template>
<script>
export const component_1 = defineComponent({
// ... logic for component 1
})
export const component_2 = defineComponent({
// ... logic for component 2
})
</script>
I have been talking to myself on this issue for several days. I devoted significant effort and time on this. I want to make it mean something.
@sxzz @yyx990803 Commit messages show that you are likely the primary contributors to the related modules. May I directly ask for your opinions?
Vue version
latest
Link to minimal reproduction
https://github.com/zhangyx1998/vite-plugin-vue-issue-reproduction
Steps to reproduce
npm install
npm run dev
will show all 3 components.npm run preview
will only show component A and C.P.S. Screenshots are shown in README
What is expected?
The most intuitive behavior I would expect is
compiler-sfc
automatically inject compiled render function into whichever object that is wrapped withdefineComponent()
. Regardless of whether and how the component is exported. In this way more flexibility would be given to the developer.What is actually happening?
However, in the current codebase, the
facade module
generated bycompiler-sfc
will not get a chance to run if the default exported variable is not accessed.Due to the tree-shaking process, even if the default export was imported by another module, it will still not work if the imported variable was not read. This can be shown by commenting out this line in the provided reproduction snippet.
System Info
Any additional comments?
This issue was originally submitted under
vitejs/plugin-vue
. But as I dug deeper into the build process, I found that my issue was not introduced by the plugin. So I closed that issue and re-submitted it here.Below is a copy of the original issue in which I wrote a code snippet to justify my use case