vite 动态导入 element

使用element-plus + vite按需引入的方式,当首次启动vite服务时会对style进行依赖预构建,并且在切换不同路由时如果其他模块有使用到新的组件,页面会卡住直至dependencies optimized完成:

dependencies optimized

解决方案

开发时全量导入element-plus组件,打包时按需导入。

开发环境完整引入

针对开发环境,我们希望完整引入 element-plus 的所有组件。在 vite.config.js 中通过添加一个自定义插件来实现这个功能:

import process from 'node:process'
import { defineConfig } from 'vite'
import ElementPlus from 'unplugin-element-plus/vite'

export default defineConfig({
  // ...
  plugins: [
    // ...
    process.env.NODE_ENV === 'development'
      ? {
          name: 'vite:element-plus-auto-import-in-dev',
          transform(code, id) {
            if (/src\/main.ts$/.test(id)) {
              return {
                code: `
                  import ElementPlus from 'element-plus'
                  import 'element-plus/theme-chalk/src/index.scss'
                  ${code.split('const app = createApp(App)').join('const app = createApp(App);app.use(ElementPlus);')};
                `,
                map: null,
              }
            }
          },
        }
      : ElementPlus({ useSource: true }),
  ],
})
  • transform: 用于修改代码的钩子函数,返回值是一个对象,包含修改后的代码和 sourcemap。
  • process.env.NODE_ENV: 用于判断当前是否是开发环境。
  • src/main.ts: 用于匹配入口文件。
  • code: 用于修改入口文件的代码,将 ElementPlus 注入到 app 中。
  • map: 用于 sourcemap,这里不需要 sourcemap,所以设置为 null。
  • split: 此处是为了避免在APP.vue中使用了el-config-provider,导致页面比ElementPlus加载快,因此需要在mount之前引入ElementPlus

引入css的时候最好是用以上的方式,如果使用import 'element-plus/dist/index.css'的方式,可能会存在自定义样式覆盖不到的问题。

tsconfig.json中添加 element-plus 的类型声明:

{
  "compilerOptions": {
    "types": [
      // ...
      "element-plus/global"
    ]
  }
}

打包按需引入

在生产环境,我们希望按需引入 element-plus 的组件,这样可以减少打包后的体积,通过配置unplugin-vue-components插件来实现这个功能:

import process from 'node:process'
import { defineConfig } from 'vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    Components({
      dts: 'src/components.d.ts',
      include: [/\.vue$/, /\.vue\?vue/],
      exclude: ['src/components/**/components/**/*'],
      resolvers: process.env.NODE_ENV === 'production' ? ElementPlusResolver({ importStyle: 'sass' }) : undefined,
    }),
  ],
})

unplugin-vue-components的具体配置项可以查看GithubElementPlusResolver具体配置项可以查看官方文档或者Github

还需注意

因为这个方案是在开发环境进行全局引入,打包构建后还是保留按需引入。所以在使用 message 这类函数组件时,需要使用import { ElMessage } from 'element-plus'这种手动导入的方式。如果嫌麻烦则可以使用 unplugin-auto-import ,并且在 optimizeDeps 里对这类反馈组件的样式进行预构建:

import path from 'node:path'
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'

export default defineConfig({
  // ...
  optimizeDeps: {
    include: [
      '@element-plus/icons-vue',
      'element-plus/es',
      'element-plus/es/components/base/style/index',
      'element-plus/es/components/message/style/index',
      'element-plus/es/components/message-box/style/index',
      'element-plus/es/components/notification/style/index',
    ],
  },
  plugins: [
    // ...
    AutoImport({
      imports: [
        'vue',
        'pinia',
        '@vueuse/core',
        VueRouterAutoImports,
        {
          // add any other imports you were relying on
          'vue-router/auto': ['useLink'],
        },
      ],
      resolvers: [ElementPlusResolver({ importStyle: 'sass' })],
      dts: 'src/auto-imports.d.ts',
      dirs: [
        'src/composables',
        'src/events',
        'src/stores',
        'src/utils',
      ],
      vueTemplate: true,
    }),
  ],
})