1397 字
7 分钟
Vue 3 项目的目录结构与启动流程

目录结构#

my-vue-app/
├── public/ 静态资源,直接复制到 dist,不做转换
├── src/
│ ├── assets/ 被引用的资源,构建时加哈希
│ ├── components/ 公共组件
│ ├── App.vue 根组件
│ └── main.ts 应用入口
├── index.html 页面入口
├── vite.config.ts Vite 配置文件
└── package.json

TypeScript 模板用 vite.config.ts,JavaScript 模板用 vite.config.js,其余部分一致。

public 与 assets 的区别#

public/ 里的文件在开发环境按根路径访问,构建时原样复制到 dist,不经过 Vite 转换。判断一个文件放哪里,看它需不需要用 import 引入:需要引入的放 src/assets,不需要的放 public。

src/assets 里的资源由 Vite 处理。被引用的资源会带上内容哈希写进文件名;小于 assetsInlineLimit 的资源会内联成 base64。

index.html 是入口#

Vite 把 index.html 当成源码,它也是模块图的一部分。Vite 会解析其中的 <script type="module" src="/src/main.ts">,从这个标签出发找到整个依赖图。

Webpack 和 Rollup 的入口是 JS 文件,Vite 的入口是 HTML。

这个文件放在项目根目录,不在 src/ 里。多页应用可以配多个 .html 入口,每个页面各有自己的模块图;应用如果不用 index.html 当入口,可以在构建配置的 input 里指定其他入口文件。

单文件组件的三类顶层块#

每个 .vue 文件由三类顶层语言块组成:<template>、<script> 和 <style>。除此之外还能写自定义块,块名自己定,交给相应的工具处理。

<template> 最多一个,写模板,最终编译成 render 函数。

<script> 最多一个,导出组件配置对象。它有一个变体 <script setup>,同样是顶层块,同样最多一个。

Vue 文档把 <script setup> 列为推荐写法:顶层变量和函数自动暴露给模板,可以理解成组件 setup() 的入口,编译期能做的优化也更多。

<style> 可以写多个。加 scoped 时样式只作用于当前组件,加 module 时按 CSS Modules 处理,使用时拿到一个样式对象。

npm run dev 做了什么#

npm 读取 package.json 的 scripts,找到 "dev": "vite",然后执行 vite 对应的可执行文件,启动开发服务器。

npm 会把依赖包的可执行文件链接到 ./node_modules/.bin,并在运行 scripts 时把这个目录加进 PATH。所以在脚本里直接写 vite,找到的就是项目本地安装的版本。

在命令行里直接敲 vite 走的是系统 PATH,找不到项目里的那一份。这时可以用 npx vite 或 npm exec vite,它们会在本地依赖里查找并执行。

./node_modules/.bin 里的可执行文件在 POSIX 系统上是符号链接。Windows 上会额外生成一个 cmd 文件,可以直接用名字调用,也可以用名字加 .cmd,在 PowerShell 里同样可用。

开发服务器的请求流程#

以 import { createApp } from 'vue' 为例,一次页面加载大致经过下面几步。

浏览器访问 http://localhost:5173,服务器返回 index.html。HTML 里有这一行,浏览器读到后会去请求 main.ts。

index.html
<script type="module" src="/src/main.ts"></script>

Vite 拦截这个请求并按需编译:TS 转成 JS,Vue 模板转成渲染函数。main.ts 里的 import { createApp } from 'vue' 是裸模块名,浏览器没法直接解析,于是再发一次请求。

这次 Vite 把裸模块重写成可以访问的 URL 返回,例如 /node_modules/.vite/deps/vue.js?v=f3sf2ebd。依赖预构建的产物默认缓存在 node_modules/.vite。

这些响应带 Cache-Control: max-age=31536000,immutable,缓存靠 URL 上的版本查询参数失效。依赖变了,参数就变,浏览器拿到的是新文件。

开发服务器默认监听 5173 端口,端口被占用时自动换下一个空闲端口。

浏览器主动请求每一个依赖,服务器按需编译,而不是一次打包全部文件。源码不预先打包,冷启动只要处理入口和少量依赖,不必等整个应用编译完。

浏览器怎么存放这些模块#

浏览器拿到 ESM 模块后加载到内存里执行,不写到硬盘。一次模块加载在规范里的过程是:下载代码,解析成抽象语法树,编译成字节码,实例化模块环境记录并绑定导出,最后执行代码。

同一个模块只加载一次,之后再 import 直接读缓存。

硬盘上的缓存分两类。HTTP 缓存由响应头控制,Service Worker 缓存由代码自己控制,常见于 PWA。

HTTP 缓存#

强缓存用 Cache-Control: max-age=N 表示响应在 N 秒内保持新鲜、可以复用,浏览器具体存在哪里由实现决定。max-age 只管新鲜期,不管要不要校验:用户刷新页面时,浏览器仍可能发一次条件请求。

协商缓存是另一种做法:浏览器带着标识发请求问服务器文件变了吗,没变就返回 304 Not Modified,浏览器继续用本地副本。

Service Worker 缓存#

Service Worker 是可编程的缓存层,缓存哪些文件、什么时候更新都由代码决定。PWA 常用它做离线访问,它和 HTTP 缓存是两套机制,可以叠加使用。

第 18 篇,共 19 篇本文属于系列

Vue 3 项目的目录结构与启动流程
https://www.choria.top/posts/vue3-project-file/
作者
乔瑞雅·克林威尔
发布于
2026-04-08
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 214 天

部分内容可能已过时