帶你了解一些package.json的騷操作

前言 ?
在每個(gè)項(xiàng)目的根目錄下面,一般都會有一個(gè) package.json 文件,其定義了運(yùn)行項(xiàng)目所需要的各種依賴和項(xiàng)目的配置信息(如名稱、版本、許可證等元數(shù)據(jù))。
大多數(shù)人對 package.json 文件的了解,僅停留在:
-
項(xiàng)目名稱、項(xiàng)目構(gòu)建版本、許可證的定義; -
依賴定義(包括 dependencies字段,devDependencies字段); -
使用 scripts字段指定運(yùn)行腳本命令的npm命令行縮寫。
其實(shí),package.json 的作用遠(yuǎn)不止于此,我們可以通過新增配置項(xiàng)實(shí)現(xiàn)更強(qiáng)大的功能,下面將帶你重新認(rèn)識 package.json。
由簡入繁,豐富項(xiàng)目的 package.json
簡單版的 package.json
my-test 的項(xiàng)目時(shí),使用 yarn init -y 或 npm init -y 命令后,在項(xiàng)目目錄下會新增一個(gè) package.json文件,內(nèi)容如下:
{
"name": "my-test", # 項(xiàng)目名稱
"version": "1.0.0", # 項(xiàng)目版本(格式:大版本.次要版本.小版本)
"description": "", # 項(xiàng)目描述
"main": "index.js", # 入口文件
"scripts": { # 指定運(yùn)行腳本命令的 npm 命令行縮寫
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [], # 關(guān)鍵詞
"author": "", # 作者
"license": "ISC" # 許可證
}
package.json 文件的內(nèi)容是一個(gè) JSON 對象,對象的每一個(gè)成員就是當(dāng)前項(xiàng)目的一項(xiàng)配置。
必備屬性(name & version)
-
package.json中有非常多的配置項(xiàng),其中必須填寫的兩個(gè)字段分別是name字段和version字段,它們是組成一個(gè)npm模塊的唯一標(biāo)識。
name 字段
name 字段定義了模塊的名稱,其命名時(shí)需要遵循官方的一些規(guī)范和建議:
-
模塊名會成為模塊 url、命令行中的一個(gè)參數(shù)或者一個(gè)文件夾名稱,任何非url安全的字符在模塊名中都不能使用(我們可以使用validate-npm-package-name包來檢測模塊名是否合法); -
語義化模塊名,可以幫助開發(fā)者更快的找到需要的模塊,并且避免意外獲取錯(cuò)誤的模塊; -
若模塊名稱中存在一些符號,將符號去除后不得與現(xiàn)有的模塊名重復(fù),例如:由于 react-router-dom已經(jīng)存在,react.router.dom、reactrouterdom都不可以再創(chuàng)建。
name 字段不能與其他模塊名重復(fù),我們可以執(zhí)行以下命令查看模塊名是否已經(jīng)被使用:
npm view
如果模塊存在,可以查看該模塊的一些基本信息:

如果該模塊名從未被使用過,則會拋出 404 錯(cuò)誤:

或者,我們也可以去 npm 上輸入模塊名,如果搜不到,則可以使用該模塊名。
version 字段
npm 包中的模塊版本都需要遵循 SemVer 規(guī)范,該規(guī)范的標(biāo)準(zhǔn)版本號采用 X.Y.Z 的格式,其中 X、Y 和 Z 均為非負(fù)的整數(shù),且禁止在數(shù)字前方補(bǔ)零:
-
X是主版本號(major):修改了不兼容的 API -
Y是次版本號(minor):新增了向下兼容的功能 -
Z為修訂號(patch):修正了向下兼容的問題
當(dāng)某個(gè)版本改動比較大、并非穩(wěn)定而且可能無法滿足預(yù)期的兼容性需求時(shí),我們可能要先發(fā)布一個(gè)先行版本。
先行版本號可以加到主版本號.次版本號.修訂號的后面,通過 - 號連接一連串以句點(diǎn)分隔的標(biāo)識符和版本編譯信息:
-
內(nèi)部版本(alpha) -
公測版本(beta) -
正式版本的候選版本rc(即 Release candiate)
我們可以執(zhí)行以下命令查看模塊的版本:
npm view
version
# 查看某個(gè)模塊的最新版本
npm view
versions
# 查看某個(gè)模塊的所有歷史版本
復(fù)制代碼
查看 antd 的最新版本:

查看 antd 的所有歷史版本:

可以看到,antd 是嚴(yán)格按照 SemVer 規(guī)范來發(fā)版的,版本號是嚴(yán)格按照主版本號.次版本號.修訂號的格式命名和嚴(yán)格遞增的,在發(fā)布的版本改動較大時(shí),還會先發(fā)布alpha、beta、rc等先行版本。
描述信息(description & keywords)
-
description字段用于添加模塊的描述信息,便于用戶了解該模塊。 -
keywords字段用于給模塊添加關(guān)鍵字。 -
當(dāng)我們使用 npm檢索模塊時(shí),會對模塊中的description字段和keywords字段進(jìn)行匹配,寫好package.json中的description和keywords將有利于增加我們模塊的曝光率。
安裝項(xiàng)目依賴(dependencies & devDependencies)
dependencies字段指定了項(xiàng)目運(yùn)行所依賴的模塊(生產(chǎn)環(huán)境使用),如 antd、 react、 moment等插件庫:
-
它們是我們生產(chǎn)環(huán)境所需要的依賴項(xiàng),在把項(xiàng)目作為一個(gè) npm包的時(shí)候,用戶安裝npm包時(shí)只會安裝dependencies里面的依賴。
devDependencies 字段指定了項(xiàng)目開發(fā)所需要的模塊(開發(fā)環(huán)境使用),如 webpack、typescript、babel等:
-
在代碼打包提交線上時(shí),我們并不需要這些工具,所以我們將它放入 devDependencies中。
如果一個(gè)模塊不在 package.json 文件之中,我們可以單獨(dú)安裝這個(gè)模塊,并使用相應(yīng)的參數(shù),將其寫入 dependencies 字段/ devDependencies 字段中:
# 使用 npm
npm install
--save # 寫入 dependencies 屬性
npm install
--save-dev # 寫入 devDependencies 屬性
# 使用 yarn
yarn add
# 寫入 dependencies 屬性
yarn add
--dev # 寫入 devDependencies 屬性
復(fù)制代碼
package.json 文件,開發(fā)直接使用 npm install / yarn install 命令,就會在當(dāng)前目錄中自動安裝所需要的模塊,安裝完成項(xiàng)目所需的運(yùn)行和開發(fā)環(huán)境就配置好了。
簡化終端命令(scripts)
scripts 字段是 package.json 中的一種元數(shù)據(jù)功能,它接受一個(gè)對象,對象的屬性為可以通過 npm run 運(yùn)行的腳本,值為實(shí)際運(yùn)行的命令(通常是終端命令),如:
"scripts": {
"start": "node index.js"
},
scripts 字段,既可以記錄它們又可以實(shí)現(xiàn)輕松重用。
定義項(xiàng)目入口(main)
main 字段是 package.json 中的另一種元數(shù)據(jù)功能,它可以用來指定加載的入口文件。假如你的項(xiàng)目是一個(gè) npm 包,當(dāng)用戶安裝你的包后,require('my-module') 返回的是 main 字段中所列出文件的 module.exports 屬性。
main 字段時(shí),默認(rèn)值是模塊根目錄下面的index.js 文件。
發(fā)布文件配置(files)
files 字段用于描述我們使用 npm publish 命令后推送到 npm 服務(wù)器的文件列表,如果指定文件夾,則文件夾內(nèi)的所有內(nèi)容都會包含進(jìn)來。
antd 的 package.json 的files 字段,內(nèi)容如下:
"files": [
"dist",
"lib",
"es"
],
可以看到下載后的 antd 包是下面的目錄結(jié)構(gòu):

另外,我們還可以通過配置一個(gè) .npmignore 文件來排除一些文件, 防止大量的垃圾文件推送到 npm 上。
定義私有模塊(private)
private 屬性的值為 true,這是因?yàn)?nbsp;npm 拒絕發(fā)布私有模塊,通過設(shè)置該字段可以防止私有模塊被無意間發(fā)布出去。
指定模塊適用系統(tǒng)(os)
darwin 系統(tǒng)下,我們需要保證 windows 用戶不會安裝到該模塊,從而避免發(fā)生不必要的錯(cuò)誤。
os 屬性則可以幫助我們實(shí)現(xiàn)以上的需求,該屬性可以指定模塊適用系統(tǒng)的系統(tǒng),或者指定不能安裝的系統(tǒng)黑名單(當(dāng)在系統(tǒng)黑名單中的系統(tǒng)中安裝模塊則會報(bào)錯(cuò)):
"os" : [ "darwin", "linux" ] # 適用系統(tǒng)
"os" : [ "!win32" ] # 黑名單
Tips:在
node環(huán)境下可以使用process.platform來判斷操作系統(tǒng)。
指定模塊適用 cpu 架構(gòu)(cpu)
os 字段類似,我們可以用 cpu 字段更精準(zhǔn)的限制用戶安裝環(huán)境:
"cpu" : [ "x64", "ia32" ] # 適用 cpu
"cpu" : [ "!arm", "!mips" ] # 黑名單
Tips:在
node環(huán)境下可以使用process.arch來判斷cpu架構(gòu)。
指定項(xiàng)目 node 版本(engines)
有時(shí)候,新拉一個(gè)項(xiàng)目的時(shí)候,由于和其他開發(fā)使用的 node 版本不同,導(dǎo)致會出現(xiàn)很多奇奇怪怪的問題(如某些依賴安裝報(bào)錯(cuò)、依賴安裝完項(xiàng)目跑步起來等)。

為了實(shí)現(xiàn)項(xiàng)目開箱即用的偉大理想,這時(shí)候可以使用 package.json 的 engines 字段來指定項(xiàng)目 node 版本:
"engines": {
"node": ">= 8.16.0"
},
npm 版本:
"engines": {
"npm": ">= 6.9.0"
},
自定義命令(bin)
用過 vue-cli,create-react-app等腳手架的朋友們,不知道你們有沒有好奇過,為什么安裝這些腳手架后,就可以使用類似 vue create/create-react-app之類的命令,其實(shí)這和 package.json 中的 bin 字段有關(guān)。
bin 字段用來指定各個(gè)內(nèi)部命令對應(yīng)的可執(zhí)行文件的位置。當(dāng)package.json 提供了 bin 字段后,即相當(dāng)于做了一個(gè)命令名和本地文件名的映射。
當(dāng)用戶安裝帶有 bin 字段的包時(shí),
-
如果是全局安裝, npm將會使用符號鏈接把這些文件鏈接到/usr/local/node_modules/.bin/; -
如果是本地安裝,會鏈接到 ./node_modules/.bin/。
舉個(gè) ?,如果要使用 my-app-cli 作為命令時(shí),可以配置以下 bin 字段:
"bin": {
"my-app-cli": "./bin/cli.js"
}
my-app-cli 命令對應(yīng)的可執(zhí)行文件為 bin 子目錄下的 cli.js,因此在安裝了 my-app-cli 包的項(xiàng)目中,就可以很方便地利用 npm執(zhí)行腳本:
"scripts": {
start: 'node node_modules/.bin/my-app-cli'
}

咦,怎么看起來和 vue create/create-react-app之類的命令不太像?原因:
-
當(dāng)需要 node環(huán)境時(shí)就需要加上node前綴 -
如果加上 node前綴,就需要指定my-app-cli的路徑 ->node_modules/.bin,否則node my-app-cli會去查找當(dāng)前路徑下的my-app-cli.js,這樣肯定是不對。
若要實(shí)現(xiàn)像 vue create/create-react-app之類的命令一樣簡便的方式,則可以在上文提到的 bin 子目錄下可執(zhí)行文件cli.js 中的第一行寫入以下命令:
#!/usr/bin/env node
node 解析,這樣命令就可以簡寫成 my-app-cli 了。
React 項(xiàng)目相關(guān)
設(shè)置應(yīng)用根路徑(homepage)
當(dāng)我們使用 create-react-app 腳手架搭建的 React 項(xiàng)目,默認(rèn)是使用內(nèi)置的 webpack 配置,當(dāng)package.json 中不配置 homepage 屬性時(shí),build 打包之后的文件資源應(yīng)用路徑默認(rèn)是 /,如下圖:

一般來說,我們打包的靜態(tài)資源會部署在 CDN 上,為了讓我們的應(yīng)用知道去哪里加載資源,則需要我們設(shè)置一個(gè)根路徑,這時(shí)可以通過 package.json 中的 homepage 字段設(shè)置應(yīng)用的根路徑。
當(dāng)我們設(shè)置了 homepage 屬性后:
{
"homepage": "https://xxxx.cdn/my-project",
}
打包后的資源路徑就會加上 homepage 的地址:

開發(fā)環(huán)境解決跨域問題(proxy)
package.json 中的 proxy 來解決跨域問題,配置如下:
{
"proxy": "http://localhost:4000" // 配置你要請求的服務(wù)器地址
}
create-react-app 的版本高于 2.0 版本的時(shí)候在 package.json 中只能配置string 類型,這意味著如果要使用 package.json 來解決跨域問題,則只能代理一個(gè)服務(wù)器地址。
http-proxy-middleware ,在 src 目錄下新建 setupProxy.js :
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(
proxy("/base", {
target: "http://localhost:4000",
changeOrigin: true
})
);
app.use(
proxy("/fans", {
target: "http://localhost:5000",
changeOrigin: true
})
);
};
根據(jù)開發(fā)環(huán)境采用不同的全局變量值(自定義字段)
sentry 地址,在正式環(huán)境時(shí)則跳轉(zhuǎn)正式環(huán)境的 sentry 地址。
scripts 字段,實(shí)現(xiàn)環(huán)境變量(NODE_ENV)的設(shè)置:
"scripts": {
"start": "NODE_ENV=development node scripts/start.js",
"build": "NODE_ENV=production node scripts/build.js",
},
process.env.NODE_ENV 訪問到 NODE_ENV 的值。
方案一
sentryUrl 設(shè)置不同的值:
let sentryUrl;
if (process.env.NODE_ENV === 'development') {
sentryUrl = 'test-sentry.xxx.com';
} else {
sentryUrl = 'sentry.xxx.com';
}
這么做好像沒毛病,但是深入一想,如果有多個(gè)組件,要根據(jù)不同的環(huán)境使用不同的服務(wù)(多種服務(wù))地址,如果按照上面的寫法,項(xiàng)目中將存在許多重復(fù)的判斷代碼,且當(dāng)服務(wù)地址發(fā)生變化時(shí),包含這些服務(wù)地址的組件都需要相應(yīng)的做改動,這樣明顯是不合理的。

方案二
解決方案:相關(guān)服務(wù)的地址配置在 package.json中,同時(shí)修改項(xiàng)目的 webpack 配置。
注:修改項(xiàng)目的 webpack 配置需要 eject 項(xiàng)目的 webpack 配置(更多細(xì)節(jié)可閱讀 ?:react + typescript 項(xiàng)目的定制化過程)。
在項(xiàng)目根目錄下使用 yarn eject 成功 eject 出配置后,可以發(fā)現(xiàn)項(xiàng)目目錄的變化如下:

如果需要定制化項(xiàng)目,一般就是在 config 目錄下對默認(rèn)的 webpack 配置進(jìn)行修改,在這里我們需要關(guān)注 config/path.js 和 config/env.js 兩個(gè)文件:
-
env.js的主要目的在于讀取env配置文件并將env的配置信息給到全局變量process.env; -
path.js的主要目的在于為項(xiàng)目提供各種路徑,包括構(gòu)建路徑、public路徑等。
由于本文的重點(diǎn)不是學(xué)習(xí) webpack 配置,這里僅介紹如何實(shí)現(xiàn)【根據(jù)開發(fā)環(huán)境采用不同的全局變量值】的功能。
首先,需要在 package.json 中配置以下內(nèi)容:
"scripts": {
"start": "NODE_ENV=development node scripts/start.js",
"build": "NODE_ENV=production node scripts/build.js",
},
"sentryPath": {
"dev": "https://test-sentry.xxx.com",
"prod": "https://sentry.xxx.com"
}
path.js 文件,內(nèi)容如下:
// 重寫 getPublicUrl 方法
const getPublicUrl = (appPackageJson, pathName) => {
let path;
switch (process.env.DEPLOY_ENV) {
case 'development':
path = require(appPackageJson)[pathName].dev;
break;
case 'production':
path = require(appPackageJson)[pathName].prod;
break;
default:
path = envPublicUrl || require(appPackageJson).homepage;
}
return path;
}
// 新增 getSentryPath 方法
const getSentryPath = (appPackageJson) => {
return getPublicUrl(appPackageJson, 'sentryPath');
}
// config after eject: we're in ./config/
module.exports = {
...,
sentryUrl: getSentryPath(resolveApp('package.json')), // 新增
};
env.js 文件,內(nèi)容如下:
// 修改 getClientEnvironment 方法
function getClientEnvironment(publicUrl) {
const raw = Object.keys(process.env)
.filter(key => REACT_APP.test(key))
.reduce(
(env, key) => {
...
},
{
NODE_ENV: process.env.NODE_ENV || 'development',
PUBLIC_URL: publicUrl,
SENTRY_URL: paths.sentryUrl // 新增
}
);
const stringified = {
...
};
return { raw, stringified };
}
process.env.SENTRY_URL 獲取到 sentry 服務(wù)的地址了,雖然看起來比方案一繁瑣,但是這種收益是長期的,如要新增一個(gè) sonarqube 服務(wù),同理實(shí)現(xiàn)即可,通過使用 package.json 也可以清楚的看到當(dāng)前服務(wù)在不同環(huán)境下使用的地址。
總結(jié) ?
package.json 的多種常見的配置字段及作用,并通過例子加深大家對 package.json這些字段的理解。
React 項(xiàng)目中 package.json 文件能實(shí)現(xiàn)的一些功能進(jìn)行介紹。
以上內(nèi)容如有遺漏錯(cuò)誤,歡迎留言 ?? 指出,一起進(jìn)步 ???
如果覺得本文對你有幫助,?? 留下你寶貴的 ?
參考資料 ?
-
Creating a package.json file -
package.json bin的作用 -
在開發(fā)環(huán)境中代理 API 請求 -
react + typescript 項(xiàng)目的定制化過程 -
React學(xué)習(xí)筆記
Dotnet9網(wǎng)站常駐編輯。
長按關(guān)注我,
歡迎技術(shù)交流!
點(diǎn)擊閱讀原文,查看Dotnet9站點(diǎn)更多博文。
