cdn引入vue:不用构建工具也能优雅地上手 Vue
为什么还要用 CDN 引入 Vue
如今提到 Vue,大多数人的第一反应是 npm create vue@latest,然后打开 Vite 的开发服务器,在单文件组件里写 <template>、<script setup>。构建工具确实好用,但它同时带来了一整套前置条件:Node 环境、依赖安装、打包配置、产物体积与缓存策略。当目标只是"做一个能跑起来的小页面"时,这套流程的成本就显得偏高。
cdn引入vue 正好补上了这个缺口。一个 <script> 标签,浏览器直接把 Vue 的运行时加载进来,你可以在任何能放 HTML 的地方写响应式界面:老项目的某一个页面、后台系统里的自定义表单、给客户演示的原型、嵌入式设备的管理面板,甚至是一封可以本地打开的 HTML 文件。它不依赖构建、不依赖包管理器,复制粘贴就能运行,这种"零门槛"恰恰是 CDN 引入方式长期存在的理由。
不过,"简单"不等于"随便"。同一个 Vue 有全局构建、ESM 构建、完整版、运行时版、开发版、生产版之分;选错了轻则控制台报错,重则页面体积翻倍。下面从最基础的概念开始,把 cdn引入vue 这件事讲透。
先搞清楚:CDN 上的 Vue 到底有几个版本
打开任何一个公共 CDN 的 Vue 包目录,你会看到 dist 文件夹里躺着十几个文件。它们并不是随便起的名字,而是按两个维度组合出来的:是否包含模板编译器,以及模块格式。
完整版与运行时版
完整版(文件名里带 esm-browser 或 global 且不带 runtime)内置了模板编译器,可以直接解析 HTML 里的双大括号和 v- 指令。运行时版(带 runtime)不含编译器,只能接收已经编译好的渲染函数,体积大约小三分之一。
在 cdn引入vue 的场景里,绝大多数人写的是 DOM 内模板,也就是直接把 {{ msg }} 写在 HTML 里,这时候必须用完整版。如果你用的是渲染函数或者从别处拿到的编译结果,才需要考虑运行时版。新手最常见的报错"Component provided template option but runtime compilation is not supported",就是因为误引了运行时版却写了模板。
全局构建与 ESM 构建
全局构建(vue.global.js)执行后会挂一个 window.Vue 对象,用普通 <script> 引入即可,写法直观,兼容性最好。ESM 构建(vue.esm-browser.js)以 ES Module 形式导出,需要配合 type="module" 使用,好处是可以用 import { createApp } from 'vue' 这样的标准语法,并且能通过 import map 让多个模块共享同一个 Vue 实例。
还有一点容易被忽略:文件名里带 .prod 的是生产构建,去掉了开发期警告和调试信息;不带的是开发构建,会给出大量有用的提示。很多人上线后忘了换成 .prod,白白多下载几十 KB,也把内部实现细节暴露在了控制台里。
三种常见的引入写法
方式一:全局脚本,最省事
适合快速验证、单页面小工具、老项目局部改造。核心就是引入脚本 + 挂载应用:
<div id="app">
<p>{{ title }}</p>
<button @click="count++">点了 {{ count }} 次</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script>
const { createApp, ref } = Vue
createApp({
setup() {
const title = ref('cdn引入vue 示例')
const count = ref(0)
return { title, count }
}
}).mount('#app')
</script>
这段代码没有任何构建步骤,保存成 .html 双击就能跑。注意 mount 的目标元素必须已经存在于 DOM 中,所以脚本要放在它后面,或者包在 DOMContentLoaded 回调里。
方式二:ESM + import map,更接近工程写法
如果你希望代码看起来和构建项目一致,可以用 ES Module 加 import map。import map 的作用是告诉浏览器"import 'vue' 这个裸模块名该去哪里找文件":
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js"
}
}
</script>
<script type="module">
import { createApp, ref } from 'vue'
createApp({
setup() {
return { msg: ref('hello') }
}
}).mount('#app')
</script>
这种写法的额外好处是:当你要引入 Vue Router、Pinia 或者组件库时,它们内部对 vue 的依赖会解析到同一个地址,不会出现"两份 Vue 实例互相不认"的诡异问题。
方式三:动态加载,按需决定
有些页面只有在用户点击某个按钮后才需要交互能力,这时可以在事件回调里动态插入脚本,用 import() 加载 ESM 版本,或者用 Promise 包装脚本加载。这样首屏完全不受影响,算是一种轻量的懒加载思路。
版本锁定:别在生产环境用 latest
公共 CDN 上的 vue@3 是一个范围标签,会随着新版本发布而变动。开发阶段图方便可以这么写,但生产环境一旦上游发布了带破坏性变更的版本,你的页面可能在某天突然坏掉,而且很难第一时间联想到是 CDN 上依赖被替换了。
正确做法是固定到具体的小版本,例如 vue@3.4.21,让 URL 指向一个内容永不变动的文件。固定版本还有一个隐藏收益:浏览器缓存命中率更高,因为文件内容稳定,CDN 的边缘节点也可以长期缓存。
如果对安全比较敏感,可以再加一层子资源完整性校验(SRI)。做法是先在本地计算出目标文件的哈希,然后写成 integrity="sha384-..." 并配上 crossorigin="anonymous"。这样即使 CDN 被劫持或文件被篡改,浏览器也会拒绝执行。哈希值需要针对你锁定的那个具体文件去算,不能照抄别人的。
进阶:路由、状态与组件库怎么一起引入
真实项目很少只有一个 createApp。用 CDN 方式搭建稍大的应用时,通常会顺带引入官方生态:
- Vue Router 需要引入
vue-router.global.prod.js后从VueRouter取createRouter、createWebHashHistory。用 CDN 时建议选 hash 模式,因为 history 模式需要服务器把所有路径回退到同一个 HTML,静态托管的场景下往往做不到。 - Pinia 同理,引入后从
Pinia取createPinia,通过app.use(createPinia())注册。 - UI 组件库一般会同时提供脚本和样式两份文件,样式必须单独用
<link>引入,否则组件渲染出来是"裸"的。
引入顺序很重要:Vue 必须最先加载,插件在 Vue 之后、业务代码之前。用传统 <script> 时这是靠标签顺序保证的;如果你想并行下载又不想写 defer 之外的逻辑,那就统一加 defer,浏览器会按顺序执行而不会阻塞解析。
选择 CDN 服务商时,可以对比几家主流方案:unpkg 直接映射 npm 包结构,找文件最方便;jsDelivr 节点覆盖广、支持合并请求;cdnjs 老牌稳定、库收录严格;esm.sh 擅长把 CommonJS 包转成 ESM,适合混合依赖的场景。国内访问情况差异较大,正式项目最好配置一到两个备用地址。
几个必须知道的坑
DOM 内模板是大小写不敏感的
浏览器解析 HTML 时会把标签名统一转成小写,所以 <MyComponent /> 在 DOM 内模板里会变成 <mycomponent>,导致组件注册失败。解决方式是注册时就用 kebab-case,例如 app.component('my-component', ...),模板里写 <my-component>。这也是为什么 CDN 场景下很少见到 PascalCase 写法。
template 选项与 .vue 文件的区别
用 CDN 引入时没有单文件组件,组件的模板要么写在 template 选项的字符串里(注意用反引号包裹多行),要么写在页面上的 <template> 标签里再取 innerHTML。字符串模板的缺点是没有语法高亮和格式化,可读性明显下降,这也是当组件数量增长后应该迁移到构建方案的原因之一。
热更新与调试体验缺失
CDN 引入没有 HMR,改一行代码就得刷新页面;样式和逻辑混在同一个 HTML 里,稍微复杂一点就难以维护。开发期可以先用开发构建版本获得完整警告,上线前再切换到 .prod 版本,这个切换动作别漏掉。
多个 Vue 实例冲突
页面上如果同时存在两个版本的 Vue(比如某个老插件自己带了一个),会引发难以定位的响应式失效问题。排查时在控制台打印一下 Vue.version,确认只有一份。混合引入时优先用 ESM + import map 统一来源。
什么情况下不适合用 CDN 引入
说清楚适用边界比一味推荐更重要。以下场景建议直接上构建工具:
- 组件数量超过十来个,或者需要多人协作。没有单文件组件和类型检查,维护成本会指数级上升。
- 需要 TypeScript、单元测试、代码分割、Tree-shaking 优化体积。
- 内网或离线环境。公共 CDN 不可达时,页面会白屏,此时应把 Vue 文件下载到项目内自行托管。
- 对首屏性能极度敏感的产品。第三方域名意味着额外的 DNS 查询和连接建立,自托管配合长缓存往往更优。
反过来,如果你只是要在一个已有页面里加几个交互模块,或者需要交付一个"打开就能用"的演示文件,cdn引入vue 几乎是投入产出比最高的选择。
小结
cdn引入vue 的本质是用一个外链换取构建流程的省略。要做对这件事,记住四件事就够了:认准完整版还是运行时版,避免模板编译报错;生产环境锁定具体版本并切换到 .prod 文件,必要时加 SRI 校验;组件名统一用 kebab-case,插件按依赖顺序加载;清楚它的边界,组件变多、协作变复杂时就该迁移到工程化方案。
技术选型没有绝对的好坏,只有是否匹配当下的复杂度。先用 CDN 把想法跑通,再决定要不要引入构建链路,这种渐进式的路径往往比一上来就搭全套脚手架更务实。





