You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用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:

  1. When you use npm link, your library's node_modules folder still has its own copy of Vue (if you listed it as a regular dependency instead of a peerDependency).
  2. 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'
  }
}

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.

  • Rebuild your library with npm run build.
  • In your test project, run npm unlink muvement followed by npm link muvement to 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

火山引擎 最新活动