管理到工程化實(shí)踐)
1. 為什么 package.json 值得你花時(shí)間吃透如果你寫(xiě) JavaScript 或者 TypeScript哪怕只是偶爾碰一下前端項(xiàng)目package.json這個(gè)文件你肯定見(jiàn)過(guò)。它躺在項(xiàng)目根目錄里看起來(lái)就是一堆 JSON 鍵值對(duì)很多人平時(shí)只關(guān)心兩件事dependencies里裝了什么包scripts里怎么啟動(dòng)項(xiàng)目。剩下的字段要么視而不見(jiàn)要么復(fù)制粘貼的時(shí)候順手帶過(guò)來(lái)從來(lái)沒(méi)想過(guò)它們是干什么用的。我剛開(kāi)始做項(xiàng)目那幾年也是這個(gè)狀態(tài)。直到有一次排查一個(gè)線上問(wèn)題發(fā)現(xiàn)構(gòu)建產(chǎn)物里混進(jìn)了開(kāi)發(fā)環(huán)境的調(diào)試代碼查了半天才定位到是dependencies和devDependencies放錯(cuò)了位置還有一次發(fā)布一個(gè)工具庫(kù)別人裝完之后類(lèi)型提示全丟了最后發(fā)現(xiàn)是types字段沒(méi)配對(duì)。這些坑踩多了之后我才意識(shí)到package.json根本不是一個(gè)“配置文件”那么簡(jiǎn)單它是整個(gè) Node 生態(tài)里包與包之間溝通的契約是 npm 理解你項(xiàng)目的唯一入口。這篇文章就是把我這些年跟package.json打交道積累下來(lái)的東西系統(tǒng)梳理一遍。從最基礎(chǔ)的字段含義到依賴(lài)版本號(hào)的語(yǔ)義化規(guī)則再到exports、workspaces這些相對(duì)新但越來(lái)越重要的能力我都會(huì)結(jié)合真實(shí)項(xiàng)目里的使用場(chǎng)景講清楚。不管你是剛?cè)胄械男率诌€是已經(jīng)寫(xiě)了幾年業(yè)務(wù)代碼想往工程化方向走的人看完之后應(yīng)該都能對(duì)這個(gè)文件有一個(gè)更完整的認(rèn)知至少下次再看到不認(rèn)識(shí)的字段你知道該去哪里查、它大概會(huì)影響什么。文章里涉及的所有項(xiàng)目名、包名、人名我都會(huì)用虛構(gòu)代稱(chēng)但字段名、版本規(guī)則、行為邏輯都是真實(shí)可驗(yàn)證的。你可以邊看邊打開(kāi)自己手頭的項(xiàng)目對(duì)照著檢查效果會(huì)更好。2. package.json 的整體結(jié)構(gòu)與核心定位2.1 它在 Node 生態(tài)里到底扮演什么角色要理解package.json得先搞清楚 npm 是怎么看待一個(gè)項(xiàng)目的。在 npm 的世界里任何一個(gè)包含package.json的目錄都可以被當(dāng)作一個(gè)“包”來(lái)處理。這個(gè)文件承擔(dān)了三個(gè)層面的職責(zé)身份標(biāo)識(shí)、依賴(lài)聲明、行為定義。身份標(biāo)識(shí)指的是name和version這兩個(gè)字段它們組合起來(lái)構(gòu)成了這個(gè)包在 registry 里的唯一坐標(biāo)。依賴(lài)聲明就是dependencies、devDependencies、peerDependencies這些字段告訴 npm 裝這個(gè)包的時(shí)候還需要哪些東西。行為定義則包括scripts、main、exports、bin等決定了這個(gè)包被安裝后怎么被調(diào)用、入口在哪里、有哪些命令可以執(zhí)行。很多人會(huì)把package.json和package-lock.json搞混。簡(jiǎn)單說(shuō)前者是你手寫(xiě)的“意圖聲明”后者是 npm 根據(jù)你的聲明實(shí)際解析出來(lái)的“精確快照”。你寫(xiě)lodash: ^4.17.0這是意圖lock 文件里記錄的4.17.21加上 integrity 哈希這是實(shí)際結(jié)果。兩者配合才能保證團(tuán)隊(duì)里每個(gè)人裝出來(lái)的依賴(lài)樹(shù)是一致的。2.2 一個(gè)最小可用文件長(zhǎng)什么樣理論上一個(gè)合法的package.json只需要name和version兩個(gè)字段就能通過(guò)校驗(yàn)。但實(shí)際項(xiàng)目里當(dāng)然不會(huì)這么寒酸。下面是一個(gè)我常用的起步模板你可以直接拿去改{ name: my-project, version: 1.0.0, description: 一個(gè)用于演示的項(xiàng)目, main: dist/index.js, types: dist/index.d.ts, scripts: { build: tsc, test: jest }, keywords: [demo, template], author: someone, license: MIT }這個(gè)模板里每個(gè)字段都有明確用途后面我會(huì)逐個(gè)展開(kāi)。這里先提醒一點(diǎn)name字段有命名規(guī)范不能有大寫(xiě)字母、空格也不能以點(diǎn)或下劃線開(kāi)頭長(zhǎng)度不能超過(guò) 214 個(gè)字符。如果你打算發(fā)布到公共 registry還要注意名字不能被別人占用。2.3 字段的分類(lèi)邏輯我把package.json里的字段大致分成四類(lèi)這樣記憶和理解都會(huì)清晰很多分類(lèi)代表字段作用元信息name, version, description, author, license描述這個(gè)包是誰(shuí)、干什么用的入口與導(dǎo)出main, module, types, exports, browser告訴外部怎么引用這個(gè)包依賴(lài)管理dependencies, devDependencies, peerDependencies, optionalDependencies聲明運(yùn)行和開(kāi)發(fā)所需的其他包腳本與工具scripts, bin, engines, config定義可執(zhí)行命令和運(yùn)行環(huán)境要求這個(gè)分類(lèi)不是官方標(biāo)準(zhǔn)但用起來(lái)很順手。你在排查問(wèn)題時(shí)先判斷問(wèn)題屬于哪一類(lèi)然后去對(duì)應(yīng)字段里找原因效率會(huì)高很多。3. 依賴(lài)管理字段的深度拆解3.1 dependencies 與 devDependencies 的邊界怎么劃這是最常被問(wèn)、也最容易出錯(cuò)的一組字段。核心原則只有一句話運(yùn)行時(shí)需要的放 dependencies只在開(kāi)發(fā)、構(gòu)建、測(cè)試階段需要的放 devDependencies。什么叫“運(yùn)行時(shí)需要”就是這個(gè)包被安裝到別人項(xiàng)目里之后如果缺少這個(gè)依賴(lài)代碼就會(huì)報(bào)錯(cuò)或者功能不正常。比如你寫(xiě)了一個(gè) React 組件庫(kù)那react就是運(yùn)行時(shí)依賴(lài)必須放dependencies。但如果你只是用typescript來(lái)編譯代碼編譯完的產(chǎn)物里并不包含 tsc 本身那typescript就應(yīng)該放devDependencies。我見(jiàn)過(guò)最常見(jiàn)的錯(cuò)誤是把構(gòu)建工具、測(cè)試框架、類(lèi)型定義全都塞進(jìn)dependencies。這樣做本身不會(huì)讓代碼跑不起來(lái)但會(huì)帶來(lái)兩個(gè)后果一是別人安裝你的包時(shí)會(huì)莫名其妙多下載一堆根本用不到的東西安裝體積和耗時(shí)都會(huì)增加二是在一些對(duì)依賴(lài)體積敏感的場(chǎng)景比如前端打包可能會(huì)把不該打進(jìn)去的東西打進(jìn)去。有一個(gè)判斷技巧很實(shí)用問(wèn)自己“如果把這個(gè)包刪掉用戶在使用我的包的功能時(shí)會(huì)不會(huì)出問(wèn)題”會(huì)就是dependencies不會(huì)就是devDependencies。3.2 peerDependencies 的真實(shí)用途peerDependencies是很多人一直沒(méi)搞明白的字段。它的含義是“我用到了某個(gè)包但我不自己安裝它而是要求使用我的人來(lái)提供?!弊畹湫偷膱?chǎng)景就是插件體系。假設(shè)你寫(xiě)了一個(gè) Webpack 插件這個(gè)插件必須配合 Webpack 使用但你不應(yīng)該把 Webpack 打包進(jìn)插件里因?yàn)槭褂谜咦约旱捻?xiàng)目里已經(jīng)有 Webpack 了。這時(shí)候你就把 Webpack 聲明在peerDependencies里。從 npm 7 開(kāi)始peerDependencies的默認(rèn)行為發(fā)生了變化如果使用者沒(méi)有安裝對(duì)應(yīng)的 peer 依賴(lài)npm 會(huì)嘗試自動(dòng)安裝。這個(gè)變化解決了很多“裝完插件發(fā)現(xiàn)缺依賴(lài)”的問(wèn)題但也帶來(lái)了一些意料之外的版本沖突。所以在聲明 peer 依賴(lài)時(shí)版本范圍要盡量寫(xiě)得寬松一些比如4.0.0而不是^4.17.0給使用者留出選擇空間。3.3 optionalDependencies 與 bundledDependenciesoptionalDependencies的語(yǔ)義是“裝不上也沒(méi)關(guān)系”。npm 在安裝時(shí)會(huì)嘗試安裝這些依賴(lài)但如果失敗比如某個(gè)平臺(tái)沒(méi)有對(duì)應(yīng)的二進(jìn)制包不會(huì)導(dǎo)致整個(gè)安裝過(guò)程失敗。這個(gè)字段用得不多主要出現(xiàn)在一些需要跨平臺(tái)兼容的底層工具里。bundledDependencies也可以寫(xiě)成bundleDependencies則是把依賴(lài)直接打包進(jìn)你的發(fā)布產(chǎn)物里。這個(gè)字段現(xiàn)在很少用了因?yàn)楝F(xiàn)代 npm 的依賴(lài)解析已經(jīng)很可靠但在一些離線部署或者需要鎖定特定版本的場(chǎng)景下仍然有用。3.4 版本號(hào)語(yǔ)義化規(guī)則與選擇策略版本號(hào)是依賴(lài)管理里最容易埋雷的地方。npm 遵循的是語(yǔ)義化版本SemVer規(guī)范格式是主版本號(hào).次版本號(hào).修訂號(hào)。規(guī)則很簡(jiǎn)單主版本號(hào)變化有不兼容的 API 修改次版本號(hào)變化新增了向后兼容的功能修訂號(hào)變化修復(fù)了向后兼容的 bug在package.json里寫(xiě)版本范圍時(shí)常用的前綴有這幾種寫(xiě)法含義示例匹配^1.2.3允許次版本和修訂號(hào)升級(jí)1.2.3 到 1.x.x~1.2.3只允許修訂號(hào)升級(jí)1.2.3 到 1.2.x1.2.3精確鎖定只有 1.2.31.2.3大于等于1.2.3 及以上*任意版本任何版本我的經(jīng)驗(yàn)是應(yīng)用項(xiàng)目用^工具庫(kù)用~或精確版本。應(yīng)用項(xiàng)目不怕自動(dòng)升級(jí)因?yàn)槟阌?lock 文件和測(cè)試兜底但工具庫(kù)如果依賴(lài)范圍太寬使用者裝到的版本可能跟你測(cè)試時(shí)不一樣容易出兼容性問(wèn)題。注意不要手動(dòng)去改package-lock.json里的版本號(hào)那個(gè)文件應(yīng)該由 npm 自動(dòng)維護(hù)。你只需要改package.json然后重新執(zhí)行安裝命令。4. 入口與導(dǎo)出字段的實(shí)戰(zhàn)解析4.1 main、module、browser 的分工這三個(gè)字段都跟“入口”有關(guān)但面向的環(huán)境不同。main是最傳統(tǒng)的字段指向 CommonJS 格式的入口文件。Node.js 環(huán)境默認(rèn)讀這個(gè)字段。module是社區(qū)約定指向 ES Module 格式的入口打包工具如 Rollup、Webpack會(huì)優(yōu)先讀它以便做 tree-shaking。browser則用于指定瀏覽器環(huán)境下的替代入口可以是一個(gè)字符串也可以是一個(gè)對(duì)象映射把某些 Node 專(zhuān)用的模塊替換成瀏覽器可用的版本。一個(gè)典型的雙格式包會(huì)這樣寫(xiě){ main: dist/index.cjs.js, module: dist/index.esm.js, browser: dist/index.umd.js }這樣 Node 環(huán)境用 CJS打包工具用 ESM直接在瀏覽器里用 script 標(biāo)簽引入時(shí)用 UMD。4.2 types 與類(lèi)型聲明的正確姿勢(shì)如果你用 TypeScript 寫(xiě)庫(kù)types字段必須指向生成的.d.ts文件。很多人會(huì)把它寫(xiě)成源碼里的.ts文件路徑這是不對(duì)的——使用者裝的是你發(fā)布后的產(chǎn)物源碼文件通常不在發(fā)布范圍內(nèi)。正確的做法是在tsconfig.json里開(kāi)啟declaration: true讓編譯器生成類(lèi)型聲明文件然后讓types指向輸出目錄里的對(duì)應(yīng)文件。如果你同時(shí)提供了 CJS 和 ESM 兩套產(chǎn)物最好用exports字段里的types條件分別指定避免類(lèi)型解析出錯(cuò)。4.3 exports 字段新一代的入口控制exports是 Node.js 12.7 之后引入的字段它比main更強(qiáng)大也更嚴(yán)格。一旦你定義了exportsNode 就會(huì)忽略main所有外部引用都必須通過(guò)exports里聲明的路徑來(lái)訪問(wèn)。它的基本結(jié)構(gòu)是這樣的{ exports: { .: { types: ./dist/index.d.ts, import: ./dist/index.esm.js, require: ./dist/index.cjs.js }, ./utils: { import: ./dist/utils.esm.js, require: ./dist/utils.cjs.js } } }這個(gè)配置做了兩件事一是定義了根入口在不同模塊系統(tǒng)下的解析路徑二是暴露了一個(gè)子路徑./utils讓使用者可以按需引入。exports的一個(gè)重要作用是封裝內(nèi)部結(jié)構(gòu)。在沒(méi)有exports的年代使用者可以引用你包里的任意文件比如require(your-pkg/dist/internal/helper)。這很危險(xiǎn)因?yàn)閮?nèi)部文件隨時(shí)可能重構(gòu)。有了exports之后只有你顯式暴露的路徑才能被訪問(wèn)其他路徑一律報(bào)錯(cuò)。這對(duì)庫(kù)作者來(lái)說(shuō)是一個(gè)很好的保護(hù)機(jī)制。提示exports的路徑必須以./開(kāi)頭不能寫(xiě)成dist/index.js。這個(gè)細(xì)節(jié)很容易忽略但寫(xiě)錯(cuò)了會(huì)直接導(dǎo)致解析失敗。5. 腳本、二進(jìn)制與運(yùn)行環(huán)境配置5.1 scripts 的進(jìn)階用法scripts字段大家都會(huì)用但有些技巧能讓它更好用。首先是pre和post鉤子如果你定義了build腳本那么prebuild會(huì)在它之前執(zhí)行postbuild會(huì)在它之后執(zhí)行。這個(gè)機(jī)制可以用來(lái)做清理、校驗(yàn)、通知等事情。其次是跨平臺(tái)兼容。scripts里的命令默認(rèn)由系統(tǒng)的 shell 執(zhí)行這意味著 Windows 和 Unix 下的寫(xiě)法可能不一樣。比如rm -rf dist在 Windows 的 cmd 里就不管用。解決辦法是用rimraf這類(lèi)跨平臺(tái)工具或者用 Node 腳本代替 shell 命令。還有一個(gè)實(shí)用技巧是用npm-run-all或者concurrently來(lái)并行或串行執(zhí)行多個(gè)腳本。比如{ scripts: { build: npm-run-all clean build:*, build:js: tsc, build:css: sass src:dist, clean: rimraf dist } }這樣build會(huì)先執(zhí)行clean然后依次執(zhí)行build:js和build:css邏輯清晰也方便單獨(dú)調(diào)用某個(gè)子任務(wù)。5.2 bin 字段與命令行工具如果你想讓自己的包提供一個(gè)命令行命令就需要用到bin字段。最簡(jiǎn)單的寫(xiě)法是{ bin: { my-cli: ./bin/cli.js } }這樣使用者全局安裝或者通過(guò)npx調(diào)用時(shí)就可以直接執(zhí)行my-cli。對(duì)應(yīng)的cli.js文件開(kāi)頭必須加上 shebang#!/usr/bin/env node沒(méi)有這行系統(tǒng)不知道用什么解釋器來(lái)執(zhí)行這個(gè)文件。另外bin指向的文件在發(fā)布時(shí)必須有可執(zhí)行權(quán)限npm 在打包時(shí)會(huì)自動(dòng)處理但如果你手動(dòng)拷貝文件就要注意這一點(diǎn)。5.3 engines 與 packageManagerengines字段用來(lái)聲明你的包對(duì) Node.js 或 npm 版本的要求{ engines: { node: 16.0.0, npm: 8.0.0 } }默認(rèn)情況下npm 只會(huì)在版本不滿足時(shí)給出警告不會(huì)阻止安裝。如果你希望強(qiáng)制阻止可以在項(xiàng)目里加一個(gè).npmrc文件寫(xiě)入engine-stricttrue。packageManager字段是較新的約定用來(lái)指定項(xiàng)目應(yīng)該使用哪個(gè)包管理器及其版本比如packageManager: pnpm8.6.0。配合 Corepack 使用可以確保團(tuán)隊(duì)成員用同一套工具鏈減少“我這里能跑你那里跑不了”的問(wèn)題。6. 常見(jiàn)問(wèn)題排查與避坑經(jīng)驗(yàn)6.1 依賴(lài)裝不上或版本沖突怎么辦這是最高頻的問(wèn)題。排查思路我一般按這個(gè)順序走第一步刪掉node_modules和package-lock.json重新安裝。很多問(wèn)題是緩存或者 lock 文件損壞導(dǎo)致的重裝能解決一大半。第二步如果重裝無(wú)效用npm ls 包名查看這個(gè)包被誰(shuí)依賴(lài)了、裝了幾個(gè)版本。版本沖突往往是因?yàn)椴煌陌蕾?lài)了同一個(gè)包的不兼容版本。第三步如果確實(shí)存在無(wú)法調(diào)和的沖突可以考慮用overrides字段npm 8.3強(qiáng)制指定某個(gè)包的版本{ overrides: { some-package: 2.0.0 } }這個(gè)能力要謹(jǐn)慎使用因?yàn)樗赡茏屇硞€(gè)依賴(lài)拿到它并不兼容的版本。用之前最好確認(rèn)一下被覆蓋的包在目標(biāo)版本下是否還能正常工作。6.2 發(fā)布后類(lèi)型提示丟失這個(gè)問(wèn)題通常有三個(gè)原因types字段路徑寫(xiě)錯(cuò)、.d.ts文件沒(méi)有被包含在發(fā)布產(chǎn)物里、或者exports里沒(méi)有聲明types條件。排查時(shí)可以先執(zhí)行npm pack生成一個(gè) tarball解壓看看里面到底有哪些文件。如果.d.ts不在里面就要檢查files字段或者.npmignore是不是把它排除了。6.3 files 字段與發(fā)布范圍控制files字段決定了哪些文件會(huì)被發(fā)布到 registry。不寫(xiě)這個(gè)字段的話npm 會(huì)發(fā)布除了.npmignore和默認(rèn)忽略列表之外的所有文件很容易把測(cè)試代碼、配置文件、甚至密鑰文件發(fā)出去。我的習(xí)慣是顯式聲明files{ files: [dist, README.md, LICENSE] }這樣只有dist目錄和兩個(gè)說(shuō)明文件會(huì)被發(fā)布干凈利落。發(fā)布前用npm pack --dry-run預(yù)覽一下文件列表確認(rèn)沒(méi)問(wèn)題再正式發(fā)。6.4 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因解決方向安裝時(shí)報(bào) peer 依賴(lài)缺失peerDependencies 未滿足手動(dòng)安裝對(duì)應(yīng) peer 包或調(diào)整版本打包體積異常大開(kāi)發(fā)依賴(lài)放進(jìn)了 dependencies檢查依賴(lài)分類(lèi)移到 devDependencies引用子路徑報(bào)錯(cuò)exports 未暴露該路徑在 exports 中添加對(duì)應(yīng)子路徑類(lèi)型提示不生效types 路徑錯(cuò)誤或文件未發(fā)布檢查 types 字段和 files 字段命令執(zhí)行報(bào)權(quán)限錯(cuò)誤bin 文件缺少 shebang 或權(quán)限添加 shebang檢查文件權(quán)限不同環(huán)境裝出不同版本缺少 lock 文件或版本范圍過(guò)寬提交 lock 文件收緊版本范圍7. 進(jìn)階字段與工程化實(shí)踐7.1 workspaces 與 monorepoworkspaces字段讓一個(gè)倉(cāng)庫(kù)可以管理多個(gè)包是 monorepo 的基礎(chǔ)?;緦?xiě)法是{ workspaces: [packages/*] }這樣packages目錄下的每個(gè)子目錄都會(huì)被當(dāng)作一個(gè)獨(dú)立的包來(lái)處理npm 會(huì)把它們之間的依賴(lài)關(guān)系自動(dòng)鏈接起來(lái)不需要手動(dòng)npm link。對(duì)于維護(hù)多個(gè)相互依賴(lài)的包的項(xiàng)目來(lái)說(shuō)這個(gè)能力能省掉大量繁瑣的操作。使用 workspaces 時(shí)要注意一點(diǎn)根目錄的package.json通常設(shè)為private: true避免誤發(fā)布。各個(gè)子包的依賴(lài)盡量聲明在子包自己的package.json里而不是全部堆在根目錄否則依賴(lài)關(guān)系會(huì)變得難以追蹤。7.2 private 字段與防誤發(fā)布private: true是一個(gè)簡(jiǎn)單但極其重要的字段。加上它之后npm 會(huì)拒絕發(fā)布這個(gè)包。對(duì)于公司內(nèi)部項(xiàng)目、示例項(xiàng)目、根目錄的 monorepo 配置來(lái)說(shuō)這個(gè)字段應(yīng)該默認(rèn)加上。我見(jiàn)過(guò)不止一次有人不小心把內(nèi)部項(xiàng)目發(fā)到了公共 registry雖然可以撤回但處理起來(lái)很麻煩。7.3 條件導(dǎo)出與多環(huán)境適配exports字段支持條件導(dǎo)出可以根據(jù)不同的環(huán)境返回不同的文件。除了前面提到的import、require、types還有node、browser、default等條件。一個(gè)完整的例子{ exports: { .: { types: ./dist/index.d.ts, browser: ./dist/index.browser.js, node: { import: ./dist/index.node.esm.js, require: ./dist/index.node.cjs.js }, default: ./dist/index.esm.js } } }條件的匹配是從上到下依次判斷的所以順序很重要。types必須放在最前面default放在最后作為兜底。7.4 版本發(fā)布與 changelog 維護(hù)版本號(hào)不是隨便定的。每次發(fā)布前想清楚這次改動(dòng)屬于哪一類(lèi)修 bug 就升修訂號(hào)加功能就升次版本號(hào)有破壞性變更就升主版本號(hào)。配合CHANGELOG.md記錄每個(gè)版本的變化對(duì)使用你包的人來(lái)說(shuō)是很大的幫助。如果團(tuán)隊(duì)規(guī)模不大手動(dòng)維護(hù) changelog 完全夠用。如果發(fā)布頻繁可以考慮用changesets或者standard-version這類(lèi)工具來(lái)自動(dòng)化版本管理和 changelog 生成。工具的選擇取決于團(tuán)隊(duì)習(xí)慣沒(méi)有絕對(duì)的好壞。8. 我個(gè)人的一些使用習(xí)慣寫(xiě)了這么多年package.json有幾個(gè)習(xí)慣我覺(jué)得挺有價(jià)值分享出來(lái)供參考。第一每次新增依賴(lài)都問(wèn)一句“這是運(yùn)行時(shí)還是開(kāi)發(fā)時(shí)”。這個(gè)動(dòng)作只需要幾秒鐘但能避免后面大量的依賴(lài)分類(lèi)混亂。第二發(fā)布前一定跑npm pack --dry-run。這個(gè)命令會(huì)列出即將發(fā)布的文件清單比任何檢查都直觀。我靠它攔下過(guò)好幾次誤包含的測(cè)試文件和本地配置。第三版本范圍寧緊勿松。尤其是工具庫(kù)依賴(lài)范圍寫(xiě)寬了使用者裝到的版本可能跟你測(cè)試的完全不一樣。寧可讓使用者偶爾遇到版本沖突手動(dòng)解決也不要讓他們?cè)诓恢榈那闆r下裝到一個(gè)你沒(méi)驗(yàn)證過(guò)的版本。第四把package.json當(dāng)成代碼來(lái)維護(hù)。它值得被 review值得有注釋雖然 JSON 不支持注釋但可以在 README 里說(shuō)明值得在重構(gòu)時(shí)一起整理。一個(gè)混亂的package.json往往是項(xiàng)目工程化水平的一個(gè)縮影。這些經(jīng)驗(yàn)沒(méi)有什么高深的理論都是在一次次踩坑和修坑中攢下來(lái)的。你如果剛開(kāi)始接觸這些字段不用一次性全部記住先把dependencies、scripts、main、types這幾個(gè)最常用的搞清楚其他的遇到再查、再用、再記慢慢就熟了。