前端工程化入门:npm与Vite从零到实践
1. 前端工程化解决的是哪个层面的问题1.1 从三五个js文件说起我见过很多自学JavaScript的同学学到一定阶段会产生一个困惑明明JS写得好好的浏览器也能跑为什么网上那些项目代码里全是看不懂的命令行操作双击打开的html文件换到别人电脑上就白屏报错。这个困惑的本质是你已经碰到了“代码量增长带来的复杂度爆炸”。举个具体例子。你之前可能写过这样一个页面!DOCTYPE html html langzh-CN head meta charsetUTF-8 title我的页面/title /head body script src./js/utils.js/script script src./js/api.js/script script src./js/components/header.js/script script src./js/components/list.js/script script src./js/main.js/script /body /html刚开始只有三个文件后面变成五个、十个、二十个。你开始需要自己记住“哪个文件先加载、哪个文件依赖哪个全局变量”。如果某个文件里有个变量叫formatTime另一个文件也定义了一个同名函数后加载的会直接覆盖前者。排查这种问题的时候你根本不知道是哪个文件先执行出了错只能一遍一遍打开控制台看报错堆栈。这就是“没有工程化”的状态。JS这门语言本身没有模块隔离能力所有script标签共享同一个全局作用域。代码量一上来靠人肉管理依赖关系一定会崩。而前端工程化做的事情概括来说就是三件模块化管理代码、自动化处理依赖、标准化开发流程。npm负责“依赖”这个维度Vite负责“开发和构建”这个维度两者合在一起就完善了从“写代码”到“跑起来”的全过程。1.2 工程化的三个核心诉求拆开看前端工程化要解决三个问题。模块化隔离每个文件里的变量、函数只在自己的作用域里生效不被外部污染。这个需求在ES Module标准出现之后才算真正解决。现代浏览器原生支持import和export也就是说你可以在A文件中export function foo() {}在B文件中import { foo } from ./A.js两个文件之间通过显式声明建立依赖关系不再依赖加载顺序。依赖管理你的项目会用很多第三方包比如日期处理库dayjs、状态管理库pinia、HTTP请求库axios。手动下载源码复制进项目里升级版本靠重新下载遇到传递依赖比如A包依赖B包B包又依赖C包时更是灾难。npm的出现让“安装包”这件事变成了npm install 包名所有的依赖关系记录在package.json里别人拿到项目后只需要执行一条命令就能把所有依赖装好。开发体验以前改一行代码要手动刷新浏览器还要等编译。现在开发服务器可以把改动实时推到浏览器里样式和组件更新不需要刷新整个页面。构建的时候还能对代码做压缩、混淆、按需加载提升线上运行效率。这三个诉求正好对应了npm和Vite这两个工具的核心定位。npm管的是“包”这个静态层面的问题Vite管的是“运行”这个动态层面的问题。两个配合使用才能达到工程化的完整闭环。1.3 为什么选npm和Vite而不是其他组合现在前端圈子里的工具很多Webpack、Rollup、Parcel、Turbopack包管理器也有yarn、pnpm可以选择。课程里选择npm加Vite是有原因的。npm是Node.js官方自带的包管理器只要安装了Node.jsnpm就跟着一起装好了不需要额外配置。它也是目前生态覆盖最广泛的包管理器绝大多数开源项目默认使用npm。对于初学者来说选npm意味着遇到问题时搜索引擎能搜到的资料量是最多的。Vite则是在2021年之后逐渐成为主流的新一代构建工具。它最大的特点是启动速度极快对初学者非常友好。传统工具如Webpack启动一个项目需要先把所有模块打包成本地文件项目的代码量越大启动越慢。而Vite利用了浏览器原生ES Module的能力开发模式下不打包直接按需加载模块所以启动速度是毫秒级的。我可以把两者的核心差异整理成一个表格对比维度WebpackVite开发模式启动全量打包后启动按需编译浏览器直接执行ES Module首次冷启动速度慢项目越大越慢快基本秒开热更新效率需要重新编译变更模块基于ESM的精准热更新配置复杂度配置项多上手门槛高约定优于配置零配置即可运行适用场景大型复杂项目、历史项目新项目、中小型项目、学习场景Vite在开发模式下的工作原理是当浏览器请求一个.js文件时Vite开发服务器只对这个请求做轻量的转换然后把代码直接返回给浏览器浏览器自己负责模块解析。这样每次改动服务器只需要通知浏览器“这个文件变了”浏览器再去请求这个文件就行不涉及全量打包自然快得多。不过我要额外提醒一句这不是说Webpack没用了很多公司的老项目依然在用Webpack它依然是一个成熟稳定的方案。但作为基础课程Vite的学习曲线更平缓让初学者能把注意力先放在“项目结构”和“依赖关系”这两个核心概念上而不是被构建配置淹没。2. npm包管理器项目的“快递站”2.1 环境准备先装好Node.js在使用npm之前你的电脑上需要先有Node.js。很多初学者分不清Node.js和JavaScript的关系这里简单解释一下JavaScript这门语言本身可以在不同的“宿主环境”里运行。浏览器是一个宿主环境Node.js是另一个宿主环境它让JS可以在电脑上直接运行不需要浏览器。npm就是随Node.js一起发布的包管理工具。你可以把npm理解成一个快递站我们需要什么第三方包就在npm这个“仓库”里取npm负责下载、安装、记录版本。去Node.js官网下载LTS版本也就是长期支持版一路下一步安装就行。安装完成后打开命令行工具执行node -v npm -v如果能看到版本号说明安装成功了。如果提示“不是内部或外部命令”说明Node.js没有正确写入系统环境变量。这种情况在Windows上比较常见通常重新安装Node.js或者在系统环境变量的Path里手动加上Node.js的安装目录就能解决。注意安装路径尽量不要带中文和空格否则后续可能出现各种奇怪问题。2.2 初始化项目package.json是项目的“身份证”进入一个空目录执行npm init -y这个命令会生成一个package.json文件内容大概长这样{ name: my-project, version: 1.0.0, description: , main: index.js, scripts: { test: echo \Error: no test specified\ exit 1 }, keywords: [], author: , license: ISC }这个文件是整个项目依赖管理的核心。dependencies字段记录项目运行时需要的依赖devDependencies字段记录开发时需要的工具比如构建工具、代码检查工具。scripts字段可以定义命令别名比如dev: vite之后在命令行执行npm run dev就等同于执行vite。为什么要用npm init来初始化而不是自己手动创建一个json文件因为npm会帮你把基础字段填好避免格式错误。而且package.json里还有一个隐藏的package-lock.json文件它在安装依赖时自动生成帮你锁定每个依赖的具体版本号保证你在任何电脑上执行npm install装出来的依赖版本都完全一致。这份“锁定”能力很重要否则你周一装的依赖和周五装的依赖可能是不同版本行为完全不一样。2.3 安装依赖install、save、dev的区别安装一个运行时依赖npm install dayjs这条命令会做三件事把dayjs下载到node_modules目录、在package.json的dependencies里写入dayjs以及版本号、更新package-lock.json。安装一个开发依赖npm install -D vite-D是--save-dev的简写写入的是devDependencies。区分这两类依赖有一个简单的判断标准项目上线运行的时候还需要这个包吗如果不需要就是开发依赖。Vite在做完构建之后生成的是纯静态文件运行时不依赖Vite自身所以Vite属于开发依赖。全局安装和局部安装的差别也要搞清楚。局部安装是装在当前项目的node_modules里全局安装的命令是npm install -g 包名全局安装的包可以在电脑的任何目录里直接以命令行的方式调用。但全局装的包不能直接出现在项目代码的import语句里项目代码需要的是局部安装的版本。这个区别初学者经常踩坑总是想着“我把Vite全局装一下所有项目都能用了”结果运行npm run dev时项目找不到Vite。实际工作中全局限定在少数几个命令行工具比如npm install -g pnpm、npm install -g typescript命令行版。项目自身的依赖一律局部安装。还有一种情况有些包不需要装到项目里只是想临时跑一下它提供的命令。这时候可以用npxnpx vite --versionnpx会先检查本地有没有这个包没有就临时从npm仓库拉取执行完自动清理不污染项目的依赖列表。很多脚手架工具推荐用npx方式运行就是出于这个原因。2.4 换一个快的镜像源解决下载卡顿npm默认从国外的registry下载包在国内网络环境下经常速度很慢或者直接超时。解决办法是换成国内镜像源。执行以下命令将npm的registry切换为淘宝镜像npm config set registry https://registry.npmmirror.com验证是否生效npm config get registry如果输出https://registry.npmmirror.com说明配置成功。淘宝镜像源每隔十分钟左右会同步一次npm官方仓库绝大多数包的下载速度都有质的提升。还有一个小技巧如果某个包在镜像源上还没有同步到最新版本你可以临时指定官方源来安装npm install 包名 --registryhttps://registry.npmjs.org这种“临时指定”的方式不会修改全局配置适合偶尔的一次性需求。2.5 常用命令速查表我最常被学生问到的npm命令基本上就下面这些整理出来供你随时查阅命令作用npm init -y快速初始化项目生成package.jsonnpm install安装package.json里所有的依赖npm install 包名安装指定包到dependenciesnpm install 包名 -D安装指定包到devDependenciesnpm uninstall 包名卸载指定包npm update 包名更新指定包npm ls列出当前项目安装的所有包及依赖关系npm run 脚本名执行package.json里scripts字段定义的命令npm config get registry查看当前镜像源地址npm cache clean --force清理npm缓存解决缓存异常问题其中npm run要稍微解释一下。npm run dev这种写法实际上就是去package.json的scripts里找到名为dev的命令来执行。有同学踩过这样的坑在命令行直接输入dev提示找不到命令。原因是npm run会把这个项目的node_modules/.bin目录临时加入PATH所以scripts里写的命令能直接找到而你直接在命令行用dev系统PATH里没有它自然找不到。3. Vite开发服务器让改代码立刻见效3.1 Vite为什么快Vite这个名字来自法语含义是“快速的”。它快的原因主要有两个开发模式不用打包生产模式用Rollup做打包Vite 3之后底层打包器是Rollup。先说开发模式。传统的打包工具Webpack等启动开发服务器时需要先把整个项目所有模块打包成一个大文件再启动服务器。项目几百个模块的话即使做了缓存首次启动也要几秒到几十秒。而Vite利用浏览器原生支持ES Module的特性只启动一个服务器浏览器请求哪个文件服务器就处理哪个文件完全不做“预打包”。举个例子。网页首次加载时浏览器拿到入口的index.html然后通过script typemodule加载main.jsmain.js里import了App.vue浏览器就会再发一个请求去拿App.vue。Vite在这个请求到达时把App.vue编译成浏览器能识别的JS代码返回。整个过程是按需的不加载不需要的模块。热更新方面Vite也是精准到模块级别。当你的某个.vue文件被修改Vite会通过WebSocket通知浏览器“这个模块变了”浏览器自动重新请求这个模块替换页面中对应的部分。修改一个组件不会导致整个页面刷新组件自身的状态还能被保留。有一点值得注意Vite开发模式下运行的是“源码”所以调试工具里看到的代码是未压缩的报错堆栈和源码的对应关系比较清晰这对学习阶段来说非常友好。生产构建时Vite会对代码做压缩合并、去除无用的模块代码最终输出的是优化过的静态文件。3.2 创建Vite项目不用手写配置的快乐Vite官方提供了一个脚手架命令创建项目非常方便npm create vitelatest my-vite-app -- --template vanilla这条命令会创建一个名为my-vite-app的目录其中--template vanilla表示使用原生JavaScript模板。如果你学习的是Vue则可以把vanilla换成vue。这就是一个零配置可运行的项目。进去看看目录结构你会发现my-vite-app/ ├── index.html ├── package.json ├── public/ └── src/ ├── main.js └── style.css注意观察index.html在项目的根目录而不是在src里面。这是因为index.html是Vite开发服务器的入口页面它通过script typemodule src/src/main.js/script这个标签把入口JS文件加载进来。然后在命令行启动项目cd my-vite-app npm install npm run dev执行完npm run dev控制台会输出一个本地地址浏览器打开这个地址就能看到页面。在src/main.js里改点什么保存后页面会自动更新。第一次体验到这种“改完保存页面自动变”的流程时你会明白热更新给开发效率带来的提升有多大。3.3 vite.config.js里的关键配置Vite虽然零配置就能跑但真实项目中几乎一定会用到vite.config.js这个配置文件。我会给你介绍几个我实际开发中使用最频繁的配置项。server.host和server.port默认情况下开发服务器监听localhost:5173端口被占用时会自动递增到下一个可用端口。如果你希望服务器对局域网内的其他设备比如手机可见import { defineConfig } from vite export default defineConfig({ server: { host: true, port: 5173, open: true } })open: true的作用是启动后自动打开浏览器。这个配置在开发时很实用省去了手动输入地址的步骤。resolve.alias这个配置用来设置路径别名让你在import文件时不用写一长串相对路径。比如你的项目里src目录下的文件经常出现import { formatTime } from ../../utils/date.js写起来很烦而且一旦文件移动位置相对路径就会出错。配置别名后import { defineConfig } from vite import { fileURLToPath, URL } from node:url export default defineConfig({ resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } } })之后就可以写成import { formatTime } from /utils/date.js不管你这个文件在哪个层级始终指向src目录。这个配置几乎是Vue和React项目里的标准操作我强烈建议你养成用别名的习惯。server.proxy开发环境下前端项目通常跑在5173端口后端接口可能跑在8080端口这就产生跨域问题。Vite的代理配置可以解决export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })这样配置后前端请求/api/xxxVite会把请求转发到http://localhost:8080/xxx浏览器端感知不到跨域因为请求是同源的。这个配置在联调后端接口时几乎是必用的我后面会在报错排查部分再讲一个相关的坑。3.4 环境变量与模式区分开发、测试、生产真实项目中开发环境和生产环境的配置往往不同。比如开发环境的后端接口地址是http://localhost:8080生产环境的接口地址是https://api.example.com。Vite支持通过环境变量文件区分。在项目根目录创建.env.development和.env.production两个文件# .env.development VITE_API_BASE_URLhttp://localhost:8080# .env.production VITE_API_BASE_URLhttps://api.example.com在代码里通过import.meta.env.VITE_API_BASE_URL读取运行时Vite会自动加载当前模式对应的文件。npm run dev默认使用development模式npm run build默认使用production模式。有个小坑要提醒环境变量名必须以VITE_开头否则不会暴露给客户端代码。这个设计是为了防止开发时意外把密钥之类的敏感信息暴露到前端。任何以前缀VITE_开头的变量都会被打包到构建产物里所以不要把真正的密码往后端看这样做的目的就是为了让客户端代码中不出现敏感信息。4. 从报错邮件里爬出来的经验4.1 环境类问题npm命令找不到“npm不是内部或外部命令”是我见过高频率最高的一类报错几乎每天都有同学问到。出现这个错误的原因基本是Node.js虽然装了但安装目录没有被加入系统环境变量Path。排查步骤是这样的。先确认Node.js装在哪。Windows下通常装在C:\Program Files\nodejs\macOS下通常装在/usr/local/bin/。然后检查Path环境变量里有没有这个路径。在Windows的“系统属性 → 环境变量”里能看到Path的内容把Node.js的安装目录加进去重新打开命令行窗口npm -v就能正常输出了。注意一个细节修改环境变量后已经打开的终端窗口不会自动刷新关掉重新开一个再试。这看起来是个小问题但很容易让人误以为没设置成功反复折腾。4.2 PowerShell执行策略问题在Windows的PowerShell里执行npm或Vite命令时可能会看到这样的报错npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本。这个报错的本质不是npm的问题而是PowerShell的执行策略默认禁止运行.ps1脚本。npm命令在PowerShell里实际是执行一个npm.ps1脚本文件被拦住了。解决方法有两种。第一种在PowerShell里执行一次性放行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser这个命令只对当前用户生效不涉及管理员权限推荐这种方式。第二种直接改用cmd或Windows Terminalcmd里执行npm run dev不会受PowerShell执行策略影响。顺便说一个相关的事情如果你在VS Code的终端里执行时遇到同样的问题终端类型要切换成cmd。VS Code默认终端可能是PowerShell点击终端右边的下拉箭头可以换成“命令提示符”。4.3 热更新失效的排查思路Vite的热更新偶尔也会出问题而且表现形式多样。有的情况是改了文件页面没有反应有的情况是页面刷新了但状态丢了还有的情况是控制台报了一堆错误。如果你碰到“改文件不更新”的情况先按顺序排查检查终端里有没有报错信息。如果编译报错热更新会暂停等待你修复。把开发服务器停掉重启。Vite偶尔会有缓存状态错乱重启能解决很大一部分问题。确认文件路径有没有套在监听范围内。Vite默认监听项目根目录如果你把文件放在项目目录之外它就监听不到。如果是.vue文件相关的热更新异常检查是否安装了对应的插件比如vitejs/plugin-vue。还有一个细节我是踩过坑才记住的。如果你在配置里设置了server.hmr相关选项改动了监听端口、协议之类的配置那么整个开发服务器需要完全重启热更新连接才会重新建立。光刷新浏览器页面旧的WebSocket连接已经断开新的连接没有建立热更新自然就失效了。4.4 代理接口报错代理配置出问题时的现象是前端请求接口时控制台Network面板里能看到一个http proxy error或者504之类的状态码接口返回不了数据。常见原因之一是target地址写错了。target必须用完整的HTTP地址包含协议部分。你在浏览器里访问后端地址能通不代表Vite代理能从目标地址请求到数据如果后端服务没启动代理自然失败。另一个常见问题是代理路径的rewrite写错了。比如你的后端接口实际路径是/list前端请求的路径是/api/list那么你必须用rewrite把/api前缀剥掉否则转发给后端的路径还是/api/list后端一看路由不认识直接404。这个小细节经常被忽略。4.5 其他高频报错速查我在教学过程中收集了几条最高频的报错整理成速查表方便你遇到的时候立刻找到方向报错信息原因解决方案npm ERR! code ELIFECYCLE执行脚本时出错查看终端更上方的完整报错通常是代码语法错误或依赖缺失Cannot find module xxx找不到指定的模块执行npm install确认模块已安装或检查导入路径大小写npm WARN deprecated xxx依赖包已过时通常是间接依赖不阻断运行可暂时忽略vite 不是内部或外部命令Vite未安装成功或未在局部安装执行npm install -D viteEADDRINUSE端口被占用修改配置里的server.port或找到占用进程并结束它error:0308010C:digital envelope routines::unsupportedNode.js版本与Vite版本兼容性问题升级Node.js到LTS版本或升级Vite版本[vite] http proxy error代理配置问题或后端服务未启动检查后端服务是否运行确认代理target和rewrite配置正确排查报错有一个总体原则看终端里第一条报错不要看最后一条。Vite的错误信息有时会带出一长串堆栈最下面一条往往只是“最终结果”第一条才是“起因”。学会找第一条报错排查效率能提升一半以上。5. 从这套基础出发你的下一站在哪学完npm和Vite的使用已经意味着你拿到了打开前端工程化世界大门的钥匙。我建议的下一步学习路线是先深入学习JavaScript的ES Module语法把import、export的各种特性吃透这会直接影响你对依赖关系的理解。然后是TypeScript现代前端项目大多数都基于TypeScript开发它和Vite的结合也非常自然。接着可以了解一个框架Vue或React把Vite和框架插件配合使用。再往后你可以去尝试理解代码规范工具ESLint、Prettier、测试工具Vitest、Playwright和CI/CD流程。你会发现这些工具和npm、Vite有极高的相似性每个工具都解决一类问题工具之间通过配置文件协作整个体系环环相扣。我个人在实际操作中的体会是学习这些工具的时候不要一味追求“记住命令”。工具迭代很快今天记的参数明天可能就变了。更值得花时间理解的是“它解决了什么问题、它和别的工具之间是怎么衔接的”。把这两个问题想明白了换一个新工具你也能很自然地迁移过去。再分享一个真正实用的习惯我会在自己的学习项目里刻意制造问题。比如故意把某个依赖的版本改错、故意把某个环境变量前缀写错、故意把代理端口写错然后去读报错信息找问题原因修复它。这个过程比看十遍教程都管用。报错信息是一手的老师它会把真实的工作原理一点一点教给你。

相关新闻

最新新闻

日新闻

周新闻

月新闻