前端动态导入(import.meta.glob)

150 阅读2分钟

以下详细介绍 import.meta.glob 的使用:

1. 参数详解

import.meta.glob(
  pattern,          // 匹配模式:字符串或字符串数组
  {
    eager?: boolean, // 是否同步导入
    import?: string | string[], // 指定导入的内容
    query?: stringRecord<string, string | number | boolean> // 查询参数
  }
)

pattern 通配符详解

2. 基础示例(带详细注释)

// 1. 基本异步导入
const modules = import.meta.glob('./modules/*.ts')

async function loadModules() {
  // 遍历所有匹配的模块
  for (const path in modules) {
    // 等待模块加载完成
    const module = await modules[path]()
    // 输出模块路径和内容
    console.log('模块路径:', path)
    console.log('模块内容:', module)
  }
}

// 2. 同步导入(eager 模式)
const eagerModules = import.meta.glob('./modules/*.ts', {
  eager: true // 设置为 true 表示同步导入
})

// 3. 导入特定内容
const specificImports = import.meta.glob('./components/*.vue', {
  import: 'default', // 只导入默认导出
  eager: true
})

// 4. 多种导入内容
const multipleImports = import.meta.glob('./components/*.vue', {
  import: ['default', 'setup'], // 导入多个指定内容
  eager: true
})

// 5. 以 URL 形式导入
const imageUrls = import.meta.glob('./assets/*.png', {
  query: '?url' // 作为 URL 导入
})

// 6. 导入原始内容
const rawContents = import.meta.glob('./files/*.md', {
  query: '?raw' // 作为原始文本导入
})

// 7. 多模式匹配
const multiPattern = import.meta.glob([
  './components/**/*.vue', // 匹配所有子目录的 Vue 文件
  '!./components/ignored/*.vue' // 排除特定目录
])

3. 实际使用场景

场景一:动态组件加载系统

// components/loader.ts

// 定义组件模块的类型
interface ComponentModule {
  default: any; // Vue 组件
  meta?: {
    name: string;
    version: string;
  }
}

// 导入所有组件
const componentModules = import.meta.glob<ComponentModule>('./*/index.vue')

export class ComponentLoader {
  // 存储已加载的组件
  private loadedComponents: Map<string, any> = new Map()
  
  // 加载组件
  async loadComponent(name: string): Promise<any> {
    // 构建组件路径
    const componentPath = `./${name}/index.vue`
    
    // 检查组件是否存在
    if (!componentModules[componentPath]) {
      throw new Error(`组件 ${name} 不存在`)
    }
    
    // 检查是否已加载
    if (this.loadedComponents.has(name)) {
      return this.loadedComponents.get(name)
    }
    
    try {
      // 加载组件
      const module = await componentModules[componentPath]()
      // 存储组件
      this.loadedComponents.set(name, module.default)
      return module.default
    } catch (error) {
      throw new Error(`加载组件 ${name} 失败: ${error.message}`)
    }
  }
}

场景二:多语言文件管理系统

// i18n/loader.ts

// 定义语言包类型
interface LocaleMessages {
  [key: string]: any;
}

// 导入所有语言文件
const localeFiles = import.meta.glob('./locales/**/*.json', {
  eager: true // 同步导入
})

export class I18nLoader {
  // 存储语言包
  private messages: Record<string, LocaleMessages> = {}
  
  constructor() {
    // 初始化语言包
    this.initializeLocales()
  }
  
  private initializeLocales() {
    // 遍历所有语言文件
    Object.entries(localeFiles).forEach(([path, content]) => {
      // 解析路径获取语言代码和命名空间
      // 例如: ./locales/zh-CN/common.json
      const matches = path.match(/\.\/locales\/([^/]+)\/([^.]+)\.json/)
      if (matches) {
        const [, locale, namespace] = matches
        
        // 初始化语言对象
        if (!this.messages[locale]) {
          this.messages[locale] = {}
        }
        
        try {
          // 赋值
          const messages = content
          // 按命名空间存储消息
          this.messages[locale][namespace] = messages
        } catch (error) {
          console.error(`解析语言文件失败 ${path}:`, error)
        }
      }
    })
  }
  
  // 获取特定语言的消息
  getMessages(locale: string): LocaleMessages {
    return this.messages[locale] || {}
  }
  
  // 获取支持的语言列表
  getSupportedLocales(): string[] {
    return Object.keys(this.messages)
  }
}

场景三:插件系统

// plugins/manager.ts

// 定义插件接口
interface Plugin {
  name: string;
  install: (app: any) => void;
  priority?: number;
}

// 导入所有插件
const pluginModules = import.meta.glob<{ default: Plugin }>('./*/index.ts')

export class PluginManager {
  // 存储已加载的插件
  private loadedPlugins: Plugin[] = []
  
  // 加载所有插件
  async loadPlugins(): Promise<void> {
    // 存储加载Promise
    const loadPromises: Promise<void>[] = []
    
    // 遍历所有插件模块
    for (const path in pluginModules) {
      const loadPromise = (async () => {
        try {
          // 加载插件模块
          const module = await pluginModules[path]()
          const plugin = module.default
          
          // 验证插件格式
          if (!plugin.name || !plugin.install) {
            throw new Error(`插件格式无效: ${path}`)
          }
          
          // 添加到已加载插件列表
          this.loadedPlugins.push(plugin)
        } catch (error) {
          console.error(`加载插件失败 ${path}:`, error)
        }
      })()
      
      loadPromises.push(loadPromise)
    }
    
    // 等待所有插件加载完成
    await Promise.all(loadPromises)
    
    // 按优先级排序
    this.loadedPlugins.sort((a, b) => 
      (b.priority || 0) - (a.priority || 0)
    )
  }
  
  // 安装所有插件
  async installPlugins(app: any): Promise<void> {
    // 确保插件已加载
    if (this.loadedPlugins.length === 0) {
      await this.loadPlugins()
    }
    
    // 安装每个插件
    for (const plugin of this.loadedPlugins) {
      try {
        await plugin.install(app)
        console.log(`插件 ${plugin.name} 安装成功`)
      } catch (error) {
        console.error(`插件 ${plugin.name} 安装失败:`, error)
      }
    }
  }
}

4. 使用注意事项

  1. 路径规范:
// 正确用法
const modules = import.meta.glob('./components/*.vue')

// 错误用法 - 不支持动态路径
// import.meta.glob 的参数都必须以字面量传入。
// 你不可以在其中使用变量或表达式。
const path = './components'
const modules = import.meta.glob(`${path}/*.vue`) // ❌
  1. 性能考虑:
// 异步加载 - 代码分割,按需加载
const modules = import.meta.glob('./modules/*.ts')

// 同步加载 - 所有模块打包在一起
const modules = import.meta.glob('./modules/*.ts', { eager: true })
  1. 类型支持:
// 定义模块类型
interface ModuleType {
  name: string;
  setup: () => void;
}

// 使用泛型指定类型
const modules = import.meta.glob<ModuleType>('./modules/*.ts', {
  eager: true
})

这些示例展示了 import.meta.glob 在不同场景下的应用,每个示例都包含了详细的注释和错误处理,可以作为实际项目中的参考。