前端项目配置文件解析

98 阅读7分钟

总览

项目根目录下躺着几个配置文件,读它们的东西其实不是同一个:Vite 读 vite.config.js,编辑器读 jsconfig.json,npm 读 package.json 和 package-lock.json。

文件谁读它管什么什么时候生效
vite.config.jsVite代码怎么编译、怎么打包dev / build
jsconfig.json编辑器@ 指向哪、有没有补全和跳转写代码时
package.jsonnpm、Node项目身份、脚本、依赖范围、模块规范装包、跑脚本、加载 .js
package-lock.jsonnpm每个依赖最终落在哪个版本装包

vite.config.js

当以命令行方式运行 vite 时,Vite 会自动解析 项目根目录 下名为 vite.config.js 的配置文件。

它是 Vite 的唯一配置文件,Node 进程在启动 dev / 执行 build 时读取,控制 "代码怎么被编译、怎么打包"。

最基础的配置文件是这样的:

export default {
  // 配置选项
}

案例

export default defineConfig({
  server: {
    host: true,
    port: 5173,
    proxy: {
      '/api': 'http://localhost:3000',
    },
  },
  base: '/blog/',
  plugins: [
    vue(),
    //&&表示左边为真才执行右边;左边为假直接返回 `false`
    process.env.NODE_ENV !== 'production' && vueDevTools(),
    AutoImport({ ... }),
    Components({ ... }),
    { name: 'katex-woff2-only', ... },
    blogsPlugin(),
  ],
  resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
  build: { rolldownOptions: { output: { manualChunks(...) {} } } },
})

defineConfig:在 TS 项目里提供类型检查和补全,在js项目里面没用

vite.config.js常用参数详解

server

server 是 Vite 的开发服务器配置, 只管 npm run dev,对 npm run build 的生产产物没有任何影响。

proxy用于处理跨域问题。
proxy: { '/api': 'http://localhost:3000' }:凡是前端发出以 /api 开头的请求,Vite 都替它转发到 localhost:3000
host: true:开启局域网访问,这样其他设备也可以通过ip来浏览当前开发中的网站
open: true:启动时自动打开浏览器
port: 5173:指定端口;被占用时 Vite 会自动换成 5174、5175…

base

base 决定打包产物里资源引用的 URL 前缀

base: '/blog/' 部署在 xxx.com/blog/ 下
不配默认是 /(根路径)
只有资源请求的前缀和文件在服务器上的实际位置对不上时才需要写 base。

打包产物被读取 = 浏览器按 index.html 里的 src(base 决定)发请求 → Nginx 拿 root 把 URL 翻译成磁盘路径(root 决定)→ 找到文件返回。root = html/ 的意思是 "URL 的 / 对应磁盘的 html/",所有请求都拼在它后面找文件。base 和 root 就是" 请求方写什么地址 "与" 服务器去哪找 " 这对必须对齐的搭档。

例:nginx里面 root = html/,而文件在 html/blog/。此时页面里 /assets/x.js 会去请求 html/assets/导致找不到文件。此时配 base: '/blog/',来让页面请求正确路径。

直接把 nginx root 指向 html/blog/,base 就不需要改了

plugins

Vite的核心机制—— 所有 "处理文件" 的能力都来自插件

这里我们以 plugins: [ vue() ] 来讲解一下插件配置的原理

vue() 是一个工厂函数调用:执行后返回一个插件对象。这个对象包含 name 以及钩子函数(resolveId / load / transform 等),还可能带 api。plugins 数组里的每一项,最终都是一个对象(或 false)——vue 只是 "能产生插件对象的函数"。

Vite 的机制非常简单粗暴: Vite 按钩子名分组执行:所有插件的 resolveId 先跑完,再跑所有插件的 load,最后跑所有插件的 transform(同一钩子内部按 plugins 数组的顺序)。对每个被 import 的模块,这组流程都会走一遍。

resolve

resolve 是 Vite 的 "模块解析总配置",通俗来说就是:负责把import { useUserStore } from '@/store/user'里面的@/store/user翻译成磁盘文件路径

常规用法就是alias,

alias: {
  '@': fileURLToPath(new URL('./src', import.meta.url)),
},

表示用@指代./src

"以配置文件为基准点,算出 src 的绝对路径" —— 不管你在哪启动项目,这个路径都稳定。
import.meta.url —— 当前文件自己的 "地址"
new URL('./src', import.meta.url) —— 以配置文件为原点,找 ./src
fileURLToPath(...) —— URL 转成 Windows 路径

build

build 是 Vite 配置里的生产构建块—— 只在 npm run build 时生效,npm run dev 完全不经过它

build.rolldownOptions.output.manualChunks = 给打包器指定的 "分组名单":哪些第三方大库单独打包成一个文件,让它们永远命中浏览器缓存。它只在 npm run build 时起作用,是发布后加载性能的优化手段

jsconfig.json

jsconfig.json 是给编辑器(VS Code)看的配置文件 —— 它不参与打包,只管你的 "编辑器体验":代码提示、跳转、类型检查

TS项目里面是tsconfig.json

案例

{
  "compilerOptions": { "paths": { "@/*": ["./src/*"] } },
  "exclude": ["node_modules", "dist"]
}

"compilerOptions": { "paths": { "@/*": ["./src/*"] } }:不配置这个就不能Ctrl+点击 @ 跳转到指定文件

vite.config.js里面的才是真正决定代码能不能跑的,jsconfig.json只是告诉编辑器@是什么路径

"exclude": ["node_modules", "dist"]:排除 node_modules 和 dist,避免语言服务卡顿和把第三方 / 构建产物也纳入检查

package.json

package.json 不是 Vite 的配置文件,是 npm 的项目清单:项目叫什么、有哪些脚本、依赖哪些包、.js 按哪套模块规范解析。

跑 npm run dev 的时候,是 npm 先查了这里的 scripts,才把 Vite 调起来的。

scripts

npm run dev 自己不干活,它去 scripts 里查出 "dev": "vite",把 node_modules/.bin 加进 PATH,再执行这条命令。真正跑起来的是 Vite,npm 只负责查表。

scripts 不是必需的:删掉 "dev": "vite",项目照样能跑,直接 npx vite(等于执行 node_modules/.bin/vite)。只是 npm run dev 这个入口就没了 —— 它会直接报 Missing script: "dev" 然后退出,连 Vite 都还没碰到。

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview",
  "lint": "eslint ."
}

前三行对应 Vite 的三个命令:启动开发服务器、构建生产包、本地预览产物。Vite 并不读脚本名,它只认冒号右边那条命令 —— dev / build / preview 是脚手架和部署平台的惯例(不少平台会默认执行 npm run build),换成别的名字照样能跑,只是得自己记住。

其余条目与 Vite 无关,是这个项目的自定义任务。scripts 本质上就是一张「任务名 → 命令」的映射表,放什么命令都行。

type

type决定.js文件按哪套模块规范解析:
写module按 ESM规范(import / export);
写commonjs或不写按 CommonJS规范(require / module.exports)。

后缀可以覆盖这个默认值,.cjs后缀的文件会强制按CommonJS解析,.mjs后缀的文件会强制按ESM解析。

注意type是包级别的:node_modules 里的依赖看各自的 package.json.

private

"private": true 拦截 npm publish命令,避免将自己的项目打包上传到 npm registry

依赖

  • dependencies:页面跑起来真的要用,如vue、element-plus
  • devDependencies:只在开发 / 构建时用,如vite、@vitejs/plugin-vue

package.json里默认写的是范围,不是精确版本 ^ 的规则是「最左边那个非零位保持不变」 ^8.1.5 表示 >=8.1.5 且 <9.0.0 ^0.18.1 表示 >=0.18.1 且 <0.19.0(0.x 阶段,第二位一变就算大版本) 真正装的是哪个版本,由 package-lock.json 决定。

package-lock.json

package-lock.json 是 npm 生成的依赖锁定文件。package.json 写范围,它写落地结果:每个包的确切版本、从哪下载、文件哈希,还有整棵树长什么样。

条目结构

"node_modules/vite": {
  "version": "8.3.5",
  "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz",
  "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==",
  "dev": true
}

version:最终选定的版本号
resolved:tarball 的下载地址,换过镜像这里就是镜像地址
integrity:文件哈希,下载后校验;同时是 npm 缓存找文件的钥匙(哈希相同 = 同一份文件)
dev: true:只供开发 / 构建使用,npm ci --omit=dev 会跳过

lockfileVersion

lockfileVersion 是这个 JSON 文件本身的格式版本,不是依赖的版本。它回答的是「npm 该按哪套规则来读这个文件」。

npm 9及以上 是 "lockfileVersion": 3

提交

package-lock.json虽然是生成物,但是也需要提交git

package-lock.json 用于固定依赖树的解析结果:同一份 package.json,在本机、同事的机器和 CI 上装出的版本保持一致。
不提交 lock 时,^ 范围会在每台机器上重新解析。CI 因此可能装到比本地更新的补丁版本,构建产物与验证时不一致,且难以复现。

  • npm install:优先按 lock 安装;package.json 有变化(增删依赖、改范围)时才重新解析并更新 lock
  • npm ci:只按 lock 安装,先删除 node_modules;lock 与 package.json 不一致时直接报错

lock 顶部的 "" 条目是 package.json 的快照(name、version、依赖范围)。该条目由 npm 在安装时重写,因此可能滞后于当前的 package.json。

维护

lock 是生成物,应通过命令维护:安装用 npm i,移除用 npm uninstall,升级用 npm update。

手动修改 version 会留下三处不一致:resolved 仍指向旧 tarball 的地址,integrity 与新内容不匹配,新版本新增的传递依赖也不会出现在树中。下一次安装要么报错,要么装出一棵与实际不符的依赖树。