[{"data":1,"prerenderedAt":919},["ShallowReactive",2],{"note:code\u002Fvue\u002F13-build-deploy-engineering":3},{"id":4,"title":5,"body":6,"description":16,"extension":678,"meta":914,"navigation":691,"path":915,"seo":916,"stem":917,"__hash__":918},"notes\u002Fsource\u002Fcode\u002Fvue\u002F13-build-deploy-engineering.md","13 构建、部署和工程化",{"type":7,"value":8,"toc":894},"minimark",[9,13,17,20,25,28,45,48,67,70,79,82,104,108,114,251,254,274,277,285,288,297,301,310,313,333,338,343,358,363,371,374,378,386,391,395,404,410,417,421,424,432,435,441,445,452,463,466,500,503,506,512,518,522,525,531,537,540,546,552,555,566,570,573,579,582,638,642,645,671,674,756,759,763,766,780,783,792,795,809,813,818,821,825,834,838,841,845,851,854,890],[10,11,5],"h1",{"id":12},"_13-构建部署和工程化",[14,15,16],"p",{},"工程化不是高级项目才需要。只要你希望项目能稳定运行、多人协作、可以上线，就需要理解基本工具链。",[14,18,19],{},"本章目标：你能理解 Vite、package.json、开发服务器、生产构建、环境变量、部署刷新 404 和项目目录组织。",[21,22,24],"h2",{"id":23},"_1-vite-做了什么","1. Vite 做了什么",[14,26,27],{},"开发时：",[29,30,35],"pre",{"className":31,"code":32,"language":33,"meta":34,"style":34},"language-powershell shiki shiki-themes github-light github-dark","npm run dev\n","powershell","",[36,37,38],"code",{"__ignoreMap":34},[39,40,43],"span",{"class":41,"line":42},"line",1,[39,44,32],{},[14,46,47],{},"Vite 提供：",[49,50,51,55,58,61,64],"ul",{},[52,53,54],"li",{},"本地开发服务器。",[52,56,57],{},"热更新。",[52,59,60],{},"Vue 单文件组件编译。",[52,62,63],{},"模块导入处理。",[52,65,66],{},"错误提示。",[14,68,69],{},"生产构建时：",[29,71,73],{"className":31,"code":72,"language":33,"meta":34,"style":34},"npm run build\n",[36,74,75],{"__ignoreMap":34},[39,76,77],{"class":41,"line":42},[39,78,72],{},[14,80,81],{},"Vite 会：",[49,83,84,91,94,97],{},[52,85,86,87,90],{},"编译 ",[36,88,89],{},".vue"," 文件。",[52,92,93],{},"压缩 JS 和 CSS。",[52,95,96],{},"拆分资源。",[52,98,99,100,103],{},"生成 ",[36,101,102],{},"dist\u002F","。",[21,105,107],{"id":106},"_2-packagejson-是什么","2. package.json 是什么",[14,109,110,113],{},[36,111,112],{},"package.json"," 是项目说明书和命令表。",[29,115,119],{"className":116,"code":117,"language":118,"meta":34,"style":34},"language-json shiki shiki-themes github-light github-dark","{\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build\",\n    \"preview\": \"vite preview\",\n    \"test\": \"vitest run\"\n  },\n  \"dependencies\": {\n    \"vue\": \"^3.5.35\"\n  },\n  \"devDependencies\": {\n    \"vite\": \"^8.0.16\"\n  }\n}\n","json",[36,120,121,127,137,153,166,179,190,196,204,215,220,228,239,245],{"__ignoreMap":34},[39,122,123],{"class":41,"line":42},[39,124,126],{"class":125},"sVt8B","{\n",[39,128,130,134],{"class":41,"line":129},2,[39,131,133],{"class":132},"sj4cs","  \"scripts\"",[39,135,136],{"class":125},": {\n",[39,138,140,143,146,150],{"class":41,"line":139},3,[39,141,142],{"class":132},"    \"dev\"",[39,144,145],{"class":125},": ",[39,147,149],{"class":148},"sZZnC","\"vite\"",[39,151,152],{"class":125},",\n",[39,154,156,159,161,164],{"class":41,"line":155},4,[39,157,158],{"class":132},"    \"build\"",[39,160,145],{"class":125},[39,162,163],{"class":148},"\"vite build\"",[39,165,152],{"class":125},[39,167,169,172,174,177],{"class":41,"line":168},5,[39,170,171],{"class":132},"    \"preview\"",[39,173,145],{"class":125},[39,175,176],{"class":148},"\"vite preview\"",[39,178,152],{"class":125},[39,180,182,185,187],{"class":41,"line":181},6,[39,183,184],{"class":132},"    \"test\"",[39,186,145],{"class":125},[39,188,189],{"class":148},"\"vitest run\"\n",[39,191,193],{"class":41,"line":192},7,[39,194,195],{"class":125},"  },\n",[39,197,199,202],{"class":41,"line":198},8,[39,200,201],{"class":132},"  \"dependencies\"",[39,203,136],{"class":125},[39,205,207,210,212],{"class":41,"line":206},9,[39,208,209],{"class":132},"    \"vue\"",[39,211,145],{"class":125},[39,213,214],{"class":148},"\"^3.5.35\"\n",[39,216,218],{"class":41,"line":217},10,[39,219,195],{"class":125},[39,221,223,226],{"class":41,"line":222},11,[39,224,225],{"class":132},"  \"devDependencies\"",[39,227,136],{"class":125},[39,229,231,234,236],{"class":41,"line":230},12,[39,232,233],{"class":132},"    \"vite\"",[39,235,145],{"class":125},[39,237,238],{"class":148},"\"^8.0.16\"\n",[39,240,242],{"class":41,"line":241},13,[39,243,244],{"class":125},"  }\n",[39,246,248],{"class":41,"line":247},14,[39,249,250],{"class":125},"}\n",[14,252,253],{},"字段解释：",[49,255,256,262,268],{},[52,257,258,261],{},[36,259,260],{},"scripts","：可运行命令。",[52,263,264,267],{},[36,265,266],{},"dependencies","：生产运行需要的依赖。",[52,269,270,273],{},[36,271,272],{},"devDependencies","：开发和构建需要的依赖。",[14,275,276],{},"运行：",[29,278,279],{"className":31,"code":32,"language":33,"meta":34,"style":34},[36,280,281],{"__ignoreMap":34},[39,282,283],{"class":41,"line":42},[39,284,32],{},[14,286,287],{},"本质上会执行：",[29,289,291],{"className":31,"code":290,"language":33,"meta":34,"style":34},"vite\n",[36,292,293],{"__ignoreMap":34},[39,294,295],{"class":41,"line":42},[39,296,290],{},[21,298,300],{"id":299},"_3-npm-install-做了什么","3. npm install 做了什么",[29,302,304],{"className":31,"code":303,"language":33,"meta":34,"style":34},"npm install\n",[36,305,306],{"__ignoreMap":34},[39,307,308],{"class":41,"line":42},[39,309,303],{},[14,311,312],{},"它会：",[49,314,315,325,330],{},[52,316,317,318,320,321,324],{},"根据 ",[36,319,112],{}," 和 ",[36,322,323],{},"package-lock.json"," 安装依赖。",[52,326,99,327,103],{},[36,328,329],{},"node_modules",[52,331,332],{},"确保项目能运行。",[14,334,335,337],{},[36,336,329],{}," 很大，不应该手动编辑，也不应该提交到 Git。",[14,339,340,342],{},[36,341,323],{}," 记录具体安装版本，团队项目应该提交它，保证大家依赖一致。",[21,344,346,347,350,351,350,354,357],{"id":345},"_4-devbuildpreview-区别","4. ",[36,348,349],{},"dev","、",[36,352,353],{},"build",[36,355,356],{},"preview"," 区别",[14,359,360,362],{},[36,361,349],{},"：",[29,364,365],{"className":31,"code":32,"language":33,"meta":34,"style":34},[36,366,367],{"__ignoreMap":34},[39,368,369],{"class":41,"line":42},[39,370,32],{},[14,372,373],{},"用于开发。速度快，有热更新，不是生产产物。",[14,375,376,362],{},[36,377,353],{},[29,379,380],{"className":31,"code":72,"language":33,"meta":34,"style":34},[36,381,382],{"__ignoreMap":34},[39,383,384],{"class":41,"line":42},[39,385,72],{},[14,387,388,389,103],{},"生成生产文件到 ",[36,390,102],{},[14,392,393,362],{},[36,394,356],{},[29,396,398],{"className":31,"code":397,"language":33,"meta":34,"style":34},"npm run preview\n",[36,399,400],{"__ignoreMap":34},[39,401,402],{"class":41,"line":42},[39,403,397],{},[14,405,406,407,409],{},"在本地预览 ",[36,408,102],{},"，检查构建产物能否正常运行。",[14,411,412,413,416],{},"不要把 ",[36,414,415],{},"npm run dev"," 当部署方式。上线应该部署构建产物。",[21,418,420],{"id":419},"_5-dist-是什么","5. dist 是什么",[14,422,423],{},"构建后：",[29,425,430],{"className":426,"code":428,"language":429,"meta":34},[427],"language-text","dist\u002F\n  index.html\n  assets\u002F\n    index-xxxx.js\n    index-xxxx.css\n","text",[36,431,428],{"__ignoreMap":34},[14,433,434],{},"这些文件就是浏览器最终需要的静态资源。",[14,436,437,438,440],{},"部署静态站点时，通常把 ",[36,439,102],{}," 交给平台。",[21,442,444],{"id":443},"_6-环境变量","6. 环境变量",[14,446,447,448,451],{},"Vite 暴露给前端的变量通常以 ",[36,449,450],{},"VITE_"," 开头：",[29,453,457],{"className":454,"code":455,"language":456,"meta":34,"style":34},"language-env shiki shiki-themes github-light github-dark","VITE_API_BASE_URL=https:\u002F\u002Fapi.example.com\n","env",[36,458,459],{"__ignoreMap":34},[39,460,461],{"class":41,"line":42},[39,462,455],{},[14,464,465],{},"使用：",[29,467,471],{"className":468,"code":469,"language":470,"meta":34,"style":34},"language-js shiki shiki-themes github-light github-dark","const baseUrl = import.meta.env.VITE_API_BASE_URL\n","js",[36,472,473],{"__ignoreMap":34},[39,474,475,479,482,485,488,491,494,497],{"class":41,"line":42},[39,476,478],{"class":477},"szBVR","const",[39,480,481],{"class":132}," baseUrl",[39,483,484],{"class":477}," =",[39,486,487],{"class":477}," import",[39,489,490],{"class":125},".",[39,492,493],{"class":132},"meta",[39,495,496],{"class":125},".env.",[39,498,499],{"class":132},"VITE_API_BASE_URL\n",[14,501,502],{},"注意：前端环境变量不是秘密。只要被打包进前端，用户就可能看到。",[14,504,505],{},"常见文件：",[29,507,510],{"className":508,"code":509,"language":429,"meta":34},[427],".env\n.env.development\n.env.production\n",[36,511,509],{"__ignoreMap":34},[14,513,514,515,103],{},"不要提交包含秘密的 ",[36,516,517],{},".env.local",[21,519,521],{"id":520},"_7-单页应用刷新-404","7. 单页应用刷新 404",[14,523,524],{},"Vue Router 使用 history 模式时，URL 可能是：",[29,526,529],{"className":527,"code":528,"language":429,"meta":34},[427],"\u002Flessons\u002Fcore\n",[36,530,528],{"__ignoreMap":34},[14,532,533,534,103],{},"如果用户直接刷新，浏览器会向服务器请求 ",[36,535,536],{},"\u002Flessons\u002Fcore",[14,538,539],{},"服务器如果不知道这个路径，就返回 404。",[14,541,542,543,103],{},"解决：服务器把未知路径都返回 ",[36,544,545],{},"index.html",[29,547,550],{"className":548,"code":549,"language":429,"meta":34},[427],"未知路径 -> index.html -> Vue Router 接管\n",[36,551,549],{"__ignoreMap":34},[14,553,554],{},"很多部署平台叫：",[49,556,557,560,563],{},[52,558,559],{},"fallback。",[52,561,562],{},"rewrite。",[52,564,565],{},"SPA fallback。",[21,567,569],{"id":568},"_8-项目目录组织","8. 项目目录组织",[14,571,572],{},"推荐：",[29,574,577],{"className":575,"code":576,"language":429,"meta":34},[427],"src\u002F\n  assets\u002F\n  components\u002F\n  composables\u002F\n  data\u002F\n  router\u002F\n  services\u002F\n  stores\u002F\n  types\u002F\n  views\u002F\n",[36,578,576],{"__ignoreMap":34},[14,580,581],{},"职责：",[49,583,584,590,596,602,608,614,620,626,632],{},[52,585,586,589],{},[36,587,588],{},"assets","：图片、全局 CSS 等静态资源。",[52,591,592,595],{},[36,593,594],{},"components","：可复用组件。",[52,597,598,601],{},[36,599,600],{},"views","：路由页面组件。",[52,603,604,607],{},[36,605,606],{},"composables","：组合式函数。",[52,609,610,613],{},[36,611,612],{},"data","：本地静态数据。",[52,615,616,619],{},[36,617,618],{},"router","：路由配置。",[52,621,622,625],{},[36,623,624],{},"services","：请求接口封装。",[52,627,628,631],{},[36,629,630],{},"stores","：Pinia。",[52,633,634,637],{},[36,635,636],{},"types","：共享 TypeScript 类型。",[21,639,641],{"id":640},"_9-readme-应该写什么","9. README 应该写什么",[14,643,644],{},"一个项目 README 至少写：",[49,646,647,650,653,656,659,662,665,668],{},[52,648,649],{},"项目做什么。",[52,651,652],{},"技术栈。",[52,654,655],{},"如何安装。",[52,657,658],{},"如何运行。",[52,660,661],{},"如何测试。",[52,663,664],{},"如何构建。",[52,666,667],{},"目录结构。",[52,669,670],{},"环境变量说明。",[14,672,673],{},"示例：",[29,675,679],{"className":676,"code":677,"language":678,"meta":34,"style":34},"language-md shiki shiki-themes github-light github-dark","# Project Name\n\n## Setup\n\n```powershell\nnpm install\nnpm run dev\n```\n\n## Scripts\n\n- `npm run dev`\n- `npm run test`\n- `npm run build`\n","md",[36,680,681,687,693,698,702,707,711,715,720,724,729,733,742,749],{"__ignoreMap":34},[39,682,683],{"class":41,"line":42},[39,684,686],{"class":685},"sq-ep","# Project Name\n",[39,688,689],{"class":41,"line":129},[39,690,692],{"emptyLinePlaceholder":691},true,"\n",[39,694,695],{"class":41,"line":139},[39,696,697],{"class":685},"## Setup\n",[39,699,700],{"class":41,"line":155},[39,701,692],{"emptyLinePlaceholder":691},[39,703,704],{"class":41,"line":168},[39,705,706],{"class":125},"```powershell\n",[39,708,709],{"class":41,"line":181},[39,710,303],{"class":125},[39,712,713],{"class":41,"line":192},[39,714,32],{"class":125},[39,716,717],{"class":41,"line":198},[39,718,719],{"class":125},"```\n",[39,721,722],{"class":41,"line":206},[39,723,692],{"emptyLinePlaceholder":691},[39,725,726],{"class":41,"line":217},[39,727,728],{"class":685},"## Scripts\n",[39,730,731],{"class":41,"line":222},[39,732,692],{"emptyLinePlaceholder":691},[39,734,735,739],{"class":41,"line":230},[39,736,738],{"class":737},"s4XuR","-",[39,740,741],{"class":132}," `npm run dev`\n",[39,743,744,746],{"class":41,"line":241},[39,745,738],{"class":737},[39,747,748],{"class":132}," `npm run test`\n",[39,750,751,753],{"class":41,"line":247},[39,752,738],{"class":737},[39,754,755],{"class":132}," `npm run build`\n",[14,757,758],{},"好的 README 能让新同事少问很多重复问题。",[21,760,762],{"id":761},"_10-提交前检查","10. 提交前检查",[14,764,765],{},"最小检查：",[29,767,769],{"className":31,"code":768,"language":33,"meta":34,"style":34},"npm run test\nnpm run build\n",[36,770,771,776],{"__ignoreMap":34},[39,772,773],{"class":41,"line":42},[39,774,775],{},"npm run test\n",[39,777,778],{"class":41,"line":129},[39,779,72],{},[14,781,782],{},"如果项目有 lint：",[29,784,786],{"className":31,"code":785,"language":33,"meta":34,"style":34},"npm run lint\n",[36,787,788],{"__ignoreMap":34},[39,789,790],{"class":41,"line":42},[39,791,785],{},[14,793,794],{},"如果改了页面：",[49,796,797,800,803,806],{},[52,798,799],{},"打开本地页面。",[52,801,802],{},"点一遍关键流程。",[52,804,805],{},"检查控制台错误。",[52,807,808],{},"检查移动端布局。",[21,810,812],{"id":811},"_11-常见工程化错误","11. 常见工程化错误",[814,815,817],"h3",{"id":816},"错误一删除-package-lock","错误一：删除 package-lock",[14,819,820],{},"这会让团队依赖版本更不稳定。一般不要随便删。",[814,822,824],{"id":823},"错误二提交-node_modules","错误二：提交 node_modules",[14,826,827,829,830,833],{},[36,828,329],{}," 应该由 ",[36,831,832],{},"npm install"," 生成，不应该提交。",[814,835,837],{"id":836},"错误三构建前没跑测试","错误三：构建前没跑测试",[14,839,840],{},"测试能提前发现逻辑问题，构建能发现语法和打包问题，两者都重要。",[814,842,844],{"id":843},"错误四把开发服务器地址当生产地址","错误四：把开发服务器地址当生产地址",[14,846,847,850],{},[36,848,849],{},"http:\u002F\u002Flocalhost:5173"," 只适合本地开发。线上应该使用部署平台提供的域名。",[21,852,853],{"id":853},"本章练习",[855,856,857,866,875,881,884,887],"ol",{},[52,858,859,860,320,862,865],{},"解释 ",[36,861,415],{},[36,863,864],{},"npm run build"," 区别。",[52,867,868,869,871,872,103],{},"运行 ",[36,870,864],{},"，观察 ",[36,873,874],{},"dist",[52,876,868,877,880],{},[36,878,879],{},"npm run preview","，预览构建产物。",[52,882,883],{},"写一份项目 README 大纲。",[52,885,886],{},"解释 SPA 刷新 404 的原因和解决思路。",[52,888,889],{},"判断哪些文件应该提交，哪些不应该提交。",[891,892,893],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sq-ep, html code.shiki .sq-ep{--shiki-default:#005CC5;--shiki-default-font-weight:bold;--shiki-dark:#79B8FF;--shiki-dark-font-weight:bold}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":34,"searchDepth":129,"depth":129,"links":895},[896,897,898,899,901,902,903,904,905,906,907,913],{"id":23,"depth":129,"text":24},{"id":106,"depth":129,"text":107},{"id":299,"depth":129,"text":300},{"id":345,"depth":129,"text":900},"4. dev、build、preview 区别",{"id":419,"depth":129,"text":420},{"id":443,"depth":129,"text":444},{"id":520,"depth":129,"text":521},{"id":568,"depth":129,"text":569},{"id":640,"depth":129,"text":641},{"id":761,"depth":129,"text":762},{"id":811,"depth":129,"text":812,"children":908},[909,910,911,912],{"id":816,"depth":139,"text":817},{"id":823,"depth":139,"text":824},{"id":836,"depth":139,"text":837},{"id":843,"depth":139,"text":844},{"id":853,"depth":129,"text":853},{"order":241},"\u002Fsource\u002Fcode\u002Fvue\u002F13-build-deploy-engineering",{"title":5,"description":16},"source\u002Fcode\u002Fvue\u002F13-build-deploy-engineering","ZS17nJNFdfVW2ovxUw4Wy4geAmOmbvyyTXQAt2rcqIk",1784032889391]