使用npm link链接后Vue 3组件初始化异常问题排查
Let me break down what's happening here and how to fix it—this is a super common gotcha when developing Vue libraries with npm link!
The Problem Recap
You built a Vue 3 library that generates dynamic components (like <m-div>, <m-button>) using defineComponent and h. It works perfectly when:
- Imported locally from your project's
@/components/muvement.js - Installed via npm (
npm install muvement)
But when you use npm link to connect the library to a test project, or import the built bundle via an absolute path, you get errors where injectHook has a null target. Even setting Webpack's resolve.symlinks: false didn't help.
Here's your core library code for reference:
// muvement.js import { defineComponent, ref, onMounted, h } from 'vue'; const createMuvement = (tag) => { return defineComponent({ name: `m-${tag}`, setup(props, context) { const root = ref(null); onMounted(() => { console.log(root.value); }); return () => h(tag, { ...context.attrs, ref: root }, context.slots); } }); }; const muvement = (...tags) => { const components = {}; tags.map((tag) => (components[`m-${tag}`] = createMuvement(tag))); return components; }; export { muvement };
And your test component usage:
<!-- Home.vue --> <template> <div> <m-div>div</m-div> <m-button>button</m-button> </div> </template> <script> import { muvement } from "muvement"; export default { name: "Home", components: { ...muvement("div", "button") } }; </script>
The Root Cause: Dual Vue Instances
The issue boils down to Vue being loaded twice:
- When you use
npm link, your library'snode_modulesfolder still has its own copy of Vue (if you listed it as a regulardependencyinstead of apeerDependency). - Your test project also loads its own Vue instance from its own
node_modules.
Vue's internal APIs (like injectHook, which powers lifecycle hooks such as onMounted) are tied to the specific Vue instance they were imported from. When your library uses its own Vue instance to register hooks, but the component is rendered in the test project's Vue instance, there's no matching hook context—hence the null target error.
Local imports or regular npm installs work because they share a single Vue instance:
- Local imports use your test project's Vue directly.
- Regular npm installs resolve Vue to the test project's copy if you've set up peer dependencies correctly.
Step-by-Step Fixes
1. Set Vue as a Peer Dependency
First, update your library's package.json to mark Vue as a peerDependency—this tells npm that your library expects the host project to provide Vue, instead of packaging its own copy. Keep Vue in devDependencies for local development:
{ "peerDependencies": { "vue": "^3.0.0" }, "devDependencies": { "vue": "^3.0.0" // ... other dev dependencies } }
After making this change, delete your library's node_modules and run npm install again to clean up.
2. Externalize Vue in Your Build Config
Ensure your build tool (Vite, Webpack, etc.) doesn't bundle Vue into your library. This forces the library to use the host project's Vue instance.
For Vite:
Add vue to the external array in your vite.config.js:
// vite.config.js (library) export default { build: { lib: { entry: 'src/muvement.js', name: 'Muvement', fileName: 'muvement' }, rollupOptions: { external: ['vue'], output: { // Provide global variable for UMD builds (optional) globals: { vue: 'Vue' } } } } }
For Webpack:
Use the externals option to exclude Vue from bundling:
// webpack.config.js (library) module.exports = { output: { // ... your output config }, externals: { vue: 'vue' } }
3. Configure the Test Project to Resolve Symlinks Correctly
If you're using Vite in your test project, enable preserveSymlinks to ensure it uses its own Vue instance instead of the library's:
// vite.config.js (test project) export default { resolve: { preserveSymlinks: true } }
For Webpack, double-check that resolve.symlinks: false is set in your test project's config—this prevents Webpack from resolving symlinks to the library's node_modules.
4. Re-Link and Rebuild
- Rebuild your library with
npm run build. - In your test project, run
npm unlink muvementfollowed bynpm link muvementto refresh the symlink. - Restart your test project's development server to pick up the changes.
Verify the Fix
After these steps, your library and test project will share a single Vue instance. The injectHook target will no longer be null, and your dynamic components should work as expected with npm link or absolute path imports.
内容的提问来源于stack exchange,提问作者Justin Taddei




