博客 / 詳情

返回

npm init @vitejs/app的背後,僅是npm CLI的冰山一角

結尾的話説在前面。

我有時候會得出這樣的結論:原來那些我不常用的命令或工具,都是為了解決大佬們遇到的問題而存在的!

我們每天都和npm打交道,但是不少人對npm的掌握程度還停留在一個比較淺的層面(當然這也包括我)。就比如説一個用 vite 創建 app 的命令npm init @vitejs/app,很多人就懵了,“npm init不是用來創建package.json文件的嗎?”

同樣還有npx create-react-app my-app這樣的命令,懵嗎?

的確,這些命令背後還有一些我們很少關注的邏輯,雖然不難,但是我們卻沒有系統去了解過。

考慮到這些,最近我有系統地去學習npm,主要的學習方式是利用一些空餘時間,結合我之前的npm使用經驗,從npm官方文檔入手去排查一些知識盲點和疑惑。順着官方文檔一塊塊看下來,同時對不清楚的知識點進行資料查閲和驗證之後,雖然還有那麼一小部分知識點我幾乎沒用過,但是我的確對npm有了更多的認識。最後我也是以思維導圖的形式,把自己的一些學習所得簡單記錄下來。

思維導圖

經過這幾天的學習,我發現我學習npm的兩個大方向是npm CLIpackage.json

今天我想先聊聊npm CLICLI就是Command Line Interface,也就是我們説的命令行接口。npm提供了非常多的CLI,具體可以參考npm CLI commands。

Usage: npm <command>

where <command> is one of:
    access, adduser, audit, bin, bugs, c, cache, ci, cit,
    clean-install, clean-install-test, completion, config,
    create, ddp, dedupe, deprecate, dist-tag, docs, doctor,
    edit, explore, fund, get, help, help-search, hook, i, init,
    install, install-ci-test, install-test, it, link, list, ln,
    login, logout, ls, org, outdated, owner, pack, ping, prefix,
    profile, prune, publish, rb, rebuild, repo, restart, root,
    run, run-script, s, se, search, set, shrinkwrap, star,
    stars, start, stop, t, team, test, token, tst, un,
    uninstall, unpublish, unstar, up, update, v, version, view,
    whoami

npm <command> -h  quick help on <command>
npm -l            display full usage info
npm help <term>   search for help on <term>
npm help npm      involved overview

Specify configs in the ini-formatted file:
    C:\Users\Tusi\.npmrc
or on the command line via: npm <command> --key value
Config info can be viewed via: npm help config

命令太多,就不全部解釋一遍了。我篩選出了一些基礎的,同時也是我見得比較多的一些命令來簡單介紹下。

npm CLI常見命令

npm help

不懂就問,npm help是個好命令。就像我用git --help一樣,對於有些比較模糊的命令,我都會用help來查一下。

npm init

在我們初始化一個 npm 包,或者説創建 package.json 文件,就需要用到npm init

npm init xxx

雖然之前在創建vue或者react應用時,我都用到了npm init xxx,但我都沒怎麼關注npm init xxx背後發生了什麼。

比如npm init @vitejs/app,只知道官網説它是用來創建應用的,但很少會去想到其背後是調用了npx @vitejs/create-app,其實就是在執行一個create-app腳本。

這也就是説,如果你想讓別人通過npm init xxx命令調用你的包,就必須提供一個create-xxx腳本。

// 對於 yarn 來説
yarn create electron-app my-new-app
// 等價於
npx create-electron-app my-new-app
// 等價於
npm init electron-app my-new-app

npx

npx 用來運行本地或遠程npm包的一個命令。比如前面提到的npx @vitejs/create-app

如果 npx 請求的包(比如@vitejs/create-app)沒有出現在本地項目的依賴中,npm 就會把@vitejs/create-app安裝到全局的 npm cache 目錄下。

接着會執行create-app腳本,而這個腳本需要定義在package.jsonbin配置項下。

npm init xxxnpx create-xxx也是一般CLI工具的常用套路。

npm config

npm config是用來管理配置文件的,我們平時用的最多的是設置npm的源。

npm config set registry https://registry.npmmirror.com 

利用npm config list,我們可以列出所有的配置項;利用get, set, delete可以執行查詢,設置,刪除配置項的操作。

npm install / uninstall

npm install 不指定包時,會將 package.json 列出的依賴安裝到 node\_modules 中,如果指定包名,則安裝指定的包。主要注意:

  • -g是全局安裝;
  • 如果指定了 --production ,或者 NODE\_ENV 是 production,就不會安裝 devDependencies 中的依賴。
  • --save 等價於 -S,安裝的依賴包信息保存到 package.json中的 dependencies,這些依賴(比如vue, react)如果有進入 bundler (比如 webpack )的 Dependency Graph(依賴關係圖),會被打包到項目的構建結果中;npm install vue會默認執行-S的行為,但是建議顯示給出-S,給人的感覺會比較清晰。
  • --save-dev 等價於 -D,安裝的依賴包信息保存到 devDependencies 中,這些依賴一般是開發環境的工具,比如 eslint, webpack, babel之類的,這些依賴一般不會被打包工具處理到構建結果中。

但是,如果你使用npm install -D vue安裝了vue,並且在項目中引用了vue依賴,那麼 webpack 的 Dependency Graph 中也會有vue,最終vue也會體現到構建結果中;

看到這裏,是不是又懵逼了?不管是npm install -S vue還是npm install -D vue,如果項目中引用了vue,都會把vue打包進構建結果,那麼-S-D有什麼區別?

注意了,webpack 不關心一個依賴是dependencies還是devDependencies,只要進入 webpack 的 Dependency Graph,就會打包到結果中。

所以我們不要被構建工具迷了眼,-S-D影響的是npm install,而且影響的也是有限的場景。

如果別人 install 你的包package-a,他會順便安裝package-a中的dependencies,而不會去安裝package-a中的devDependencies

分兩方面來看

  • 第一種情況:生產依賴誤入開發依賴

假設你的包package-a通過package.jsonmodule字段提供了一個ESM入口。

"module": "module-entry.js"

module-entry.js裏面又依賴了一個包,假設是lodash-es吧。

// module-entry.js
import { cloneDeep } from "lodash-es"

但是,你沒注意你是通過npm install -D lodash-es安裝的,你在本地調試package-a時,沒有任何問題。於是,你發佈了這個package-a,同事小王安裝了package-a卻發現使用時報錯了(因為他不會自動安裝package-adevDependencies)。

  • 第二種情況:開發依賴誤入生產依賴

開發環境的依賴進入了生產環境,會導致構建時多了無意義的開發依賴,打包結果變大,這常發生於開發庫或組件時。

import VueAwesomeProgress from "./index.vue";

// 開發組件時,不必要的vue引入;
// 導致最終build的文件變大。
import Vue from "vue"
console.log(Vue)

VueAwesomeProgress.install = function(Vue) {
    Vue.component(VueAwesomeProgress.name, VueAwesomeProgress);
};

if (typeof window !== 'undefined' && window.Vue) {
    window.Vue.use(VueAwesomeProgress)
}

export default VueAwesomeProgress

實質上,我們在開發一個Vue組件時,僅僅需要把vue作為devDependencies即可。

npm start

npm start是一個語義化的命令。通常我們會在 scripts 中自定義 start 腳本,比如:

"start": "npm run dev"

如果沒有指定自定義的 start 腳本,npm start默認會執行:

node server.js

npm run

npm run用來運行我們定義的scripts,命令後直接跟腳本名稱就行。在npm run時,我們可以調用一些特殊路徑下的可執行文件或腳本,這些路徑包括環境變量PATH定義的路徑,也包括當前項目node_modules中的./bin

In addition to the shell's pre-existing PATH, npm run adds node\_modules/.bin to the PATH provided to scripts.

npm version

這個命令也是值得掌握的,從語義上看,npm version會修改package.json中的version字段,用來管理包的版本號。

你可以試着運行:

npm version major/minor/patch -m "reason for upgrade"

major/minor/patch三選一,分別代表主版本/次版本/補丁版本。當然也可以傳其他的版本參數,具體參考npm-version。

通常,我們還會定義一個自定義的 version 腳本,配合conventional-changelog用來自動生成CHANGELOG.md

{
  "scripts": {
    "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md"
  }
}

發包相關

npm login/ npm adduser

發佈一個 npm 包的流程並不複雜。首先你必須通過命令行登錄 npm,這用到了npm adduser,別名是npm login

確保你的代理正確

有時候,考慮到國內環境,我們安裝依賴時,會設置 npm 的源為淘寶鏡像。但是在發佈 npm 包之前,必須把源切回到 npm。

npm config set registry http://registry.npmjs.org/

npm publish

發佈一個npm包,發佈的界限是以 version 判斷的,不能發佈相同的 version。即便你只是改了一個README,也必須修改 version 才能重新 npm publish

npm unpublish

與發包對應的就是移除已發佈的包。你可以選擇移除整個已發佈的包,也可以針對性地下架某個版本。

npm pack

將package打包成 tgz 格式。舉個例子,在vue-awesome-progress目錄下,運行 npm pack 將得到一個 vue-awesome-progress-1.9.1.tgz,其中 1.9.1 是取自 version 字段。

然後通過 npm install vue-awesome-progress-1.9.1.tgz 會在當前目錄的 node\_moudles 目錄下安裝 vue-awesome-progress包及其相關依賴。

npm link

npm link用於創建一個符號鏈接,類似於 Linux 軟鏈接(ln -s)的效果。

首先需要在待創建 link 的包目錄(比如vue-awesome-progress)下運行 npm link,這會在 npm 全局文件夾下創建一個 symlink。

npm prefix -g指向 npm 全局文件夾,我這裏的值是:

PS C:\Users\Tusi> npm prefix -g
C:\Users\Tusi\AppData\Roaming\npm

npm link後,C:\Users\Tusi\AppData\Roaming\npm\node_modules\中就有一個vue-awesome-progress的目錄了,其實是一個快捷方式。

同時,如果 vue-awesome-progress 中有配置 bin 文件,也會被 link 到全局。

要用到 vue-awesome-progress 的地方可以通過npm link vue-awesome-progress安裝它,也會安裝到 node\_modules 下,不過是一個全量的 vue-awesome-progress,而非npm publish後的 vue-awesome-progress。

個人感覺,npm link 適合在本地對兩個及以上的包做調試用,這樣就不用每次調試問題時,還要重新 npm run build, npm publish,省去了很多事。

寫在結尾

當我們習慣了一個工具的常用功能時,很少會去想它背後發生了什麼,甚至更少會去思考它還有沒有其他能力。但是,當你有一定使用經驗後,再去深入瞭解它,你會感嘆:“卧槽!原來這個命令是用來解決這個問題的,大佬們果然還是考慮得全面!”

就像我開頭説的那樣,一個人如果想在技術領域進階,一定要給自己提出足夠多的問題,帶着問題去深入。至於如何帶着問題深入,我覺得最好是做一個自己的產品,可以是項目,也可以是組件,或者是 library,甚至是 framework。遇到困惑時,如果你發現大佬們給瞭解決方案,你會驚喜;如果沒有,你來成為大佬!

如果您覺得這篇文章還不錯,歡迎點個贊,加個關注(程序員白彬),真誠感謝您的支持。也歡迎和我直接交流,期待與您共同進步!

user avatar tigerandflower 頭像 ailim 頭像 gaoming13 頭像 mulander 頭像 waweb 頭像 frontoldman 頭像 musicfe 頭像 light_5cfbb652e97ce 頭像 cipchk 頭像 mmmy_a 頭像 shangxindi 頭像 moziyu 頭像
35 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.