摘要:無(wú)需手動(dòng)拷貝文件或者創(chuàng)建軟鏈接到目錄,有更優(yōu)雅的解決方案。這是因?yàn)樽R(shí)別協(xié)議的,得知這個(gè)包需要直接從文件系統(tǒng)中獲取,會(huì)自動(dòng)創(chuàng)建軟鏈接到中,完成安裝過(guò)程。
nodejs 社區(qū)乃至 Web 前端工程化領(lǐng)域發(fā)展到今天,作為 node 自帶的包管理工具的 npm 已經(jīng)成為每個(gè)前端開(kāi)發(fā)者必備的工具。但是現(xiàn)實(shí)狀況是,我們很多人對(duì)這個(gè)nodejs基礎(chǔ)設(shè)施的使用和了解還停留在: 會(huì)用 npm install 這里(一言不合就刪除整個(gè) node_modules 目錄然后重新 install 這種事你沒(méi)做過(guò)嗎?)
當(dāng)然 npm 能成為現(xiàn)在世界上最大規(guī)模的包管理系統(tǒng),很大程度上確實(shí)歸功于它足夠用戶(hù)友好,你看即使我只會(huì)執(zhí)行 install 也不必太擔(dān)心出什么大岔子. 但是 npm 的功能遠(yuǎn)不止于 install 一下那么簡(jiǎn)單,這篇文章幫你扒一扒那些你可能不知道的 npm 原理、特性、技巧,以及(我認(rèn)為的)最佳實(shí)踐。
你懶得讀的 npm 文檔,我?guī)湍惴g然后試驗(yàn)整理過(guò)來(lái)了 ???
我們都知道 package.json 文件是用來(lái)定義一個(gè) package 的描述文件, 也知道npm init 命令用來(lái)初始化一個(gè)簡(jiǎn)單的 package.json 文件,執(zhí)行該命令后終端會(huì)依次詢(xún)問(wèn) name, version, description 等字段。
1.1 npm init 執(zhí)行默認(rèn)行為
而如果想要偷懶步免去一直按 enter,在命令后追加 --yes 參數(shù)即可,其作用與一路下一步相同。
npm init --yes
1.2 自定義 npm init 行為npm init 命令的原理并不復(fù)雜,調(diào)用腳本,輸出一個(gè)初始化的 package.json 文件就是了。所以相應(yīng)地,定制 npm init 命令的實(shí)現(xiàn)方式也很簡(jiǎn)單,在 Home 目錄創(chuàng)建一個(gè) .npm-init.js 即可,該文件的 module.exports 即為 package.json 配置內(nèi)容,需要獲取用戶(hù)輸入時(shí)候,使用 prompt() 方法即可。
例如編寫(xiě)這樣的 ~/.npm-init.js
const desc = prompt("description?", "A new package...") const bar = prompt("bar?", "") const count = prompt("count?", "42") module.exports = { key: "value", foo: { bar: bar, count: count }, name: prompt("name?", process.cwd().split("/").pop()), version: prompt("version?", "0.1.0"), description: desc, main: "index.js", }
此時(shí)在 ~/hello 目錄下執(zhí)行 npm init 將會(huì)得到這樣的 package.json:
{ "key": "value", "foo": { "bar": "", "count": "42" }, "name": "hello", "version": "0.1.0", "description": "A new package...", "main": "index.js" }
除了生成 package.json, 因?yàn)?.npm-init.js 是一個(gè)常規(guī)的模塊,意味著我們可以執(zhí)行隨便什么 node 腳本可以執(zhí)行的任務(wù)。例如通過(guò) fs 創(chuàng)建 README, .eslintrc 等項(xiàng)目必需文件,實(shí)現(xiàn)項(xiàng)目腳手架的作用。
2. 依賴(lài)包安裝依賴(lài)管理是 npm 的核心功能,原理就是執(zhí)行 npm install 從 package.json 中的 dependencies, devDependencies 將依賴(lài)包安裝到當(dāng)前目錄的 ./node_modules 文件夾中。
2.1 package定義我們都知道要手動(dòng)安裝一個(gè)包時(shí),執(zhí)行 npm install
閱讀 npm的文檔, 我們會(huì)發(fā)現(xiàn)package 準(zhǔn)確的定義,只要符合以下 a) 到 g) 其中之一條件,就是一個(gè) package:
# | 說(shuō)明 | 例子 |
---|---|---|
a) | 一個(gè)包含了程序和描述該程序的 package.json 文件 的 文件夾 | ./local-module/ |
b) | 一個(gè)包含了 (a) 的 gzip 壓縮文件 | ./module.tar.gz |
c) | 一個(gè)可以下載得到 (b) 資源的 url (通常是 http(s) url) | https://registry.npmjs.org/we... |
d) | 一個(gè)格式為 |
webpack@4.1.0 |
e) | 一個(gè)格式為 |
webpack@latest |
f) | 一個(gè)格式為 |
webpack |
g) | 一個(gè) git url, 該 url 所指向的代碼庫(kù)滿(mǎn)足條件 (a) | git@github.com:webpack/webpack.git |
上面表格的定義意味著,我們?cè)诠蚕硪蕾?lài)包時(shí),并不是非要將包發(fā)表到 npm 源上才可以提供給使用者來(lái)安裝。這對(duì)于私有的不方便 publish 到遠(yuǎn)程源(即使是私有源),或者需要對(duì)某官方源進(jìn)行改造,但依然需要把包共享出去的場(chǎng)景來(lái)說(shuō)非常實(shí)用。
場(chǎng)景1: 本地模塊引用
nodejs 應(yīng)用開(kāi)發(fā)中不可避免有模塊間調(diào)用,例如在實(shí)踐中經(jīng)常會(huì)把需要被頻繁引用的配置模塊放到應(yīng)用根目錄;于是在創(chuàng)建了很多層級(jí)的目錄、文件后,很可能會(huì)遇到這樣的代碼:
const config = require("../../../../config.js");
除了看上去很丑以外,這樣的路徑引用也不利于代碼的重構(gòu)。并且身為程序員的自我修養(yǎng)告訴我們,這樣重復(fù)的代碼多了也就意味著是時(shí)候把這個(gè)模塊分離出來(lái)供應(yīng)用內(nèi)其他模塊共享了。例如這個(gè)例子里的 config.js 非常適合封裝為 package 放到 node_modules 目錄下,共享給同應(yīng)用內(nèi)其他模塊。
無(wú)需手動(dòng)拷貝文件或者創(chuàng)建軟鏈接到 node_modules 目錄,npm 有更優(yōu)雅的解決方案。
方案:
創(chuàng)建 config 包:
新增 config 文件夾; 重命名 config.js 為 config/index.js 文件; 創(chuàng)建 package.json 定義 config 包
{ "name": "config", "main": "index.js", "version": "0.1.0" }
在應(yīng)用層 package.json 文件中新增依賴(lài)項(xiàng),然后執(zhí)行 npm install; 或直接執(zhí)行第 3 步
{ "dependencies": { "config": "file:./config" } }
(等價(jià)于第 2 步)直接在應(yīng)用目錄執(zhí)行 npm install file:./config
此時(shí),查看 node_modules 目錄我們會(huì)發(fā)現(xiàn)多出來(lái)一個(gè)名為 config,指向上層 config/ 文件夾的軟鏈接。這是因?yàn)?npm 識(shí)別 file: 協(xié)議的url,得知這個(gè)包需要直接從文件系統(tǒng)中獲取,會(huì)自動(dòng)創(chuàng)建軟鏈接到 node_modules 中,完成“安裝”過(guò)程。
相比手動(dòng)軟鏈,我們既不需要關(guān)心 windows 和 linux 命令差異,又可以顯式地將依賴(lài)信息固化到 dependencies 字段中,開(kāi)發(fā)團(tuán)隊(duì)其他成員可以執(zhí)行 npm install 后直接使用。
場(chǎng)景2: 私有 git 共享 package
有些時(shí)候,我們一個(gè)團(tuán)隊(duì)內(nèi)會(huì)有一些代碼/公用庫(kù)需要在團(tuán)隊(duì)內(nèi)不同項(xiàng)目間共享,但可能由于包含了敏感內(nèi)容,或者代碼太爛拿不出手等原因,不方便發(fā)布到源。
這種情況下,我們可以簡(jiǎn)單地將被依賴(lài)的包托管在私有的 git 倉(cāng)庫(kù)中,然后將該 git url 保存到 dependencies 中. npm 會(huì)直接調(diào)用系統(tǒng)的 git 命令從 git 倉(cāng)庫(kù)拉取包的內(nèi)容到 node_modules 中。
npm 支持的 git url 格式:
://[ [: ]@] [: ][:][/] [# | #semver: ]
git 路徑后可以使用 # 指定特定的 git branch/commit/tag, 也可以 #semver: 指定特定的 semver range.
例如:
git+ssh://git@github.com:npm/npm.git#v1.0.27 git+ssh://git@github.com:npm/npm#semver:^5.0 git+https://isaacs@github.com/npm/npm.git git://github.com/npm/npm.git#v1.0.27
場(chǎng)景3: 開(kāi)源 package 問(wèn)題修復(fù)
使用某個(gè) npm 包時(shí)發(fā)現(xiàn)它有某個(gè)嚴(yán)重bug,但也許最初作者已不再維護(hù)代碼了,也許我們工作緊急,沒(méi)有足夠的時(shí)間提 issue 給作者再慢慢等作者發(fā)布新的修復(fù)版本到 npm 源。
此時(shí)我們可以手動(dòng)進(jìn)入 node_modules 目錄下修改相應(yīng)的包內(nèi)容,也許修改了一行代碼就修復(fù)了問(wèn)題。但是這種做法非常不明智!
首先 node_modules 本身不應(yīng)該放進(jìn)版本控制系統(tǒng),對(duì) node_modules 文件夾中內(nèi)容的修改不會(huì)被記錄進(jìn) git 提交記錄;其次,就算我們非要反模式,把 node_modules 放進(jìn)版本控制中,你的修改內(nèi)容也很容易在下次 team 中某位成員執(zhí)行 npm install 或 npm update 時(shí)被覆蓋,而這樣的一次提交很可能包含了幾十幾百個(gè)包的更新,你自己所做的修改很容易就被淹沒(méi)在龐大的 diff 文件列表中了。
方案:
最好的辦法應(yīng)當(dāng)是 fork 原作者的 git 庫(kù),在自己所屬的 repo 下修復(fù)問(wèn)題后,將 dependencies 中相應(yīng)的依賴(lài)項(xiàng)更改為自己修復(fù)后版本的 git url 即可解決問(wèn)題。(Fork 代碼庫(kù)后,也便于向原作者提交 PR 修復(fù)問(wèn)題。上游代碼庫(kù)修復(fù)問(wèn)題后,再次更新我們的依賴(lài)配置也不遲。)
3. npm install 如何工作 —— node_modules 目錄結(jié)構(gòu)npm install 執(zhí)行完畢后,我們可以在 node_modules 中看到所有依賴(lài)的包。雖然使用者無(wú)需關(guān)注這個(gè)目錄里的文件夾結(jié)構(gòu)細(xì)節(jié),只管在業(yè)務(wù)代碼中引用依賴(lài)包即可,但了解 node_modules 的內(nèi)容可以幫我們更好理解 npm 如何工作,了解從 npm 2 到 npm 5 有哪些變化和改進(jìn)。
為簡(jiǎn)單起見(jiàn),我們假設(shè)應(yīng)用目錄為 app, 用兩個(gè)流行的包 webpack, nconf 作為依賴(lài)包做示例說(shuō)明。并且為了正常安裝,使用了“上古” npm 2 時(shí)期的版本 webpack@1.15.0, nconf@0.8.5.
3.1 npm 2npm 2 在安裝依賴(lài)包時(shí),采用簡(jiǎn)單的遞歸安裝方法。執(zhí)行 npm install 后,npm 2 依次遞歸安裝 webpack 和 nconf 兩個(gè)包到 node_modules 中。執(zhí)行完畢后,我們會(huì)看到 ./node_modules 這層目錄只含有這兩個(gè)子目錄。
node_modules/ ├── nconf/ └── webpack/
進(jìn)入更深一層 nconf 或 webpack 目錄,將看到這兩個(gè)包各自的 node_modules 中,已經(jīng)由 npm 遞歸地安裝好自身的依賴(lài)包。包括 ./node_modules/webpack/node_modules/webpack-core , ./node_modules/conf/node_modules/async 等等。而每一個(gè)包都有自己的依賴(lài)包,每個(gè)包自己的依賴(lài)都安裝在了自己的 node_modules 中。依賴(lài)關(guān)系層層遞進(jìn),構(gòu)成了一整個(gè)依賴(lài)樹(shù),這個(gè)依賴(lài)樹(shù)與文件系統(tǒng)中的文件結(jié)構(gòu)樹(shù)剛好層層對(duì)應(yīng)。
最方便的查看依賴(lài)樹(shù)的方式是直接在 app 目錄下執(zhí)行 npm ls 命令。
app@0.1.0 ├─┬ nconf@0.8.5 │ ├── async@1.5.2 │ ├── ini@1.3.5 │ ├── secure-keys@1.0.0 │ └── yargs@3.32.0 └─┬ webpack@1.15.0 ├── acorn@3.3.0 ├── async@1.5.2 ├── clone@1.0.3 ├── ... ├── optimist@0.6.1 ├── supports-color@3.2.3 ├── tapable@0.1.10 ├── uglify-js@2.7.5 ├── watchpack@0.2.9 └─┬ webpack-core@0.6.9 ├── source-list-map@0.1.8 └── source-map@0.4.4
這樣的目錄結(jié)構(gòu)優(yōu)點(diǎn)在于層級(jí)結(jié)構(gòu)明顯,便于進(jìn)行傻瓜式的管理:
例如新裝一個(gè)依賴(lài)包,可以立即在第一層 node_modules 中看到子目錄
在已知所需包名和版本號(hào)時(shí),甚至可以從別的文件夾手動(dòng)拷貝需要的包到 node_modules 文件夾中,再手動(dòng)修改 package.json 中的依賴(lài)配置
要?jiǎng)h除這個(gè)包,也可以簡(jiǎn)單地手動(dòng)刪除這個(gè)包的子目錄,并刪除 package.json 文件中相應(yīng)的一行即可
實(shí)際上,很多人在 npm 2 時(shí)代也的確都這么實(shí)踐過(guò),的確也都可以安裝和刪除成功,并不會(huì)導(dǎo)致什么差錯(cuò)。
但這樣的文件結(jié)構(gòu)也有很明顯的問(wèn)題:
對(duì)復(fù)雜的工程, node_modules 內(nèi)目錄結(jié)構(gòu)可能會(huì)太深,導(dǎo)致深層的文件路徑過(guò)長(zhǎng)而觸發(fā) windows 文件系統(tǒng)中,文件路徑不能超過(guò) 260 個(gè)字符長(zhǎng)的錯(cuò)誤
部分被多個(gè)包所依賴(lài)的包,很可能在應(yīng)用 node_modules 目錄中的很多地方被重復(fù)安裝。隨著工程規(guī)模越來(lái)越大,依賴(lài)樹(shù)越來(lái)越復(fù)雜,這樣的包情況會(huì)越來(lái)越多,造成大量的冗余。
——在我們的示例中就有這個(gè)問(wèn)題,webpack 和 nconf 都依賴(lài) async 這個(gè)包,所以在文件系統(tǒng)中,webpack 和 nconf 的 node_modules 子目錄中都安裝了相同的 async 包,并且是相同的版本。
+-------------------------------------------+ | app/ | +----------+------------------------+-------+ | | | | +----------v------+ +---------v-------+ | | | | | webpack@1.15.0 | | nconf@0.8.5 | | | | | +--------+--------+ +--------+--------+ | | +-----v-----+ +-----v-----+ |async@1.5.2| |async@1.5.2| +-----------+ +-----------+3.2 npm 3 - 扁平結(jié)構(gòu)
主要為了解決以上問(wèn)題,npm 3 的 node_modules 目錄改成了更加扁平狀的層級(jí)結(jié)構(gòu)。文件系統(tǒng)中 webpack, nconf, async 的層級(jí)關(guān)系變成了平級(jí)關(guān)系,處于同一級(jí)目錄中。
+-------------------------------------------+ | app/ | +-+---------------------------------------+-+ | | | | +----------v------+ +-------------+ +---------v-------+ | | | | | | | webpack@1.15.0 | | async@1.5.2 | | nconf@0.8.5 | | | | | | | +-----------------+ +-------------+ +-----------------+
雖然這樣一來(lái) webpack/node_modules 和 nconf/node_modules 中都不再有 async 文件夾,但得益于 node 的模塊加載機(jī)制,他們都可以在上一級(jí) node_modules 目錄中找到 async 庫(kù)。所以 webpack 和 nconf 的庫(kù)代碼中 require("async") 語(yǔ)句的執(zhí)行都不會(huì)有任何問(wèn)題。
這只是最簡(jiǎn)單的例子,實(shí)際的工程項(xiàng)目中,依賴(lài)樹(shù)不可避免地會(huì)有很多層級(jí),很多依賴(lài)包,其中會(huì)有很多同名但版本不同的包存在于不同的依賴(lài)層級(jí),對(duì)這些復(fù)雜的情況, npm 3 都會(huì)在安裝時(shí)遍歷整個(gè)依賴(lài)樹(shù),計(jì)算出最合理的文件夾安裝方式,使得所有被重復(fù)依賴(lài)的包都可以去重安裝。
npm 文檔提供了更直觀的例子解釋這種情況:
假如 package{dep} 寫(xiě)法代表包和包的依賴(lài),那么 A{B,C}, B{C}, C{D} 的依賴(lài)結(jié)構(gòu)在安裝之后的 node_modules 是這樣的結(jié)構(gòu):
A +-- B +-- C +-- D
這里之所以 D 也安裝到了與 B C 同一級(jí)目錄,是因?yàn)?npm 會(huì)默認(rèn)會(huì)在無(wú)沖突的前提下,盡可能將包安裝到較高的層級(jí)。
如果是 A{B,C}, B{C,D@1}, C{D@2} 的依賴(lài)關(guān)系,得到的安裝后結(jié)構(gòu)是:
A +-- B +-- C `-- D@2 +-- D@1
這里是因?yàn)椋瑢?duì)于 npm 來(lái)說(shuō)同名但不同版本的包是兩個(gè)獨(dú)立的包,而同層不能有兩個(gè)同名子目錄,所以其中的 D@2 放到了 C 的子目錄而另一個(gè) D@1 被放到了再上一層目錄。
很明顯在 npm 3 之后 npm 的依賴(lài)樹(shù)結(jié)構(gòu)不再與文件夾層級(jí)一一對(duì)應(yīng)了。想要查看 app 的直接依賴(lài)項(xiàng),要通過(guò) npm ls 命令指定 --depth 參數(shù)來(lái)查看:
npm ls --depth 1
PS: 與本地依賴(lài)包不同,如果我們通過(guò) npm install --global 全局安裝包到全局目錄時(shí),得到的目錄依然是“傳統(tǒng)的”目錄結(jié)構(gòu)。而如果使用 npm 3 想要得到“傳統(tǒng)”形式的本地 node_modules 目錄,使用 npm install --global-style 命令即可。3.3 npm 5 - package-lock 文件
npm 5 發(fā)布于 2017 年也是目前最新的 npm 版本,這一版本依然沿用 npm 3 之后扁平化的依賴(lài)包安裝方式,此外最大的變化是增加了 package-lock.json 文件。
package-lock.json 的作用是鎖定依賴(lài)安裝結(jié)構(gòu),如果查看這個(gè) json 的結(jié)構(gòu),會(huì)發(fā)現(xiàn)與 node_modules 目錄的文件層級(jí)結(jié)構(gòu)是一一對(duì)應(yīng)的。
以依賴(lài)關(guān)系為: app{webpack} 的 "app" 項(xiàng)目為例, 其 package-lock 文件包含了這樣的片段。
{ "name": "app", "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { // ... 其他依賴(lài)包 "webpack": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.8.11.tgz", "integrity": "sha1-Yu0hnstBy/qcKuanu6laSYtgkcI=", "requires": { "async": "0.9.2", "clone": "0.1.19", "enhanced-resolve": "0.8.6", "esprima": "1.2.5", "interpret": "0.5.2", "memory-fs": "0.2.0", "mkdirp": "0.5.1", "node-libs-browser": "0.4.3", "optimist": "0.6.1", "supports-color": "1.3.1", "tapable": "0.1.10", "uglify-js": "2.4.24", "watchpack": "0.2.9", "webpack-core": "0.6.9" } }, "webpack-core": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", "requires": { "source-list-map": "0.1.8", "source-map": "0.4.4" }, "dependencies": { "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "requires": { "amdefine": "1.0.1" } } } }, //... 其他依賴(lài)包 } }
看懂 package-lock 文件并不難,其結(jié)構(gòu)是同樣類(lèi)型的幾個(gè)字段嵌套起來(lái)的,主要是 version, resolved, integrity, requires, dependencies 這幾個(gè)字段而已。
version, resolved, integrity 用來(lái)記錄包的準(zhǔn)確版本號(hào)、內(nèi)容hash、安裝源的,決定了要安裝的包的準(zhǔn)確“身份”信息
假設(shè)蓋住其他字段,只關(guān)注文件中的 dependencies: {} 我們會(huì)發(fā)現(xiàn),整個(gè)文件的 JSON 配置里的 dependencies 層次結(jié)構(gòu)與文件系統(tǒng)中 node_modules 的文件夾層次結(jié)構(gòu)是完全對(duì)照的
只關(guān)注 requires: {} 字段又會(huì)發(fā)現(xiàn),除最外層的 requires 屬性為 true 以外, 其他層的 requires 屬性都對(duì)應(yīng)著這個(gè)包的 package.json 里記錄的自己的依賴(lài)項(xiàng)
因?yàn)檫@個(gè)文件記錄了 node_modules 里所有包的結(jié)構(gòu)、層級(jí)和版本號(hào)甚至安裝源,它也就事實(shí)上提供了 “保存” node_modules 狀態(tài)的能力。只要有這樣一個(gè) lock 文件,不管在那一臺(tái)機(jī)器上執(zhí)行 npm install 都會(huì)得到完全相同的 node_modules 結(jié)果。
這就是 package-lock 文件致力于優(yōu)化的場(chǎng)景:在從前僅僅用 package.json 記錄依賴(lài),由于 semver range 的機(jī)制;一個(gè)月前由 A 生成的 package.json 文件,B 在一個(gè)月后根據(jù)它執(zhí)行 npm install 所得到的 node_modules 結(jié)果很可能許多包都存在不同的差異,雖然 semver 機(jī)制的限制使得同一份 package.json 不會(huì)得到大版本不同的依賴(lài)包,但同一份代碼在不同環(huán)境安裝出不同的依賴(lài)包,依然是可能導(dǎo)致意外的潛在因素。
相同作用的文件在 npm 5 之前就有,稱(chēng)為 npm shrinkwrap 文件,二者作用完全相同,不同的是后者需要手動(dòng)生成,而 npm 5 默認(rèn)會(huì)在執(zhí)行 npm install 后就生成 package-lock 文件,并且建議你提交到 git/svn 代碼庫(kù)中。
package-lock.json 文件在最初 npm 5.0 默認(rèn)引入時(shí)也引起了相當(dāng)大的爭(zhēng)議。在 npm 5.0 中,如果已有 package-lock 文件存在,若手動(dòng)在 package.json 文件新增一條依賴(lài),再執(zhí)行 npm install, 新增的依賴(lài)并不會(huì)被安裝到 node_modules 中, package-lock.json 也不會(huì)做相應(yīng)的更新。這樣的表現(xiàn)與使用者的自然期望表現(xiàn)不符。在 npm 5.1 的首個(gè) Release 版本中這個(gè)問(wèn)題得以修復(fù)。這個(gè)事情告訴我們,要升級(jí),不要使用 5.0。
——但依然有反對(duì)的聲音認(rèn)為 package-lock 太復(fù)雜,對(duì)此 npm 也提供了禁用配置:
npm config set package-lock false4. 依賴(lài)包版本管理
依賴(lài)包安裝完并不意味著就萬(wàn)事大吉了,版本的維護(hù)和更新也很重要。這一章介紹依賴(lài)包升級(jí)管理相關(guān)知識(shí),太長(zhǎng)不看版本請(qǐng)直接跳到 4.3 最佳實(shí)踐
4.1 semvernpm 依賴(lài)管理的一個(gè)重要特性是采用了語(yǔ)義化版本 (semver) 規(guī)范,作為依賴(lài)版本管理方案。
semver 約定一個(gè)包的版本號(hào)必須包含3個(gè)數(shù)字,格式必須為 MAJOR.MINOR.PATCH, 意為 主版本號(hào).小版本號(hào).修訂版本號(hào).
MAJOR 對(duì)應(yīng)大的版本號(hào)迭代,做了不兼容舊版的修改時(shí)要更新 MAJOR 版本號(hào)
MINOR 對(duì)應(yīng)小版本迭代,發(fā)生兼容舊版API的修改或功能更新時(shí),更新MINOR版本號(hào)
PATCH 對(duì)應(yīng)修訂版本號(hào),一般針對(duì)修復(fù) BUG 的版本號(hào)
對(duì)于包作者(發(fā)布者),npm 要求在 publish 之前,必須更新版本號(hào)。npm 提供了 npm version 工具,執(zhí)行 npm version major|minor|patch 可以簡(jiǎn)單地將版本號(hào)中相應(yīng)的數(shù)字加1.
如果包是一個(gè) git 倉(cāng)庫(kù),npm version 還會(huì)自動(dòng)創(chuàng)建一條注釋為更新后版本號(hào)的 git commit 和名為該版本號(hào)的 tag
對(duì)于包的引用者來(lái)說(shuō),我們需要在 dependencies 中使用 semver 約定的 semver range 指定所需依賴(lài)包的版本號(hào)或版本范圍。npm 提供了網(wǎng)站 https://semver.npmjs.com 可方便地計(jì)算所輸入的表達(dá)式的匹配范圍。常用的規(guī)則示例如下表:
range | 含義 | 例 |
---|---|---|
^2.2.1 | 指定的 MAJOR 版本號(hào)下, 所有更新的版本 | 匹配 2.2.3, 2.3.0; 不匹配 1.0.3, 3.0.1 |
~2.2.1 | 指定 MAJOR.MINOR 版本號(hào)下,所有更新的版本 | 匹配 2.2.3, 2.2.9 ; 不匹配 2.3.0, 2.4.5 |
>=2.1 | 版本號(hào)大于或等于 2.1.0 | 匹配 2.1.2, 3.1 |
<=2.2 | 版本號(hào)小于或等于 2.2 | 匹配 1.0.0, 2.2.1, 2.2.11 |
1.0.0 - 2.0.0 | 版本號(hào)從 1.0.0 (含) 到 2.0.0 (含) | 匹配 1.0.0, 1.3.4, 2.0.0 |
任意兩條規(guī)則,通過(guò) || 連接起來(lái),則表示兩條規(guī)則的并集:
如 ^2 >=2.3.1 || ^3 >3.2 可以匹配:
* `2.3.1`, `2,8.1`, `3.3.1` * 但不匹配 `1.0.0`, `2.2.0`, `3.1.0`, `4.0.0`
PS: 除了這幾種,還有如下更直觀的表示版本號(hào)范圍的寫(xiě)法:
* 或 x 匹配所有主版本
1 或 1.x 匹配 主版本號(hào)為 1 的所有版本
1.2 或 1.2.x 匹配 版本號(hào)為 1.2 開(kāi)頭的所有版本
PPS: 在常規(guī)僅包含數(shù)字的版本號(hào)之外,semver 還允許在 MAJOR.MINOR.PATCH 后追加 - 后跟點(diǎn)號(hào)分隔的標(biāo)簽,作為預(yù)發(fā)布版本標(biāo)簽 - Prerelese Tags,通常被視為不穩(wěn)定、不建議生產(chǎn)使用的版本。例如:
1.0.0-alpha
1.0.0-beta.1
1.0.0-rc.3
上表中我們最常見(jiàn)的是 ^1.8.11 這種格式的 range, 因?yàn)槲覀冊(cè)谑褂?npm install
問(wèn)題來(lái)了,在安裝完一個(gè)依賴(lài)包之后有新版本發(fā)布了,如何使用 npm 進(jìn)行版本升級(jí)呢?——答案是簡(jiǎn)單的 npm install 或 npm update,但在不同的 npm 版本,不同的 package.json, package-lock.json 文件,安裝/升級(jí)的表現(xiàn)也不同。
我們不妨還以 webpack 舉例,做如下的前提假設(shè):
我們的工程項(xiàng)目 app 依賴(lài) webpack
項(xiàng)目最初初始化時(shí),安裝了當(dāng)時(shí)最新的包 webpack@1.8.0,并且 package.json 中的依賴(lài)配置為: "webpack": "^1.8.0"
當(dāng)前(2018年3月) webpack 最新版本為 4.2.0, webpack 1.x 最新子版本為 1.15.0
如果我們使用的是 npm 3, 并且項(xiàng)目不含 package-lock.json, 那么根據(jù) node_modules 是否為空,執(zhí)行 install/update 的結(jié)果如下 (node 6.13.1, npm 3.10.10 環(huán)境下試驗(yàn)):
# | package.json (BEFORE) | node_modules (BEFORE) | command (npm 3) | package.json (AFTER) | node_modules (AFTER) |
---|---|---|---|---|---|
a) | webpack: ^1.8.0 | webpack@1.8.0 | install | webpack: ^1.8.0 | webpack@1.8.0 |
b) | webpack: ^1.8.0 | 空 | install | webpack: ^1.8.0 | webpack@1.15.0 |
c) | webpack: ^1.8.0 | webpack@1.8.0 | update | webpack: ^1.8.0 | webpack@1.15.0 |
d) | webpack: ^1.8.0 | 空 | update | webpack: ^1.8.0 | webpack@1.15.0 |
根據(jù)這個(gè)表我們可以對(duì) npm 3 得出以下結(jié)論:
如果本地 node_modules 已安裝,再次執(zhí)行 install 不會(huì)更新包版本, 執(zhí)行 update 才會(huì)更新; 而如果本地 node_modules 為空時(shí),執(zhí)行 install/update 都會(huì)直接安裝更新包;
npm update 總是會(huì)把包更新到符合 package.json 中指定的 semver 的最新版本號(hào)——本例中符合 ^1.8.0 的最新版本為 1.15.0
一旦給定 package.json, 無(wú)論后面執(zhí)行 npm install 還是 update, package.json 中的 webpack 版本一直頑固地保持 一開(kāi)始的 ^1.8.0 巋然不動(dòng)
這里不合理的地方在于,如果最開(kāi)始團(tuán)隊(duì)中第一個(gè)人安裝了 webpack@1.8.0, 而新加入項(xiàng)目的成員, checkout 工程代碼后執(zhí)行 npm install 會(huì)安裝得到不太一樣的 1.15.0 版本。雖然 semver 約定了小版本號(hào)應(yīng)當(dāng)保持向下兼容(相同大版本號(hào)下的小版本號(hào))兼容,但萬(wàn)一有不熟悉不遵循此約定的包發(fā)布者,發(fā)布了不兼容的包,此時(shí)就可能出現(xiàn)因依賴(lài)環(huán)境不同導(dǎo)致的 bug。
下面由 npm 5 帶著 package-lock.json 閃亮登場(chǎng),執(zhí)行 install/update 的效果是這樣的 (node 9.8.0, npm 5.7.1 環(huán)境下試驗(yàn)):
下表為表述簡(jiǎn)單,省略了包名 webpack, install 簡(jiǎn)寫(xiě) i, update 簡(jiǎn)寫(xiě)為 up
# | package.json (BEFORE) | node_modules (BEFORE) | package-lock (BEFORE) | command | package.json (AFTER) | node_modules (AFTER) |
---|---|---|---|---|---|---|
a) | ^1.8.0 | @1.8.0 | @1.8.0 | i | ^1.8.0 | @1.8.0 |
b) | ^1.8.0 | 空 | @1.8.0 | i | ^1.8.0 | @1.8.0 |
c) | ^1.8.0 | @1.8.0 | @1.8.0 | up | ^1.15.0 | @1.15.0 |
d) | ^1.8.0 | 空 | @1.8.0 | up | ^1.8.0 | @1.15.0 |
e) | ^1.15.0 | @1.8.0 (舊) | @1.15.0 | i | ^1.15.0 | @1.15.0 |
f) | ^1.15.0 | @1.8.0 (舊) | @1.15.0 | up | ^1.15.0 | @1.15.0 |
與 npm 3 相比,在安裝和更新依賴(lài)版本上主要的區(qū)別為:
無(wú)論何時(shí)執(zhí)行 install, npm 都會(huì)優(yōu)先按照 package-lock 中指定的版本來(lái)安裝 webpack; 避免了 npm 3 表中情形 b) 的狀況;
無(wú)論何時(shí)完成安裝/更新, package-lock 文件總會(huì)跟著 node_modules 更新 —— (因此可以視 package-lock 文件為 node_modules 的 JSON 表述)
已安裝 node_modules 后若執(zhí)行 npm update,package.json 中的版本號(hào)也會(huì)隨之更改為 ^1.15.0
由此可見(jiàn) npm 5.1 使得 package.json 和 package-lock.json 中所保存的版本號(hào)更加統(tǒng)一,解決了 npm 之前的各種問(wèn)題。只要遵循好的實(shí)踐習(xí)慣,團(tuán)隊(duì)成員可以很方便地維護(hù)一套應(yīng)用代碼和 node_modules 依賴(lài)都一致的環(huán)境。
皆大歡喜。
4.3 最佳實(shí)踐總結(jié)起來(lái),在 2018 年 (node 9.8.0, npm 5.7.1) 時(shí)代,我認(rèn)為的依賴(lài)版本管理應(yīng)當(dāng)是:
使用 npm: >=5.1 版本, 保持 package-lock.json 文件默認(rèn)開(kāi)啟配置
初始化:第一作者初始化項(xiàng)目時(shí)使用 npm install
初始化:項(xiàng)目成員首次 checkout/clone 項(xiàng)目代碼后,執(zhí)行一次 npm install 安裝依賴(lài)包
不要手動(dòng)修改 package-lock.json
升級(jí)依賴(lài)包:
升級(jí)小版本: 本地執(zhí)行 npm update 升級(jí)到新的小版本
升級(jí)大版本: 本地執(zhí)行 npm install
也可手動(dòng)修改 package.json 中版本號(hào)為要升級(jí)的版本(大于現(xiàn)有版本號(hào))并指定所需的 semver, 然后執(zhí)行 npm install
本地驗(yàn)證升級(jí)后新版本無(wú)問(wèn)題后,提交新的 package.json, package-lock.json 文件
降級(jí)依賴(lài)包:
正確: npm install
錯(cuò)誤: 手動(dòng)修改 package.json 中的版本號(hào)為更低版本的 semver, 這樣修改并不會(huì)生效,因?yàn)樵俅螆?zhí)行 npm install 依然會(huì)安裝 package-lock.json 中的鎖定版本
刪除依賴(lài)包:
Plan A: npm uninstall
Plan B: 把要卸載的包從 package.json 中 dependencies 字段刪除, 然后執(zhí)行 npm install 并提交 package.json 和 package-lock.json
任何時(shí)候有人提交了 package.json, package-lock.json 更新后,團(tuán)隊(duì)其他成員應(yīng)在 svn update/git pull 拉取更新后執(zhí)行 npm install 腳本安裝更新后的依賴(lài)包
恭喜你終于可以跟 rm -rf node_modules && npm install 這波操作說(shuō)拜拜了(其實(shí)并不會(huì))
npm scripts 是 npm 另一個(gè)很重要的特性。通過(guò)在 package.json 中 scripts 字段定義一個(gè)腳本,例如:
{ "scripts": { "echo": "echo HELLO WORLD" } }
我們就可以通過(guò) npm run echo 命令來(lái)執(zhí)行這段腳本,像在 shell 中執(zhí)行該命令 echo HELLO WORLD 一樣,看到終端輸出 HELLO WORLD.
—— npm scripts 的基本使用就是這么簡(jiǎn)單,它提供了一個(gè)簡(jiǎn)單的接口用來(lái)調(diào)用工程相關(guān)的腳本。關(guān)于更詳細(xì)的相關(guān)信息,可以參考阮一峰老師的文章 npm script 使用指南 (2016年10月).
簡(jiǎn)要總結(jié)阮老師文章內(nèi)容:
npm run 命令執(zhí)行時(shí),會(huì)把 ./node_modules/.bin/ 目錄添加到執(zhí)行環(huán)境的 PATH 變量中,因此如果某個(gè)命令行包未全局安裝,而只安裝在了當(dāng)前項(xiàng)目的 node_modules 中,通過(guò) npm run 一樣可以調(diào)用該命令。
執(zhí)行 npm 腳本時(shí)要傳入?yún)?shù),需要在命令后加 -- 標(biāo)明, 如 npm run test -- --grep="pattern" 可以將 --grep="pattern" 參數(shù)傳給 test 命令
npm 提供了 pre 和 post 兩種鉤子機(jī)制,可以定義某個(gè)腳本前后的執(zhí)行腳本
運(yùn)行時(shí)變量:在 npm run 的腳本執(zhí)行環(huán)境內(nèi),可以通過(guò)環(huán)境變量的方式獲取許多運(yùn)行時(shí)相關(guān)信息,以下都可以通過(guò) process.env 對(duì)象訪(fǎng)問(wèn)獲得:
npm_lifecycle_event - 正在運(yùn)行的腳本名稱(chēng)
npm_package_
npm_package_
上面所說(shuō)的 node_modules/.bin 目錄,保存了依賴(lài)目錄中所安裝的可供調(diào)用的命令行包。
何謂命令行包?例如 webpack 就屬于一個(gè)命令行包。如果我們?cè)诎惭b webpack 時(shí)添加 --global 參數(shù),就可以在終端直接輸入 webpack 進(jìn)行調(diào)用。但如果不加 --global 參數(shù),我們會(huì)在 node_modules/.bin 目錄里看到名為 webpack 的文件,如果在終端直接輸入 ./node_modules/.bin/webpack 命令,一樣可以執(zhí)行。
這是因?yàn)?webpack 在 package.json 文件中定義了 bin 字段為:
{ "bin": { "webpack": "./bin/webpack.js" } }
bin 字段的配置格式為:
正如上一節(jié)所說(shuō),npm run 命令在執(zhí)行時(shí)會(huì)把 ./node_modules/.bin 加入到 PATH 中,使我們可直接調(diào)用所有提供了命令行調(diào)用接口的依賴(lài)包。所以這里就引出了一個(gè)最佳實(shí)踐:
將項(xiàng)目依賴(lài)的命令行工具安裝到項(xiàng)目依賴(lài)文件夾中,然后通過(guò) npm scripts 調(diào)用;而非全局安裝
舉例而言 webpack 作為前端工程標(biāo)配的構(gòu)建工具,雖然我們都習(xí)慣了全局安裝并直接使用命令行調(diào)用,但不同的項(xiàng)目依賴(lài)的 webpack 版本可能不同,相應(yīng)的 webpack.config.js 配置文件也可能只兼容了特定版本的 webpack. 如果我們僅全局安裝了最新的 webpack 4.x 并使用 webpack 命令調(diào)用,在一個(gè)依賴(lài) webpack 3.x 的工程中就會(huì)無(wú)法成功執(zhí)行構(gòu)建。
但如果這類(lèi)工具總是本地安裝,我們要調(diào)用一個(gè)命令,要手動(dòng)添加 ./node_modules/.bin 這個(gè)長(zhǎng)長(zhǎng)的前綴,未免也太麻煩了,我們 nodejs 開(kāi)發(fā)者都很懶的。于是 npm 從5.2 開(kāi)始自帶了一個(gè)新的工具 npx.
5.3 npxnpx 的使用很簡(jiǎn)單,就是執(zhí)行 npx
除了這種最簡(jiǎn)單的場(chǎng)景, npm cli 團(tuán)隊(duì)開(kāi)發(fā)者 Kat Marchán 還在這篇文章中介紹了其他幾種 npx 的神奇用法: Introducing npx: an npm package runner, 國(guó)內(nèi)有位開(kāi)發(fā)者 robin.law 將原文翻譯為中文 npx是什么,為什么需要npx?.
有興趣的可以戳鏈接了解,懶得點(diǎn)鏈接的,看總結(jié):
場(chǎng)景a) 一鍵執(zhí)行遠(yuǎn)程 npm 源的二進(jìn)制包除了在 package 中執(zhí)行 ./node_modules/.bin 中已安裝的命令, 還可以直接指定未安裝的二進(jìn)制包名執(zhí)行。例如我們?cè)谝粋€(gè)沒(méi)有 package.json 也沒(méi)有 node_modules 的目錄下,執(zhí)行:
npx cowsay hello
npx 將會(huì)從 npm 源下載 cowsay 這個(gè)包(但并不安裝)并執(zhí)行:
_______ < hello > ------- ^__^ (oo)\_______ (__) )/ ||----w | || ||
這種用途非常適合 1. 在本地簡(jiǎn)單測(cè)試或調(diào)試 npm 源上這些二進(jìn)制包的功能;2. 調(diào)用 create-react-app 或 yeoman 這類(lèi)往往每個(gè)項(xiàng)目只需要使用一次的腳手架工具
PS: 此處有彩蛋,執(zhí)行這條命令試試:
npx workin-hard場(chǎng)景b) 一鍵執(zhí)行 GitHub Gist
還記得前面提到的 2.1 package定義 么,npm install
剛好 GitHub Gist 也是 git 倉(cāng)庫(kù) 的一種,集合 npx 就可以方便地將簡(jiǎn)單的腳本共享給其他人,擁有該鏈接的人無(wú)需將腳本安裝到本地工作目錄即可執(zhí)行。將 package.json 和 需執(zhí)行的二進(jìn)制腳本上傳至 gist, 在運(yùn)行 npx
原文作者 Kat Marchán 提供了這個(gè)示例 gist, 執(zhí)行:
npx https://gist.github.com/zkat/4bc19503fe9e9309e2bfaa2c58074d32
可得到一個(gè)來(lái)自 GitHubGist 的 hello world 問(wèn)候。
場(chǎng)景c) 使用不同版本 node 執(zhí)行命令將 npx 與 Aria Stewart 創(chuàng)建的 node 包 (https://www.npmjs.com/package... 結(jié)合,可以實(shí)現(xiàn)在一行命令中使用指定版本的 node 執(zhí)行命令。
例如先后執(zhí)行:
npx node@4 -e "console.log(process.version)" npx node@6 -e "console.log(process.version)"
將分別輸出 v4.8.7 和 v6.13.0.
往常這種工作是由 nvm 這類(lèi) node 版本管理工具來(lái)做的,但 npx node@4 這種方式免去 nvm 手動(dòng)切換配置的步驟,更加簡(jiǎn)潔簡(jiǎn)單。
6. npm 配置 6.1 npm confignpm cli 提供了 npm config 命令進(jìn)行 npm 相關(guān)配置,通過(guò) npm config ls -l 可查看 npm 的所有配置,包括默認(rèn)配置。npm 文檔頁(yè)為每個(gè)配置項(xiàng)提供了詳細(xì)的說(shuō)明 https://docs.npmjs.com/misc/c... .
修改配置的命令為 npm config set
proxy, https-proxy: 指定 npm 使用的代理
registry 指定 npm 下載安裝包時(shí)的源,默認(rèn)為 https://registry.npmjs.org/ 可以指定為私有 Registry 源
package-lock 指定是否默認(rèn)生成 package-lock 文件,建議保持默認(rèn) true
save true/false 指定是否在 npm install 后保存包為 dependencies, npm 5 起默認(rèn)為 true
刪除指定的配置項(xiàng)命令為 npm config delete
除了使用 CLI 的 npm config 命令顯示更改 npm 配置,還可以通過(guò) npmrc 文件直接修改配置。
這樣的 npmrc 文件優(yōu)先級(jí)由高到低包括:
工程內(nèi)配置文件: /path/to/my/project/.npmrc
用戶(hù)級(jí)配置文件: ~/.npmrc
全局配置文件: $PREFIX/etc/npmrc (即npm config get globalconfig 輸出的路徑)
npm內(nèi)置配置文件: /path/to/npm/npmrc
通過(guò)這個(gè)機(jī)制,我們可以方便地在工程跟目錄創(chuàng)建一個(gè) .npmrc 文件來(lái)共享需要在團(tuán)隊(duì)間共享的 npm 運(yùn)行相關(guān)配置。比如如果我們?cè)诠緝?nèi)網(wǎng)環(huán)境下需通過(guò)代理才可訪(fǎng)問(wèn) registry.npmjs.org 源,或需訪(fǎng)問(wèn)內(nèi)網(wǎng)的 registry, 就可以在工作項(xiàng)目下新增 .npmrc 文件并提交代碼庫(kù)。
proxy = http://proxy.example.com/ https-proxy = http://proxy.example.com/ registry = http://registry.example.com/
因?yàn)轫?xiàng)目級(jí) .npmrc 文件的作用域只在本項(xiàng)目下,所以在非本目錄下,這些配置并不生效。對(duì)于使用筆記本工作的開(kāi)發(fā)者,可以很好地隔離公司的工作項(xiàng)目、在家學(xué)習(xí)研究項(xiàng)目?jī)煞N不同的環(huán)境。
將這個(gè)功能與 ~/.npm-init.js 配置相結(jié)合,可以將特定配置的 .npmrc 跟 .gitignore, README 之類(lèi)文件一起做到 npm init 腳手架中,進(jìn)一步減少手動(dòng)配置。
6.3 node 版本約束雖然一個(gè)項(xiàng)目的團(tuán)隊(duì)都共享了相同的代碼,但每個(gè)人的開(kāi)發(fā)機(jī)器可能安裝了不同的 node 版本,此外服務(wù)器端的也可能與本地開(kāi)發(fā)機(jī)不一致。
這又是一個(gè)可能帶來(lái)不一致性的因素 —— 但也不是很難解決,聲明式約束+腳本限制即可。
聲明:通過(guò) package.json 的 engines 屬性聲明應(yīng)用運(yùn)行所需的版本運(yùn)行時(shí)要求。例如我們的項(xiàng)目中使用了 async, await 特性,查閱兼容性表格得知最低支持版本為 7.6.0,因此指定 engines 配置為:
{ "engines": { "node": ">=7.6.0"} }
強(qiáng)約束(可選):在 npm 中以上字段內(nèi)容僅作為建議字段使用,若要在私有項(xiàng)目中添加強(qiáng)約束,需要自己寫(xiě)腳本鉤子,讀取并解析 engines 字段的 semver range 并與運(yùn)行時(shí)環(huán)境做對(duì)比校驗(yàn)并適當(dāng)提醒。
7. 小結(jié) npm 最佳實(shí)踐使用 npm-init 初始化新項(xiàng)目
統(tǒng)一項(xiàng)目配置: 需團(tuán)隊(duì)共享的 npm config 配置項(xiàng),固化到 .npmrc 文件中
統(tǒng)一運(yùn)行環(huán)境,統(tǒng)一 package.json,統(tǒng)一 package-lock 文件
合理使用多樣化的源安裝依賴(lài)包: npm install
使用 npm: >=5.2 版本
使用 npm scripts 與 npx (npm: >=5.2) 腳本管理應(yīng)用相關(guān)腳本
8. 更多資料參考
npm team 成員 Ashley Williams 在 2016 年 Node.js Live 上的 talk: You Don"t Know npm, 當(dāng)時(shí)還沒(méi)有 npm 5
YouTube 視頻鏈接: Node.js Live (Paris) - Ashley Williams, You Don"t Know npm
演講用的 slides: the ag_deck
這篇 2015 年的文章介紹了如何使用把本地模塊打包到 node_modules 依賴(lài)中: Build modular application with npm local modules
一篇很好的介紹 package-lock.json 的文章: Everything you wanted to know about package-lock.json
阮一峰 npm scripts 使用指南
Kat Marchán 介紹npx:
原文 Introducing npx: an npm package runner
中文 npx是什么,為什么需要npx?
文檔
npm 官方文檔, 無(wú)中文翻譯
package.json 文件
npm config 配置
npm semver 計(jì)算器
node_modules 目錄扁平化
yarn 中文文檔,雖然是 npm 競(jìng)爭(zhēng)者但兼容 package.json 和 node_modules 目錄,因此這兩部分一樣可參考:
package.json - 中文
依賴(lài)與版本 - 中文
延伸閱讀
sam boyer 《所以你想開(kāi)發(fā)一個(gè)包管理系統(tǒng)》,從無(wú)關(guān)特定語(yǔ)言的角度,介紹一個(gè)包管理系統(tǒng)的方面: So you want to write a package manager
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/93656.html
摘要:幸運(yùn)的是,供應(yīng)似乎與需求相匹配,并且有多種選擇。讓我們來(lái)看看年值得關(guān)注的十大動(dòng)畫(huà)庫(kù)。八年了,仍然是一個(gè)強(qiáng)大的動(dòng)畫(huà)工具。接下來(lái)在這個(gè)令人驚嘆的動(dòng)畫(huà)庫(kù)列表上的就是了。,通常被稱(chēng)為動(dòng)畫(huà)平臺(tái),我們忽略它在列表中的排名,它是列表中最受歡迎的庫(kù)之一。 原文鏈接原譯文鏈接 現(xiàn)代網(wǎng)站的客戶(hù)端依靠高質(zhì)量的動(dòng)畫(huà),這就使得JavaScript動(dòng)畫(huà)庫(kù)的需求不斷增加。幸運(yùn)的是,供應(yīng)似乎與需求相匹配,并且有多種選...
摘要:因?yàn)樯婕皹I(yè)務(wù)敏感話(huà)題,本文只記錄我們學(xué)習(xí)的歷程。我由衷的感謝團(tuán)隊(duì)的小伙伴們,感謝你們的堅(jiān)韌不拔,感謝你們的持續(xù)成長(zhǎng)。這個(gè)變化只是在每天的堅(jiān)持和刻意練習(xí)中發(fā)生的,是那么的神奇。 因?yàn)樯婕皹I(yè)務(wù)敏感話(huà)題,本文只記錄我們學(xué)習(xí)的歷程。 關(guān)于堅(jiān)持 ??從2016年起,我們團(tuán)隊(duì)堅(jiān)持每天早晨8:50-10:30的100分鐘早晨討論,到現(xiàn)在已經(jīng)兩年了,期間沒(méi)有中斷過(guò)。我由衷的感謝團(tuán)隊(duì)的小伙伴們,感謝你們...
摘要:因?yàn)樯婕皹I(yè)務(wù)敏感話(huà)題,本文只記錄我們學(xué)習(xí)的歷程。我由衷的感謝團(tuán)隊(duì)的小伙伴們,感謝你們的堅(jiān)韌不拔,感謝你們的持續(xù)成長(zhǎng)。這個(gè)變化只是在每天的堅(jiān)持和刻意練習(xí)中發(fā)生的,是那么的神奇。 因?yàn)樯婕皹I(yè)務(wù)敏感話(huà)題,本文只記錄我們學(xué)習(xí)的歷程。 關(guān)于堅(jiān)持 ??從2016年起,我們團(tuán)隊(duì)堅(jiān)持每天早晨8:50-10:30的100分鐘早晨討論,到現(xiàn)在已經(jīng)兩年了,期間沒(méi)有中斷過(guò)。我由衷的感謝團(tuán)隊(duì)的小伙伴們,感謝你們...
摘要:第二十二期掘金團(tuán)隊(duì)請(qǐng)來(lái)了進(jìn)階解密作者劉望舒做了為期三天的活動(dòng)活動(dòng)已結(jié)束。我們?cè)诖司x了一些來(lái)自用戶(hù)的提問(wèn)及劉望舒的回答。提醒本期分布式微服務(wù)主題的正在進(jìn)行,歡迎前去提問(wèn),傳送門(mén)關(guān)于劉望舒進(jìn)階之光進(jìn)階解密的作者,安卓巴士等技術(shù)大會(huì)特邀講師。第二十二期 AMA 掘金團(tuán)隊(duì)請(qǐng)來(lái)了《Android進(jìn)階解密》作者-- 劉望舒做了為期三天的 Ask Me Anything (AMA) 活動(dòng)(活動(dòng)已結(jié)束)。...
閱讀 2805·2021-11-22 14:44
閱讀 545·2021-11-22 12:00
閱讀 3686·2019-08-30 15:54
閱讀 1576·2019-08-29 17:15
閱讀 1903·2019-08-29 13:50
閱讀 1113·2019-08-29 13:17
閱讀 3519·2019-08-29 13:05
閱讀 1184·2019-08-29 11:31