import.meta.glob
前置知识
- ES 模块系统 (ES Modules)
- import/export 语法是现代 JavaScript 的模块系统;
- 静态 import 必须在顶层声明,无法条件导入;
- 动态 import() 函数允许按需加载模块;
- import.meta 对象
- import.meta 是 ECMAScript 提供的宿主对象;
- 包含当前模块的元信息;
- 具体属性由宿主环境决定;
eager 选项配置
-
默认行为 (eager: false 或未设置)
- 懒加载:每个文件会生成一个返回 Promise 的函数;
- 代码分割:每个模块会生成独立的 chunk 文件;
- 运行时加载:实际访问模块时才触发加载;
const modules = import.meta.glob('./components/*.vue') // 输出结构: { './components/A.vue': () => Promise<Module>, './components/B.vue': () => Promise<Module> }
-
配置 eager: true 时
- 立即导入:在应用初始化时同步导入所有匹配模块;
- 无代码分割:所有模块会合并到当前 chunk;
- 直接访问:模块已经是解析后的对象,无需调用函数;
const modules = import.meta.glob('./components/*.vue', { eager: true }) // 输出结构: { './components/A.vue': Module, './components/B.vue': Module }
-
关键区别对比
特性 eager: false(默认) eager: true 加载时机
按需懒加载 应用初始化时立即加载 代码组织
每个模块生成独立 chunk 合并到当前 chunk 返回值类型
() => Promise<Module> Module (已解析的模块对象) 适用场景
需要代码分割的大中型应用 小型应用或必须同步加载的模块 构建产物大小
主包较小,按需加载 主包较大 Tree Shaking
支持 支持
加载方案
预加载组件方案
-
usePreloadComponents.ts
import { defineAsyncComponent, shallowRef, type Component } from "vue"; interface UsePreloadComponentsReturn { components: Record<string, Component>; currentComponent: Component | null; loadComponent: (name: string) => void; } export function usePreloadComponents(): UsePreloadComponentsReturn { // 预加载所有组件(eager: true) const modules: Record<string, Component> = import.meta.glob("./components/*.vue", { eager: true, import: "default", }); const components = Object.keys(modules).reduce((acc, path) => { const name = path.split("/").pop()?.replace(/\.vue$/, "") || ""; acc[name] = modules[path]; return acc; }, {} as Record<string, Component>); const currentComponent = shallowRef<Component | null>(null); const loadComponent = (name: string) => { currentComponent.value = components[name] || null; }; return { components, currentComponent, loadComponent }; }
-
test.vue
<template> <div> <button v-for="name in Object.keys(components)" :key="name" @click="loadComponent(name)"> {{ name }} </button> <component :is="currentComponent" /> </div> </template> <script setup lang="ts"> import { usePreloadComponents } from './usePreloadComponents' const { components, currentComponent, loadComponent } = usePreloadComponents(); // 默认加载第一个组件 const firstComponent = Object.keys(components)[0]; if (firstComponent) loadComponent(firstComponent); </script>
骨架屏与智能预加载方案
-
useSmartLoading.ts
import { defineAsyncComponent, shallowRef, ref, type Component, type AsyncComponentLoader } from "vue"; interface UseSmartLoadingReturn { components: Record<string, Component>; currentComponent: Component | null; isLoading: Ref<boolean>; loadComponent: (name: string) => Promise<void>; preloadVisible: (names: string[]) => void; } export function useSmartLoading(): UseSmartLoadingReturn { const isLoading = ref(false); const componentPromises = new Map<string, Promise<Component>>(); const componentLoaders: Record<string, AsyncComponentLoader> = import.meta.glob("./components/*.vue", { eager: false, // 懒加载 }); const components = Object.keys(componentLoaders).reduce((acc, path) => { const name = path.split("/").pop()?.replace(/\.vue$/, "") || ""; const loader = componentLoaders[path]; componentPromises.set( name, loader().then((m) => m.default) ); acc[name] = defineAsyncComponent({ loader: () => componentPromises.get(name)!, loadingComponent: { template: '<div class="skeleton">Loading...</div>', }, }); return acc; }, {} as Record<string, Component>); const currentComponent = shallowRef<Component | null>(null); // 智能预加载 const preloadVisible = (names: string[]) => { names.forEach((name) => { if (componentPromises.has(name)) { componentPromises.get(name)!.catch(() => {}); } }); }; const loadComponent = async (name: string) => { isLoading.value = true; try { if (componentPromises.has(name)) { await componentPromises.get(name); currentComponent.value = components[name]; } } finally { isLoading.value = false; } }; return { components, currentComponent, isLoading, loadComponent, preloadVisible, }; }
-
test.vue
<template> <div> <button v-for="name in Object.keys(components)" :key="name" @click="loadComponent(name)" :disabled="isLoading"> {{ name }} </button> <component :is="currentComponent" /> </div> </template> <script setup lang="ts"> import { onMounted } from 'vue' import { useSmartLoading } from './useDynamicComponents' const { components, currentComponent, isLoading, loadComponent, preloadVisible } = useSmartLoading() // 预加载首屏组件 onMounted(() => { preloadVisible(['Header', 'MainContent']) loadComponent('Header') }) </script> <style> .skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; height: 200px; } @keyframes shimmer { to { background-position: -200% 0; } } </style>
基于 Intersection Observer 的懒加载方案
-
useLazyComponents.ts
import { defineAsyncComponent, shallowRef, type Component, type AsyncComponentLoader } from "vue"; interface UseLazyComponentsReturn { components: Record<string, Component>; currentComponent: Component | null; registerObserver: (el: HTMLElement, name: string) => void; } export function useLazyComponents(): UseLazyComponentsReturn { const componentLoaders: Record<string, AsyncComponentLoader> = import.meta.glob("./components/*.vue", { eager: false, // 懒加载 }); const observers = new Map<string, IntersectionObserver>(); const components = Object.keys(componentLoaders).reduce((acc, path) => { const name = path.split("/").pop()?.replace(/\.vue$/, "") || ""; acc[name] = defineAsyncComponent({ loader: componentLoaders[path], loadingComponent: { template: '<div class="lazy-placeholder">Loading...</div>', }, }); return acc; }, {} as Record<string, Component>); const currentComponent = shallowRef<Component | null>(null); const registerObserver = (el: HTMLElement, name: string) => { if (observers.has(name)) return; const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { currentComponent.value = components[name]; observer.unobserve(entry.target); observers.delete(name); } }); }, { threshold: 0.1 } ); observer.observe(el); observers.set(name, observer); }; return { components, currentComponent, registerObserver }; }
-
test.vue
<template> <div> <button v-for="name in Object.keys(components)" :key="name" @click="currentComponent = components[name]"> {{ name }} </button> <div style="height: 100%; background: pink;"> aaa </div> <div ref="container" class="component-container"> <component :is="currentComponent" /> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' import { useLazyComponents } from './useDynamicComponents' const container = ref<HTMLElement | null>(null) const { components, currentComponent, registerObserver } = useLazyComponents() onMounted(() => { if (container.value) { registerObserver(container.value, 'overviewInfo'); } }) </script> <style> .lazy-placeholder { height: 300px; display: flex; align-items: center; justify-content: center; background: #f5f5f5; } .component-container { min-height: 100vh; } </style>
方案对比总结
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
预加载关键组件 |
无加载延迟,实现简单 | 初始资源占用大 | 小型应用,组件少且固定 |
骨架屏与智能预加载 |
平衡性能与体验,按需加载 | 实现较复杂 | 中大型应用,需要良好用户体验 |
Intersection Observer |
极致性能优化,滚动懒加载 | 需要处理观察者生命周期 | 长页面/内容密集型应用 |
位运算实现权限控制
上一篇