Webpack 动态引入组件

// 假设文件夹路径是 @/components
const modules = {};
const files = require.context('@/components', false, /\.vue$/);

files.keys().forEach(key => {
  const name = key.replace(/(\.\/|\.vue)/g, '');
  modules[name] = files(key).default || files(key);
})

export default modules;

Vite 动态引入组件

// 获取所有 .vue 文件
const modules = import.meta.glob('@/components/*.vue');

// 如果需要立即加载所有模块
const modulesEager = import.meta.glob('@/components/*.vue', { eager: true });

// 转换为组件对象
const components = {};
for (const path in modulesEager) {
  const name = path.split('/').pop().replace(/\.vue$/, '');
  components[name] = modulesEager[path].default;
}

export default components;

使用场景

动态注册全局组件

  1. 主要在应用入口文件 (main.js) 中使用

  2. 使用常规的 app.component 注册

  3. 直接注册,不显式处理异步加载

  4. 更适合一次性全局注册

  5. 相对简单直接

// main.js 或单独的工具文件
import { createApp } from 'vue';

const app = createApp(App);

// Vite 方式
const modules = import.meta.glob('./components/*.vue');

Object.entries(modules).forEach(([path, module]) => {
  const componentName = path
    .split('/')
    .pop()
    .replace(/\.\w+$/, '');
  
  module().then((mod) => {
    app.component(componentName, mod.default);
  })
});

app.mount('#app');

动态导入并自动注册

  1. 可以在任何地方使用,更具模块化

  2. 使用 defineAsyncComponent 进行异步组件定义

  3. 明确使用异步组件加载

  4. 更适合模块化、可复用的组件注册逻辑

  5. 更灵活,可以添加更多自定义逻辑

// utils/dynamicComponents.js
export function registerDynamicComponents(app) {
  // 1. 使用 import.meta.glob 获取所有组件
  const components = import.meta.glob('@/components/custom/**/*.vue')
  
  // 2. 遍历并注册组件
  for (const path in components) {
    // 从路径提取组件名(可自定义命名规则)
    const componentName = path
      .split('/')
      .pop()
      .replace(/\.vue$/, '')
      // 可选:将驼峰式转换为连字符式 MyComponent -> my-component
      .replace(/([a-z])([A-Z])/g, '$1-$2')
      .toLowerCase()
    
    // 3. 使用 defineAsyncComponent 定义异步组件
    app.component(
      componentName,
      defineAsyncComponent({
        loader: components[path],
        loadingComponent: LoadingSpinner, // 加载中的组件
        delay: 200,                      // 延迟显示加载状态
        errorComponent: ErrorComponent,   // 错误时显示的组件
        timeout: 3000                    // 超时时间
      })
    )
  }
}

// 在 main.js 中使用
import { createApp } from 'vue'
import App from './App.vue'
import { registerDynamicComponents } from './utils/dynamicComponents'

const app = createApp(App)
registerDynamicComponents(app)
app.mount('#app')

动态导入并注册指令

import type { App } from 'vue'

// 类型定义
type DirectiveModule = {
  default: {
    name?: string
    [key: string]: any
  }
}
type DirectiveLoader = () => Promise<DirectiveModule>

export async function registerDirectives(app: App) {
  // 1. 动态导入所有指令文件
  const directiveFiles = import.meta.glob<DirectiveLoader>('./*.ts', { import: 'default', eager: false })

  // 2. 排除 index.ts 文件
  const filteredFiles = Object.entries(directiveFiles).filter(([path]) => !path.includes('index.ts'))

  // 3. 加载并注册指令
  await Promise.all(
    filteredFiles.map(async ([path, loader]) => {
      try {
        const module = await loader()
        const directive = module
        
        // 获取指令名 (从文件名或指令对象的 name 属性)
        const directiveName = path.split('/').pop()?.replace(/\.ts$/, '')

        if (directiveName) {
          // 注册指令 (自动添加 v- 前缀)
          app.directive(directiveName, directive)
          console.log(`[Directive] Registered: v-${directiveName}`)
        }
      } catch (error) {
        console.error(`[Directive] Failed to load ${path}:`, error)
      }
    })
  )
}

微前端子应用 (独立组件注册)

// 子应用入口文件
export async function registerComponentsToHost(app) {
  const componentModules = import.meta.glob('./components/*.vue')
  
  const registrationPromises = Object.entries(componentModules).map(
    async ([path, loader]) => {
      const componentName = `SubApp${path.split('/').pop().replace(/\.vue$/, '')}`
      const component = await loader()
      return { name: componentName, component: component.default }
    }
  )
  
  const components = await Promise.all(registrationPromises)
  
  // 返回注册信息供主应用使用
  return {
    install: (hostApp) => {
      components.forEach(({ name, component }) => {
        hostApp.component(name, component)
      })
    }
  }
}

// 主应用中使用
const subAppComponents = await import('sub-app/componentRegistry')
subAppComponents.install(mainApp)

延迟加载可视化编辑器组件

只在用户进入编辑器页面时才加载相关组件

// 编辑器组件懒加载
const EditorComponentLoader = {
  install(app) {
    // 注册一个虚拟组件作为占位
    app.component('LazyEditor', {
      template: '<div class="editor-placeholder"></div>',
      async mounted() {
        // 实际加载编辑器组件
        const editors = import.meta.glob('../editors/*.vue')
        const { default: TextEditor } = await editors['../editors/TextEditor.vue']()
        const { default: ImageEditor } = await editors['../editors/ImageEditor.vue']()
        
        // 动态替换当前组件
        this.$options.components.TextEditor = TextEditor
        this.$options.components.ImageEditor = ImageEditor
        this.$forceUpdate()
      }
    })
  }
}

// 在应用中使用
app.use(EditorComponentLoader)

动态绑定到特定组件

<template>
  <div>
    <!-- 动态渲染组件 -->
    <component 
      v-for="(comp, name) in dynamicComponents" 
      :key="name"
      :is="comp"
    />
  </div>
</template>

<script setup>
import { ref, onMounted, defineAsyncComponent } from 'vue'

const dynamicComponents = ref({})

onMounted(async () => {
  // 动态导入指定目录下的组件
  const modules = import.meta.glob('@/components/custom/*.vue')
  
  // 转换为组件对象
  for (const path in modules) {
    const name = path.split('/').pop().replace(/\.vue$/, '')
    dynamicComponents.value[name] = defineAsyncComponent({
      loader: modules[path],
      loadingComponent: LoadingSpinner
    })
  }
})
</script>

按需加载特定组件

// 动态加载指定名称的组件
async function loadSpecificComponent(componentName) {
  // 1. 构建组件路径模式
  const componentPath = `../components/custom/${componentName}.vue`
  
  // 2. 使用 import.meta.glob 的精确匹配
  const componentModules = import.meta.glob('../components/custom/*.vue', {
    import: 'default',
    eager: false
  })
  
  // 3. 检查并加载指定组件
  if (componentModules[componentPath]) {
    try {
      const module = await componentModules[componentPath]()
      return module.default || module
    } catch (error) {
      console.error(`加载组件 ${componentName} 失败:`, error)
      return null
    }
  }
  return null
}

// 使用示例
const myComponent = await loadSpecificComponent('MySpecialComponent')
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. Webpack 动态引入组件
  2. 2. Vite 动态引入组件
  3. 3. 使用场景
    1. 3.1. 动态注册全局组件
    2. 3.2. 动态导入并自动注册
    3. 3.3. 动态导入并注册指令
    4. 3.4. 微前端子应用 (独立组件注册)
    5. 3.5. 延迟加载可视化编辑器组件
    6. 3.6. 动态绑定到特定组件
    7. 3.7. 按需加载特定组件