import.meta.glob

前置知识

  1. ES 模块系统 (ES Modules)
    1. import/export 语法是现代 JavaScript 的模块系统;
    2. 静态 import 必须在顶层声明,无法条件导入;
    3. 动态 import() 函数允许按需加载模块;
  2. import.meta 对象
    1. import.metaECMAScript 提供的宿主对象;
    2. 包含当前模块的元信息;
    3. 具体属性由宿主环境决定;

eager 选项配置

  1. 默认行为 (eager: false 或未设置)

    • 懒加载:每个文件会生成一个返回 Promise 的函数;
    • 代码分割:每个模块会生成独立的 chunk 文件;
    • 运行时加载:实际访问模块时才触发加载;
    const modules = import.meta.glob('./components/*.vue')
    // 输出结构:
    {
      './components/A.vue': () => Promise<Module>,
      './components/B.vue': () => Promise<Module>
    }
    
  2. 配置 eager: true

    • 立即导入:在应用初始化时同步导入所有匹配模块;
    • 无代码分割:所有模块会合并到当前 chunk;
    • 直接访问:模块已经是解析后的对象,无需调用函数;
    const modules = import.meta.glob('./components/*.vue', { eager: true })
    // 输出结构:
    {
      './components/A.vue': Module,
      './components/B.vue': Module
    }
    
  3. 关键区别对比

    特性 eager: false(默认) eager: true
    加载时机 按需懒加载 应用初始化时立即加载
    代码组织 每个模块生成独立 chunk 合并到当前 chunk
    返回值类型 () => Promise<Module> Module (已解析的模块对象)
    适用场景 需要代码分割的大中型应用 小型应用或必须同步加载的模块
    构建产物大小 主包较小,按需加载 主包较大
    Tree Shaking 支持 支持

加载方案

预加载组件方案

  1. 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 };
    }
    
  2. 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>
    

骨架屏与智能预加载方案

  1. 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,
      };
    }
    
  2. 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 的懒加载方案

  1. 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 };
    }
    
  2. 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 极致性能优化,滚动懒加载 需要处理观察者生命周期 长页面/内容密集型应用
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

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

粽子

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

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

了解更多

目录

  1. 1. import.meta.glob
    1. 1.1. 前置知识
    2. 1.2. eager 选项配置
  2. 2. 加载方案
    1. 2.1. 预加载组件方案
    2. 2.2. 骨架屏与智能预加载方案
    3. 2.3. 基于 Intersection Observer 的懒加载方案
  3. 3. 方案对比总结