常用的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 對象,對象的每一個成員就是當前項目的一項配置。

必備屬性(name & version)

  • package.json 中有非常多的配置項,其中必須填寫的兩個字段分別是 name 字段和 version 字段,它們是組成一個 npm 模塊的唯一標識

name 字段

name 字段定義了模塊的名稱,其命名時需要遵循官方的一些規範和建議:

    • 模塊名會成爲模塊 url、命令行中的一個參數或者一個文件夾名稱,任何非 url 安全的字符在模塊名中都不能使用(我們可以使用 validate-npm-package-name 包來檢測模塊名是否合法);

    • 語義化模塊名,可以幫助開發者更快的找到需要的模塊,並且避免意外獲取錯誤的模塊;

    • 若模塊名稱中存在一些符號,將符號去除後不得與現有的模塊名重複,例如:由於 react-router-dom 已經存在,react.router.domreactrouterdom 都不可以再創建。

name 字段不能與其他模塊名重複,我們可以執行以下命令查看模塊名是否已經被使用:

npm view <packageName>

如果模塊存在,可以查看該模塊的一些基本信息:

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

或者,我們也可以去 npm 上輸入模塊名,如果搜不到,則可以使用該模塊名。

version 字段

npm 包中的模塊版本都需要遵循 SemVer 規範,該規範的標準版本號採用 X.Y.Z 的格式,其中 XYZ 均爲非負的整數,且禁止在數字前方補零:

    • X 是主版本號(major):修改了不兼容的 API

    • Y 是次版本號(minor):新增了向下兼容的功能

    • Z 爲修訂號(patch):修正了向下兼容的問題

當某個版本改動比較大、並非穩定而且可能無法滿足預期的兼容性需求時,我們可能要先發佈一個先行版本

先行版本號可以加到主版本號.次版本號.修訂號的後面,通過 - 號連接一連串以句點分隔的標識符和版本編譯信息:

    • 內部版本(alpha)

    • 公測版本(beta)

    • 正式版本的候選版本rc(即 Release candiate)

我們可以執行以下命令查看模塊的版本:

npm view <packageName> version # 查看某個模塊的最新版本
npm view <packageName> versions # 查看某個模塊的所有歷史版本
複製代碼

查看 antd 的最新版本:

查看 antd 的所有歷史版本:

可以看到,antd 是嚴格按照 SemVer 規範來發版的,版本號是嚴格按照主版本號.次版本號.修訂號的格式命名和嚴格遞增的,在發佈的版本改動較大時,還會先發布alphabetarc等先行版本。

描述信息(description & keywords)

  • description 字段用於添加模塊的描述信息,便於用戶瞭解該模塊。

  • keywords 字段用於給模塊添加關鍵字。

  • 當我們使用 npm 檢索模塊時,會對模塊中的 description 字段和 keywords 字段進行匹配,寫好 package.json中的 descriptionkeywords 將有利於增加我們模塊的曝光率。

安裝項目依賴(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 文件。

發佈文件配置(files)

files 字段用於描述我們使用 npm publish 命令後推送到 npm 服務器的文件列表,如果指定文件夾,則文件夾內的所有內容都會包含進來。

我們可以查看下載的 antdpackage.jsonfiles 字段,內容如下:

"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.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 文件能實現的一些功能進行介紹。

以上內容如有遺漏錯誤,歡迎留言 ✍️ 指出,一起進步 ????????????

如果覺得本文對你有幫助,???????? 留下你寶貴的 ????

參考資料 ????

  1. Creating a package.json file

  2. package.json bin的作用

  3. 在開發環境中代理 API 請求

  4. react + typescript 項目的定製化過程

  5. React學習筆記

❤️ 看完三件事

如果你覺得這篇內容對你挺有啓發,我想邀請你幫我三個小忙:

  1. 點個「在看」,讓更多的人也能看到這篇內容(喜歡不點在看,都是耍流氓 -_-)

  2. 關注我的博客 https://github.com/SHERlocked93/blog,讓我們成爲長期關係

  3. 關注公衆號「前端下午茶」,持續爲你推送精選好文,也可以加我爲好友,隨時聊騷。

在看點這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章