從新認識 package.json

前言 🤔

  • 在每一個項目的根目錄下面,通常都會有一個 package.json 文件,其定義了運行項目所須要的各類依賴和項目的配置信息(如名稱、版本、許可證等元數據)。
  • 大多數人對 package.json 文件的瞭解,僅停留在:
    • 項目名稱、項目構建版本、許可證的定義;
    • 依賴定義(包括 dependencies 字段,devDependencies 字段);
    • 使用scripts字段指定運行腳本命令的 npm 命令行縮寫。
  • 其實,package.json 的做用遠不止於此,咱們能夠經過新增配置項實現更強大的功能,下面將帶你從新認識 package.json

由簡入繁,豐富項目的 package.json

簡單版的 package.json

  • 當咱們新建一個名稱爲 my-test 的項目時,使用 yarn init -ynpm 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 對象,對象的每個成員就是當前項目的一項配置。

安裝項目依賴(dependencies & devDependencies)

  • dependencies字段指定了項目運行所依賴的模塊(生產環境使用),如 antdreactmoment等插件庫:
    • 它們是咱們生產環境所須要的依賴項,在把項目做爲一個 npm 包的時候,用戶安裝 npm 包時只會安裝 dependencies 裏面的依賴。
  • devDependencies 字段指定了項目開發所須要的模塊(開發環境使用),如 webpacktypescriptbabel等:
    • 在代碼打包提交線上時,咱們並不須要這些工具,因此咱們將它放入 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 文件。

指定項目 node 版本(engines)

  • 有時候,新拉一個項目的時候,因爲和其餘開發使用的 node 版本不一樣,致使會出現不少奇奇怪怪的問題(如某些依賴安裝報錯、依賴安裝完項目跑步起來等)。
  • 爲了實現項目開箱即用的偉大理想,這時候可使用 package.jsonengines 字段來指定項目 node 版本:
"engines": {
   "node": ">= 8.16.0"
},
複製代碼
  • 該字段也能夠指定適用的 npm 版本:
"engines": {
   "npm": ">= 6.9.0"
 },
複製代碼
  • 須要注意的是,engines屬性僅起到一個說明的做用,當用戶版本不符合指定值時也不影響依賴的安裝。

自定義命令(bin)

  • 用過 vue-clicreate-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.jsconfig/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 文件能實現的一些功能進行介紹。

以上內容若有遺漏錯誤,歡迎留言 ✍️指出,一塊兒進步💪💪💪html

若是以爲本文對你有幫助,🏀🏀留下你寶貴的 👍vue

參考資料 📖

  1. Creating a package.json file
  2. package.json bin的做用
  3. 在開發環境中代理 API 請求
  4. react + typescript 項目的定製化過程
  5. React學習筆記
相關文章
相關標籤/搜索