HMR not working when importing a library with embedded Vue
Vue version
3.2.45
Link to minimal reproduction
https://stackblitz.com/edit/vue-consume-vue-hmr?file=README.md
Steps to reproduce
- Run the vue app with
npm run dev
- Edit content in
src/components/HelloWorld.vue
What is expected?
- Browser automatically shows updated content when a component is edited
What is actually happening?
- Terminal output indicates that
vite
recognizes the changed module - Browser console output from
vite
indicates that the component was hot updated - No content changes are actually visible in the browser until manual reload
System Info
No response
Any additional comments?
I submit this as a Vue issue because the behavior of vite
seems correct, from simple observation.
The usage of a library with embedded Vue is helpful for sharing a self-contained "black box" featureset without dependency or structural conflict for the consuming app.
Related resources for reproduction example
The library app is a minimal Vue 3 app, built with Vite in library mode, visible here:
I don't think you should package the vue code into your library because it will overwrite some of the global variables that vue needs to use.
I don't think you should package the vue code into your library because it will overwrite some of the global variables that vue needs to use.
@zhangzhonghe, thank you for the reply. It is true that removing vue
from the library package will avoid this conflict, but that also greatly weakens my real use case for this library pattern.
Following Vue's goal of being flexible and incrementally adoptable, packaging vue
within the library has allowed me to provide a rich Vue-developed featureset to several different consumers, ranging from unstructured legacy web apps to modern framework apps using various versions of Angular, React, and Vue (as examples). Aside from the broken HMR at development time when consumed by a Vue 3 app, the pattern works quite well!
The major benefit of the embedded vue
is similar to the spirit of Embedded Web Components, where we want to "leverage Vue in a completely consumer-agnostic fashion". Because Vue is a very modern and fast-evolving framework (which I love), the ability to generate an insulated output module is quite valuable to ensure the library can be consumed by applications which are slow to upgrade or have conflicting Vue version dependencies for an extended time.
I think it makes sense for Vue as "The Progressive Framework" to avoid conflict with itself in this usage, and it seems very close to working already. Are there any mechanisms for protecting the global variables you mention from interference? Perhaps a configuration or transformation to be applied to Vue in the library code or its build process?
Are there any mechanisms for protecting the global variables you mention from interference? Perhaps a configuration or transformation to be applied to Vue in the library code or its build process?
You can change the vite.config.ts
to this:
export default defineConfig(({ mode }) => {
return {
plugins: [vue()],
build: {
lib: {
entry: resolve(__dirname, "src/lib.js"),
name: "SampleLibraryVueApp",
fileName: "library",
},
},
// to replace the process.env.NODE_ENV in the vue library
define: {
'process.env.NODE_ENV': JSON.stringify(mode)
}
};
});
This will ensure that the tree shakes off HMR-related code during build time. It might solve your problem.
This will ensure that the tree shakes off HMR-related code during build time. It might solve your problem.
I started thinking in this direction after your first comment, and a fresh skim of the code led me to one portion of global HMR registration in Vue's runtime-core/src/hmr.ts
which looks like this:
if (__DEV__) {
getGlobalThis().__VUE_HMR_RUNTIME__ = {
// ... removed for brevity
}
}
An inspection of my library's build output shows that __DEV__
is translated into process.env.NODE_ENV !== 'production'
, so it makes sense that this behavior is active when my consuming application is running on development server.
I don't know where exactly this __DEV__
replacement happens, but the result is clear enough to identify the issue as a mistake in my configuration of Vite rather than an issue with Vue (in fact, there are comments in the Vue code explaining the intent of the __DEV__
check to support proper tree-shaking at build time).
For my consumer-agnostic use case, I want my library to build in a production-like manner, without including any dynamic dev-time hooks, so I believe this resolution strategy is sound.
Your example modification of the vite.config.ts
looks like a nice solution. I should have some time to try this soon, and I will report back here for documentation purposes and close my issue if it is successful.
Thank you again for taking the time to look, I appreciate the help and thoughtful response.