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支持 支持
加载方案
预加载组件方案
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 };
}
<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>
骨架屏与智能预加载方案
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,
};
}
<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 的懒加载方案
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 };
}
<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 |
极致性能优化,滚动懒加载 | 需要处理观察者生命周期 | 长页面/内容密集型应用 |
位运算实现权限控制
上一篇