一个 .vue 文件从写出到在浏览器里跑起来,中间到底发生了什么
你每天写的 HelloWorld.vue,浏览器其实完全不认识。浏览器只认识 HTML、CSS、JS。所以 .vue 文件只是一个"半成品",需要被编译成浏览器认识的三件套。
| 区块 | 负责什么 | 编译后变成什么 |
|---|---|---|
| <template> | 页面长什么样 | render 函数(JS 代码) |
| <script> | 数据和逻辑 | JS 模块(export default 或 setup) |
| <style> | 样式 | CSS 文件(scoped 会加 data-v 属性) |
scoped,Vue 编译时会在 HTML 元素上加一个 data-v-abc123 属性,CSS 选择器也加上这个。所以 .hello 变成了 .hello[data-v-abc123]——只匹配当前组件,不会污染全局。
@vitejs/plugin-vue(或 webpack 的 vue-loader)会解析 .vue 文件,把它拆成三块分别编译。
你的 <h1>{{ message }}</h1> 会被编译成纯 JS 函数调用:
<template>、v-if、@click。编译器把它们翻译成纯 JS 的 createElementVNode() 调用——这才是浏览器认识的。
编译完只是得到了一堆散落的 JS 模块。接下来 Vite(生产环境用 Rollup)会把它们合并、压缩、拆分成最终的产物文件。
index-a1b2c3d4.js 中的 a1b2c3d4 是文件内容的 hash。内容变了 hash 就变。部署后浏览器发现有新 hash 就重新下载,没变就用缓存——这是缓存优化。
() => import('xxx.vue') 写法的,Vite/Webpack 都会自动拆成单独 chunk。用户第一次访问 /about 时浏览器才去下载——节省首屏加载时间。
打包后的 index.html 是浏览器入口——它只有一行 <div id="app"> 和几个 <script> 标签。浏览器加载它后会去下载 JS/CSS,然后 JS 接管页面。
id="app"。Vue 运行后把这块牌子摘掉,换成你写的整个组件树。从这一刻起,页面上的所有按钮、输入框都是 Vue 在管。
这是面试和调试中最常被问的:你的 Vue 页面在浏览器 F12 里能看到什么?
| F12 面板 | 看什么 | 对应 Vue 的什么 |
|---|---|---|
| Elements | 最终渲染的 HTML 结构 | render 函数执行后的真实 DOM。你能看到 data-v-xxx 属性(scoped 留下的痕迹) |
| Console | JS 报错和 console.log | Vue 警告(比如缺少 key)、你的 console.log 输出 |
| Sources | 加载的所有 JS/CSS 文件 | 能看到 index-[hash].js、vue-vendor-[hash].js 等打包后的文件。有 SourceMap 时还能看到 .vue 源码 |
| Network | 所有网络请求 | 能看到浏览器下载了哪些 chunk、各文件大小和加载时间。点 /about 时会看到 AboutView-[hash].js 被下载 |
| Application | LocalStorage/SessionStorage/Cookie | Vue 配合 pinia-persist 存的数据在这里 |
assets/ 下的压缩 JS(浏览器真正执行的),一层是 webpack:// 或 vite:// 下的原始 .vue 源码(通过 SourceMap 还原的)。你打断点调试用的是后者。
HelloWorld.vue 第 8 行。
| 开发 (npm run dev) | 生产 (npm run build) | |
|---|---|---|
| JS 是否压缩 | 不压缩(可读) | 压缩成一行(不可读) |
| SourceMap | 有(inline,速度快) | 默认不生成(泄露源码)。需要的话手动开 build.sourcemap: true |
| 文件名 | HelloWorld.vue(原名) | index-[hash].js(带hash) |
| 调试体验 | 直接看到 .vue 源码,打断点 | 没 SourceMap 只能看压缩 JS;有了才能映射回源码 |
index-a1b2c3.js:1:28451),完全不知道对应源码哪里。<script setup> 是 Vue 3 的语法糖,编译器会自动把它包装成 setup() 函数的返回值。你写的顶层变量自动变成组件的响应式数据,不用写 export default。
编译后两者结果几乎一样,但 <script setup> 编译出的代码更精简(编译器帮你做了优化),运行时性能更好。
因为 Vue 在运行时把 {{ message }} 替换成了实际的值(比如"你好Vue")。Elements 面板显示的是渲染后的最终 DOM,不是你的模板源码。要看模板源码,去 Sources 面板找 .vue 文件。
编译时 Vue 给当前组件的每个 DOM 元素加一个唯一属性 data-v-abc123de,同时把 CSS 选择器也加上这个属性。比如你的 .hello 变成了 .hello[data-v-abc123de],只匹配当前组件带这个属性的元素。
F12 的 Elements 面板里你能看到每个元素都带着 data-v-xxxxx 属性——那就是 scoped 的痕迹。
Vite 开发模式不打包!它利用了浏览器的 ES Module 原生支持(<script type="module">)。浏览器请求 HelloWorld.vue 时,Vite 服务器即时编译这一个文件返回,其他文件按需加载。
所以不管项目多大,开发时启动只要几百毫秒——因为不需要像 Webpack 那样把所有文件先打包成一个 bundle。
Vite/Webpack 默认会把小文件(通常 < 4KB)直接内联为 base64 编码嵌入 JS/CSS 里,避免多发一个 HTTP 请求。大文件还是保持独立文件。这叫 资源内联(asset inline)。
你在 CSS 里写 background: url(./logo.png),如果 logo.png 很小,打包后的 CSS 里就变成了 background: url(data:image/png;base64,iVBORw0KG...)。