前言 ????
在每個項目的根目錄下面,一般都會有一個 package.json
文件,其定義了運行項目所需要的各種依賴和項目的配置信息(如名稱、版本、許可證等元數據)。
大多數人對 package.json
文件的瞭解,僅停留在:
項目名稱、項目構建版本、許可證的定義;
依賴定義(包括
dependencies
字段,devDependencies
字段);使用
scripts
字段指定運行腳本命令的npm
命令行縮寫。
其實,package.json
的作用遠不止於此,我們可以通過新增配置項實現更強大的功能,下面將帶你重新認識 package.json
。
由簡入繁,豐富項目的 package.json
簡單版的 package.json
當我們新建一個名稱爲 my-test
的項目時,使用 yarn init -y
或 npm init -y
命令後,在項目目錄下會新增一個 package.json
文件,內容如下:
{
"name": "my-test", # 項目名稱
"version": "1.0.0", # 項目版本(格式:大版本.次要版本.小版本)
"description": "", # 項目描述
"main": "index.js", # 入口文件
"scripts": { # 指定運行腳本命令的 npm 命令行縮寫
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [], # 關鍵詞
"author": "", # 作者
"license": "ISC" # 許可證
}
可以看到,package.json
文件的內容是一個 JSON
對象,對象的每一個成員就是當前項目的一項配置。
必備屬性(name & version)
package.json
中有非常多的配置項,其中必須填寫的兩個字段分別是name
字段和version
字段,它們是組成一個npm
模塊的唯一標識。
name 字段
name
字段定義了模塊的名稱,其命名時需要遵循官方的一些規範和建議:
模塊名會成爲模塊
url
、命令行中的一個參數或者一個文件夾名稱,任何非url
安全的字符在模塊名中都不能使用(我們可以使用validate-npm-package-name
包來檢測模塊名是否合法);語義化模塊名,可以幫助開發者更快的找到需要的模塊,並且避免意外獲取錯誤的模塊;
若模塊名稱中存在一些符號,將符號去除後不得與現有的模塊名重複,例如:由於
react-router-dom
已經存在,react.router.dom
、reactrouterdom
都不可以再創建。
name
字段不能與其他模塊名重複,我們可以執行以下命令查看模塊名是否已經被使用:
npm view <packageName>
如果模塊存在,可以查看該模塊的一些基本信息:
如果該模塊名從未被使用過,則會拋出 404 錯誤:
或者,我們也可以去 npm
上輸入模塊名,如果搜不到,則可以使用該模塊名。
version 字段
npm
包中的模塊版本都需要遵循 SemVer
規範,該規範的標準版本號採用 X.Y.Z
的格式,其中 X
、Y
和 Z
均爲非負的整數,且禁止在數字前方補零:
X
是主版本號(major):修改了不兼容的 APIY
是次版本號(minor):新增了向下兼容的功能Z
爲修訂號(patch):修正了向下兼容的問題
當某個版本改動比較大、並非穩定而且可能無法滿足預期的兼容性需求時,我們可能要先發佈一個先行版本。
先行版本號可以加到主版本號.次版本號.修訂號
的後面,通過 -
號連接一連串以句點分隔的標識符和版本編譯信息:
內部版本(alpha)
公測版本(beta)
正式版本的候選版本rc(即 Release candiate)
我們可以執行以下命令查看模塊的版本:
npm view <packageName> version # 查看某個模塊的最新版本
npm view <packageName> versions # 查看某個模塊的所有歷史版本
複製代碼
查看 antd
的最新版本:
查看 antd
的所有歷史版本:
可以看到,antd
是嚴格按照 SemVer
規範來發版的,版本號是嚴格按照主版本號.次版本號.修訂號
的格式命名和嚴格遞增的,在發佈的版本改動較大時,還會先發布alpha
、beta
、rc
等先行版本。
描述信息(description & keywords)
description
字段用於添加模塊的描述信息,便於用戶瞭解該模塊。keywords
字段用於給模塊添加關鍵字。當我們使用
npm
檢索模塊時,會對模塊中的description
字段和keywords
字段進行匹配,寫好package.json
中的description
和keywords
將有利於增加我們模塊的曝光率。
安裝項目依賴(dependencies & devDependencies)
dependencies
字段指定了項目運行所依賴的模塊(生產環境使用),如 antd
、 react
、 moment
等插件庫:
它們是我們生產環境所需要的依賴項,在把項目作爲一個
npm
包的時候,用戶安裝npm
包時只會安裝dependencies
裏面的依賴。
devDependencies
字段指定了項目開發所需要的模塊(開發環境使用),如 webpack
、typescript
、babel
等:
在代碼打包提交線上時,我們並不需要這些工具,所以我們將它放入
devDependencies
中。
如果一個模塊不在 package.json
文件之中,我們可以單獨安裝這個模塊,並使用相應的參數,將其寫入 dependencies
字段/ devDependencies
字段中:
# 使用 npm
npm install <package...> --save # 寫入 dependencies 屬性
npm install <package...> --save-dev # 寫入 devDependencies 屬性
# 使用 yarn
yarn add <package...> # 寫入 dependencies 屬性
yarn add <package...> --dev # 寫入 devDependencies 屬性
複製代碼
有了 package.json
文件,開發直接使用 npm install
/ yarn install
命令,就會在當前目錄中自動安裝所需要的模塊,安裝完成項目所需的運行和開發環境就配置好了。
簡化終端命令(scripts)
scripts
字段是 package.json
中的一種元數據功能,它接受一個對象,對象的屬性爲可以通過 npm run
運行的腳本,值爲實際運行的命令(通常是終端命令),如:
"scripts": {
"start": "node index.js"
},
將終端命令放入 scripts
字段,既可以記錄它們又可以實現輕鬆重用。
定義項目入口(main)
main
字段是 package.json
中的另一種元數據功能,它可以用來指定加載的入口文件。假如你的項目是一個 npm
包,當用戶安裝你的包後,require('my-module')
返回的是 main
字段中所列出文件的 module.exports
屬性。
當不指定main
字段時,默認值是模塊根目錄下面的index.js
文件。
發佈文件配置(files)
files
字段用於描述我們使用 npm publish
命令後推送到 npm
服務器的文件列表,如果指定文件夾,則文件夾內的所有內容都會包含進來。
我們可以查看下載的 antd
的 package.json
的files
字段,內容如下:
"files": [
"dist",
"lib",
"es"
],
可以看到下載後的 antd
包是下面的目錄結構:
另外,我們還可以通過配置一個 .npmignore
文件來排除一些文件, 防止大量的垃圾文件推送到 npm
上。
定義私有模塊(private)
一般公司的非開源項目,都會設置 private
屬性的值爲 true
,這是因爲 npm
拒絕發佈私有模塊,通過設置該字段可以防止私有模塊被無意間發佈出去。
指定模塊適用系統(os)
假如我們開發了一個模塊,只能跑在 darwin
系統下,我們需要保證 windows
用戶不會安裝到該模塊,從而避免發生不必要的錯誤。
這時候,使用 os
屬性則可以幫助我們實現以上的需求,該屬性可以指定模塊適用系統的系統,或者指定不能安裝的系統黑名單(當在系統黑名單中的系統中安裝模塊則會報錯):
"os" : [ "darwin", "linux" ] # 適用系統
"os" : [ "!win32" ] # 黑名單
Tips:在
node
環境下可以使用process.platform
來判斷操作系統。
指定模塊適用 cpu 架構(cpu)
和上面的 os
字段類似,我們可以用 cpu
字段更精準的限制用戶安裝環境:
"cpu" : [ "x64", "ia32" ] # 適用 cpu
"cpu" : [ "!arm", "!mips" ] # 黑名單
Tips:在
node
環境下可以使用process.arch
來判斷cpu
架構。
指定項目 node 版本(engines)
有時候,新拉一個項目的時候,由於和其他開發使用的 node
版本不同,導致會出現很多奇奇怪怪的問題(如某些依賴安裝報錯、依賴安裝完項目跑步起來等)。
爲了實現項目開箱即用的偉大理想,這時候可以使用 package.json
的 engines
字段來指定項目 node 版本:
"engines": {
"node": ">= 8.16.0"
},
該字段也可以指定適用的 npm
版本:
"engines": {
"npm": ">= 6.9.0"
},
需要注意的是,engines屬性僅起到一個說明的作用,當用戶版本不符合指定值時也不影響依賴的安裝。
自定義命令(bin)
用過 vue-cli
,create-react-app
等腳手架的朋友們,不知道你們有沒有好奇過,爲什麼安裝這些腳手架後,就可以使用類似 vue create
/create-react-app
之類的命令,其實這和 package.json
中的 bin
字段有關。
bin
字段用來指定各個內部命令對應的可執行文件的位置。當package.json
提供了 bin
字段後,即相當於做了一個命令名和本地文件名的映射。
當用戶安裝帶有 bin
字段的包時,
如果是全局安裝,
npm
將會使用符號鏈接把這些文件鏈接到/usr/local/node_modules/.bin/
;如果是本地安裝,會鏈接到
./node_modules/.bin/
。
舉個 ????,如果要使用 my-app-cli
作爲命令時,可以配置以下 bin
字段:
"bin": {
"my-app-cli": "./bin/cli.js"
}
上面代碼指定,my-app-cli
命令對應的可執行文件爲 bin
子目錄下的 cli.js
,因此在安裝了 my-app-cli
包的項目中,就可以很方便地利用 npm
執行腳本:
"scripts": {
start: 'node node_modules/.bin/my-app-cli'
}
咦,怎麼看起來和 vue create
/create-react-app
之類的命令不太像?原因:
當需要
node
環境時就需要加上node
前綴如果加上
node
前綴,就需要指定my-app-cli
的路徑 ->node_modules/.bin
,否則node my-app-cli
會去查找當前路徑下的my-app-cli.js
,這樣肯定是不對。
若要實現像 vue create
/create-react-app
之類的命令一樣簡便的方式,則可以在上文提到的 bin
子目錄下可執行文件cli.js
中的第一行寫入以下命令:
#!/usr/bin/env node
這行命令的作用是告訴系統用 node
解析,這樣命令就可以簡寫成 my-app-cli
了。
React 項目相關
設置應用根路徑(homepage)
當我們使用 create-react-app
腳手架搭建的 React
項目,默認是使用內置的 webpack
配置,當package.json
中不配置 homepage
屬性時,build 打包之後的文件資源應用路徑默認是 /
,如下圖:
一般來說,我們打包的靜態資源會部署在 CDN
上,爲了讓我們的應用知道去哪裏加載資源,則需要我們設置一個根路徑,這時可以通過 package.json
中的 homepage
字段設置應用的根路徑。
當我們設置了 homepage
屬性後:
{
"homepage": "https://xxxx.cdn/my-project",
}
打包後的資源路徑就會加上 homepage
的地址:
開發環境解決跨域問題(proxy)
在做前後端分離的項目的時候,調用接口時則會遇到跨域的問題,當在開發環境中時,可以通過配置 package.json
中的 proxy
來解決跨域問題,配置如下:
{
"proxy": "http://localhost:4000" // 配置你要請求的服務器地址
}
注意,當 create-react-app
的版本高於 2.0 版本的時候在 package.json
中只能配置 string
類型,這意味着如果要使用 package.json
來解決跨域問題,則只能代理一個服務器地址。
如果要代理多個服務器地址時,則需要安裝 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
})
);
};
根據開發環境採用不同的全局變量值(自定義字段)
假設有這麼一個組件,當組件被點擊時,在開發環境時是跳轉測試環境的 sentry
地址,在正式環境時則跳轉正式環境的 sentry
地址。
首先,通過配置前面提到的 scripts
字段,實現環境變量(NODE_ENV)的設置:
"scripts": {
"start": "NODE_ENV=development node scripts/start.js",
"build": "NODE_ENV=production node scripts/build.js",
},
項目啓動起來後,在代碼中我們可以通過 process.env.NODE_ENV
訪問到 NODE_ENV
的值。
方案一
我們可以在組件中寫類似以下的判斷代碼,根據不同環境給 sentryUrl
設置不同的值:
let sentryUrl;
if (process.env.NODE_ENV === 'development') {
sentryUrl = 'test-sentry.xxx.com';
} else {
sentryUrl = 'sentry.xxx.com';
}
這麼做好像沒毛病,但是深入一想,如果有多個組件,要根據不同的環境使用不同的服務(多種服務)地址,如果按照上面的寫法,項目中將存在許多重複的判斷代碼,且當服務地址發生變化時,包含這些服務地址的組件都需要相應的做改動,這樣明顯是不合理的。
方案二
解決方案:相關服務的地址配置在 package.json
中,同時修改項目的 webpack
配置。
注:修改項目的 webpack
配置需要 eject 項目的 webpack
配置(更多細節可閱讀 ????:react + typescript 項目的定製化過程)。
在項目根目錄下使用 yarn eject
成功 eject 出配置後,可以發現項目目錄的變化如下:
如果需要定製化項目,一般就是在 config
目錄下對默認的 webpack
配置進行修改,在這裏我們需要關注 config/path.js
和 config/env.js
兩個文件:
env.js
的主要目的在於讀取env
配置文件並將env
的配置信息給到全局變量process.env
;path.js
的主要目的在於爲項目提供各種路徑,包括構建路徑、public
路徑等。
由於本文的重點不是學習 webpack
配置,這裏僅介紹如何實現【根據開發環境採用不同的全局變量值】的功能。
首先,需要在 package.json
中配置以下內容:
"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
文件,內容如下:
// 重寫 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
文件,內容如下:
// 修改 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
服務的地址了,雖然看起來比方案一繁瑣,但是這種收益是長期的,如要新增一個 sonarqube
服務,同理實現即可,通過使用 package.json
也可以清楚的看到當前服務在不同環境下使用的地址。
總結 ????
本文介紹了 package.json
的多種常見的配置字段及作用,並通過例子加深大家對 package.json
這些字段的理解。
除了一些常用字段,還介紹了在React
項目中 package.json
文件能實現的一些功能進行介紹。
以上內容如有遺漏錯誤,歡迎留言 ✍️ 指出,一起進步 ????????????
如果覺得本文對你有幫助,???????? 留下你寶貴的 ????
參考資料 ????
Creating a package.json file
package.json bin的作用
在開發環境中代理 API 請求
react + typescript 項目的定製化過程
React學習筆記
❤️ 看完三件事
如果你覺得這篇內容對你挺有啓發,我想邀請你幫我三個小忙:
點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)
關注我的博客 https://github.com/SHERlocked93/blog,讓我們成爲長期關係
關注公衆號「前端下午茶」,持續爲你推送精選好文,也可以加我爲好友,隨時聊騷。
在看點這裏