普通视图

发现新文章,点击刷新页面。
今天 — 2025年10月17日首页

vue3中使用auto-import与cdn插件冲突问题

作者 jason_yang
2025年10月16日 18:34

背景

在开发vue3项目的时候,我们经常会用到unplugin-auto-import/vite来简化项目的import,同时为了提高加载的效率,我们会把固定的资源放在cdn加速访问。

在实际项目常用的是使用unplugin-auto-import/vite做自动导入,vite-plugin-cdn-import做cdn的引入

vite-plugin-cdn-import主要做两件事,

  • 在html把你插入一个script,地址是你配置的cdn的url。
  • 在通过别名映射,把编译时会把源码中的vue改成cdn的 window.Vue
  • 把cdn的资源排除在rollup打包信息里

问题

然后在实际应用中,我们在dev开发模式下,可以不import也能正式使用ref和onMounted,但是打包后就会无法正常使用

经过寻找,在vite-plugin-cdn-import 的 issues github.com/MMF-FE/vite… 找到这个解决方案,主要说的是插件执行顺序问题。

image.png

image.png

为了搞清楚这个问题,我们调试一下vite的配置

重现问题

新建一个vite项目并安装

pnpm i unplugin-auto-import vite-plugin-cdn-import -D

vite.config.ts

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import cdnImport from "vite-plugin-cdn-import";

const config = {
  base: `/`,
  plugins: [
    vue(),
    AutoImport({
      imports: ["vue"],
    }),
    cdnImport({
      modules: [
        {
          name: "vue",
          var: "Vue",
          path: "https://unpkg.com/vue@3.5.10/dist/vue.global.prod.js",
        },
      ],
    })
  ]
}; 
export default defineConfig(config)

src/components/HelloWorld.vue 文件

<script setup lang="ts">
// import { ref ,onMounted } from 'vue'
defineProps<{ msg: string }>()
onMounted(() => {
 count.value = 1000
 console.log('onMounted called')
})
const count = ref(0)
</script>

<template>
  <h1>{{ msg }}</h1>
  <div class="card">
    <button type="button" @click="count++">count is {{ count }}</button>
  </div>
</template>

image.png

验证开发模式

pnpm dev 

image.png 一切正常:自动把count 设置成1000 ,控制台输出onMounted called

验证打包模式

pnpm build
npx serve -s dist

打包自动在 index.html 输出 <script src="https://unpkg.com/vue@3.5.10/dist/vue.global.prod.js" crossorigin="anonymous"></script> image.png

并且文件大小只有12k 没有vue原文件信息

image.png

运行结果 image.png

但是运行结果不理想,count 没有设置成1000 ,控制台也没有输出onMounted called

打印配置

从issue上看说是plugin的enforce属性影响了。那我们来打印看看,我们在返回config的时候console一下

image.png

image.png

这是虽然看到plugins 是3个对象但是 第三个居然是数组,也就是vite插件支持对象或数组,没关系,我们提取出来再打印

    cdnImport({
      modules: [
        {
          name: "vue",
          var: "Vue",
          path: "https://unpkg.com/vue@3.5.10/dist/vue.global.prod.js",
        },
      ],
    })[0]

image.png

再次运行打印

image.png

其实不用打印,使用插件一样可以显示enforce的信息

pnpm i -D vite-plugin-inspect

加入inspect到plugins

/* eslint-disable */
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import externalGlobals from 'rollup-plugin-external-globals'
import cdnImport from "vite-plugin-cdn-import";
import inspect from 'vite-plugin-inspect'

const config = {
  base: `/`,
  plugins: [
    vue(),
    AutoImport({
      imports: ["vue"],
    }),
    cdnImport({
      modules: [
        {
          name: "vue",
          var: "Vue",
          path: "https://unpkg.com/vue@3.5.10/dist/vue.global.prod.js",
        },
      ],
    })[0] 
    , // 注意这里 他返回的是数组(vite数组一个都支持)所以要取第一个才好打印,需要访问第一个元素,
   inspect({}),
  ]
};

console.log(config)
export default defineConfig(config)

运行dev后,会多一个 http://localhost:5173/__inspect/

image.png

image.png

分析问题

好家伙 unplugin-auto-import 是post ,但vite-plugin-cdn-import 居然是 pre 。那会发生什么事情?

我们重新梳理一下 正常直觉我们都以为 plugin会按数组顺序依次执行

image.png

然而根据vite的规则,会在执行时先按enforce来排序, pre优先,post 最后,不设置则在中间,

最终执行会变成如下图

image.png

修复问题

根据issue的指引,我们只需要确保auto-import先执行就行了,所以调整cdn-import 的enforce为 post,当两个都是post的时候vite就会按数组顺序执行

image.png

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import externalGlobals from 'rollup-plugin-external-globals'
import cdnImport from "vite-plugin-cdn-import";
import inspect from 'vite-plugin-inspect'

const config = {
  base: `/`,
  plugins: [
    vue(),
    AutoImport({
      imports: ["vue"],
    }),
    {
      ...cdnImport({
      modules: [
        {
          name: "vue",
          var: "Vue",
          path: "https://unpkg.com/vue@3.5.10/dist/vue.global.prod.js",
        },
      ],
    })[0], 
    enforce: 'post' // 覆盖原来的enforce值
    }, 
   inspect({}),
  ]
};

console.log(config)
export default defineConfig(config)

我们通过解构重新给 cdnImport 设置 enforce: 'post'

image.png 结果报了另一一个错误,看来还是有兼容bug,不折腾找找别的方案。

换个库试试吧

其实cdn-import 无非就是插html点js 和 把代码中的vue改成映射后window.Vue,并且把cdn资源排除在roll打包信息里。 那我们用其他库也能实现,在上面issue有另外一个大神给出其他方案

image.png

在这位大侠的源码 确实找打了解决方案 ,就是用rollup-plugin-external-globals来处理cdn 的命名,并且自己在html页面输出对应的script的标签

vite.config.js

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import externalGlobals from 'rollup-plugin-external-globals'
 

const config = {
  base: `/`,
  plugins: [ 
    vue(),
    {
      ...AutoImport({
      imports: ["vue"],
    })
    }, 
      {
      ...externalGlobals({
        vue: 'Vue', 
      }),
      enforce: 'post', // 注意这里也要加上post ,不然上面AutoImport 又会后执行
    },
  ], 
    build: { 
    rollupOptions: {   
      external: ['vue'], // 告诉 Rollup 不要将 'vue' 打包进输出文件
      plugins: [
        externalGlobals({
          vue: 'Vue',              // import vue → window.Vue 
        })
      ], 
    }
  }
};

console.log(config)
export default defineConfig(config)

注意 externalGlobals 也是要加 post 不然上面AutoImport 又会后执行 image.png

告诉rollup 不要把vue打进来 image.png

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue + TS</title>
     <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

在html 加入cdn,当然也可以改成动态配置,输出变量循环 image.png

最终运行 image.png nice

image.png 并且资源请求也符合预期,项目js 1.6k,vue.js 是另外下载

进一步分析

我们改造一下配置,让所有build的代码正常输出,通过对比两次差异

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import externalGlobals from 'rollup-plugin-external-globals'
import cdnImport from "vite-plugin-cdn-import";
import inspect from 'vite-plugin-inspect'

const config = {
  base: `/`,
  plugins: [
    vue(),
    AutoImport({
      imports: ["vue"],
    }),
   {
      ...cdnImport({
      modules: [
        {
          name: "vue",
          var: "Vue",
          path: "https://unpkg.com/vue@3.5.10/dist/vue.global.prod.js",
        },
      ],
    }), 
    enforce: 'post'
    }, // 注意这里 他返回的是数组(vite数组一个都支持)所以要取第一个才好打印,需要访问第一个元素,
   inspect({}),
  ],
  build: {
    minify: false, // 禁用代码压缩混淆
    sourcemap: true, // 生成 sourcemap 便于调试
    rollupOptions: {  
      treeshake: false, // 禁用 tree shaking
      output: {
        compact: false, // 不压缩代码
      },
    }
  } 
};

console.log(config)
export default defineConfig(config)

左边是有问题的, 右边是正常的

image.png

image.png 在生成的代码里,我们看到主要差异就是,

左边 错误的

居然还保留了import { onMounted, ref } from "vue"; 并且使用onMounted 和 ref 是直接使用,所以证明了auto-import后置执行了,

右边 正确的

使用vue的库是通过 Vue.onMounted访问,并且没有 import 的代码。

扩展

其实还有一种解决方案,如果你的项目域名已经是cdn指向的前提下。可以使用vite的rollupOptions 的manualChunks,单独把vue资源报分离出来。由于vue的版本如果没有变化,每次hash值是一样的,这样生成的vue.js文件每次都是一样的,也能达到cdn加速效果。但是就不太适合分发给其他项目。

当然本地也要先安装vue

pnpm i vue -D
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import cdnImport from "vite-plugin-cdn-import";

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    // AutoImport({
    //   imports: ["vue"],
    // }),

    cdnImport({
      modules: [
        {
          name: "vue",
          var: "Vue",
          path: "https://unpkg.com/vue@3.5.10/dist/vue.global.prod.js",
        },
      ],
    }),
  ],
  base: `/`,
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes("node_modules")) {
            // 将大的库单独打包 
            if (id.includes("element-plus")) return "vendor-element";

            // 其他第三方库按类别分组
            if (id.includes("vue")) return "vendor-vue"; 
            console.log(id)
            return "vendor";
          }
        },
      },
    },
  },
});

参考代码

github.com/mjsong07/vu…

❌
❌