{T}

Element-Plus 样式外部化与 CDN 样式注入

概述

打包优化不能只盯 JS,CSS 也可能是体积大户。本节发现 index.css 比主入口 JS 还大,主体来自 element-plus 样式;接着说明 vite-plugin-cdn2 只 external JS、不会自动处理主入口手动导入的 CSS,于是用 import.meta.env.MODE 配合动态 import() 按环境拆分入口样式,并在生产环境 HTML 中注入对应的 CDN 样式,最终把入口资源导入策略与 CDN 注入策略连成闭环。

学习目标

  • 意识到 CSS 体积同样是构建优化重点,能从 dist/assets 判断样式来源。
  • 理解 vite-plugin-cdn2 只解决 JS external,主入口静态样式导入仍需单独处理。
  • 能用 import.meta.env.MODE + 动态 import() 实现“开发导入、生产不打包”的样式策略。
  • 会为 CDN external 模块补充 css 配置,在生成 HTML 注入对应样式链接。
  • 养成“先手动验证 CDN 资源可访问,再写进配置”的习惯,并验证体积闭环。

一、CSS 也可能是体积大户

回头看构建日志时,常发现一个关键现象:主入口 JS 只有两百多 KB,但 index.css 却有三百多 KB。前面的优化已经作用在 vueelement-plusecharts 这些 JS 依赖上,但样式层还没被真正处理。

打开 dist/assets 看打包后的 CSS 内容,里面大部分其实是 element-plus 的样式。这说明:external JS 不等于 external CSS,包体积优化必须同时看脚本和样式。UI 组件库项目里,CSS 经常是隐藏的大头。

二、vite-plugin-cdn2 不会自动处理主入口样式

vite-plugin-cdn2 解决的是 JS external,但 element-plus 的样式不会自动一起被排除。根本原因在主入口还有显式样式导入:

ts
// main.ts
import "element-plus/dist/index.css"
import "element-plus/theme-chalk/dark/css-vars.css"

对 Vite 来说,这两行是明确的 CSS 依赖,只要还在静态顶层 import,构建就一定会把它们打进产物。所以这一节的优化本质是主动把样式导入逻辑按环境拆开,而不是指望插件更聪明。

三、入口样式按环境拆分:动态 import()

要同时满足两个目标——开发环境正常加载样式、生产环境不再打包这两份 CSS——继续写静态 import 没法做环境差异。课程改用 import.meta.env.MODE 配合动态 import()

ts
// main.ts
if (import.meta.env.MODE !== "production") {
  await import("element-plus/dist/index.css")
  await import("element-plus/theme-chalk/dark/css-vars.css")
}

这里有个语法点:静态 import 只能写在模块顶层,不能直接放进 if 里,所以必须改成动态 import()MODE !== "production" 这类判断适合处理“开发时要、生产时不要”的资源。

四、生产环境补 CDN 样式注入

把 CSS 从构建链里拿掉后,立刻面临一个必然问题:生产模式页面还得有样式。既然本地不再打包,就必须在 HTML 里补上对应的 CDN CSS。课程沿用 vite-plugin-cdn2,但这次给 element-plus 额外挂 css,生成的 index.html 里就会自动出现 <link> 标签。

ts
cdn({
  modules: [
    {
      name: "element-plus",
      var: "ElementPlus",
      path: "dist/index.full.min.js",
      css: [
        "https://unpkg.com/element-plus@2.4.2/dist/index.css",
        "https://unpkg.com/element-plus@2.4.2/theme-chalk/dark/css-vars.css",
      ],
    },
  ],
})

JS external 后,样式一定要有对应补偿方案,生成的 index.html 是最直接的验证位置。

五、先验证 CDN 地址再写配置

通过 CDN 引样式时,最稳的做法不是盲写路径,而是先在对应 CDN 站点手动验证资源地址能否访问。课程用的是 unpkg.com,按“包名 + 版本号 + 文件路径”拼路径,先在浏览器访问 dist/index.csstheme-chalk/dark/css-vars.css 确认真实存在,再放进配置。CDN 资源一旦路径写错,生产环境就会直接丢样式。

text
UNPKG URL 规则:
  https://unpkg.com/{package}@{version}/{file}

六、体积闭环与策略闭环

做完这套修改后重新构建,index.css 从三百多 KB 降到二十多 KB——这不是简单的数字变小,而是说明 element-plus 样式主体已经不再留在本地产物里。验证要三步走:构建日志里的 CSS 体积下降、生成的 index.html 已注入 CDN 样式、preview 页面样式正常。

这一节真正完成的是把两件配套动作连成闭环:开发环境主入口自己导入样式,生产环境主入口不再打包、转由 CDN 在 HTML 层注入。“不打包”和“运行时仍然可用”必须同时成立,优化才算完整。

七、把样式体积排查扩展到其他大样式来源

这一节验证时看到的具体数字是:优化前 index.css 约 300KB+,优化后降到 26KB 左右。而优化前主入口 JS 只有 231KB,样式反而比脚本还大——这个反差本身就提醒我们,UI 组件库项目里 CSS 经常是隐藏大头,而 element-plus 只是其中一个来源。

值得顺手把下面几类样式也排查一遍:

  • 图表库皮肤样式(如 echarts 主题、vue-echarts 附带的 CSS);
  • 全局重置 / 第三方基础样式(normalize、reset 等);
  • 自定义主题文件与覆盖样式。

排查手法和本节一致:先翻 dist/assets 看 CSS 真实占比,再决定哪些本地打包、哪些按环境或走 CDN。给 vite-plugin-cdn2 的 module 增加 css 时,也可以靠对象的扩展运算把样式数组并入既有配置,不必整段重写。只有把“JS 体积分布”和“CSS 体积分布”一起梳理,才算真正掌握项目的静态资源结构。

把这一节的入口样式策略和 CDN 注入策略合到一起看,最清晰的形式是两段配置各司其职:主入口只在开发态动态导入,vite.config.ts 里给 element-plus 同时挂 css,由插件在生产 HTML 注入对应的 <link>

ts
// main.ts:开发环境本地导入,生产环境不打包
if (import.meta.env.MODE !== "production") {
  await import("element-plus/dist/index.css")
  await import("element-plus/theme-chalk/dark/css-vars.css")
}
ts
// vite.config.ts:生产环境改用 CDN 样式补偿
cdn({
  modules: [
    {
      name: "element-plus",
      var: "ElementPlus",
      path: "dist/index.full.min.js",
      css: [
        "https://unpkg.com/element-plus@2.4.2/dist/index.css",
        "https://unpkg.com/element-plus@2.4.2/theme-chalk/dark/css-vars.css",
      ],
    },
  ],
})

两段各自负责“本地不打包”和“运行时仍可用”,合起来才是这一节真正的闭环。


常见问题

问题原因解决方案
JS 已 external 但 index.css 还是特别大element-plus 样式仍在主入口静态导入改成开发环境动态导入,生产环境不再打包
删掉样式 import 后开发环境没样式了本地开发时也把样式移除了import.meta.env.MODE !== "production" 条件动态导入
生产环境包小了但页面样式全丢没给 CDN external 补上对应 CSS 链接vite-plugin-cdn2 里为模块增加 css 配置
CDN 样式地址写了但不生效路径写错或版本号不匹配先手动打开 CDN 地址验证资源存在,再写入配置
动态 import 写在 if 里报语法错用了静态 import 而非动态 import()改成 await import("...")
构建后 CSS 体积没降生产构建里还有别的入口继续拉样式检查主入口、插件链和是否有重复样式导入

延伸阅读