最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

這才是現(xiàn)代 JavaScript 庫打包指南!

2023-04-12 13:16 作者:阿呆帶你學編程  | 我要投稿



https://github.com/frehner/modern-guide-to-packaging-js-library/blob/main/README-zh_CN.md

本指南旨在提供一些大多數(shù)庫都應該遵循的一目了然的建議。以及一些額外的信息,用來幫助你了解這些建議被提出的原因,或幫助你判斷是否不需要遵循某些建議。這個指南僅適用于?「庫(libraries)」,不適用于應用(app)。

要強調(diào)的是,這只是一些「建議」,并不是所有庫都必須要遵循的。每個庫都是獨特的,它們可能有充足的理由不采用本文中的任何建議。

最后,這個指南不針對某一個特定的打包工具 —— 已經(jīng)有許多指南來說明如何在配置特定的打包工具。相反我們聚焦于每個庫和打包工具(或不用打包工具)都適用的事項。

輸出?esm、cjs?和?umd?格式

esm?是“EcmaScript module”的縮寫。

cjs?是“CommonJS module”的縮寫。

umd?是“Universal Module Definition”的縮寫,它可以在?<script>?標簽中執(zhí)行、被?CommonJS?模塊加載器加載、被?AMD?模塊加載器加載。

esm?被認為是“未來”,但?cjs?仍然在社區(qū)和生態(tài)系統(tǒng)中占有重要地位。esm?對打包工具來說更容易正確地進行 treeshaking,因此對于庫來說,擁有這種格式很重要。或許在將來的某一天,你的庫只需要輸出?esm。

你可能已經(jīng)注意到,umd?已經(jīng)與 CommonJS 模塊加載器兼容 —— 所以為什么還要同時具備?cjs?和?umd?輸出呢?一個原因是,與?umd?文件相比,CommonJS 文件在對依賴進行條件導入時通常表現(xiàn)更好;例如:

if?(process.env.NODE_ENV?===?"production")?{
??module.exports?=?require("my-lib.production.js");
}?else?{
??module.exports?=?require("my-lib.development.js");
}

上面的例子,當使用 CommonJS 模塊時,只會引入?production?或?development?包中的一個。但是,對于 UMD 模塊,最終可能會將兩個包全部引入。有關更多信息,請參閱此討論。

最后還需要注意是,開發(fā)者可能會在其應用中同時使用?cjs?和?esm,發(fā)生雙包危險。dual package hazard 一文介紹了一些緩解該問題的方法,利用?package.json#exports?進行 package exports 也可以幫助防止這種情況的發(fā)生。

輸出多文件

通過保留文件結(jié)構(gòu)更好地支持 treeshaking

如果你對你的庫使用了打包工具或編譯器,可以對其進行配置以保留源文件目錄結(jié)構(gòu)。這樣可以更容易地對特定文件進行 side effects 標記,有助于開發(fā)者的打包工具進行 threeshaking。

一個例外是,如果你要創(chuàng)建一個不依賴任何打包工具可以直接在瀏覽器中使用的產(chǎn)出(通常是?umd?格式,但也可能是現(xiàn)代的?esm?格式)。在這種情況下,最好讓瀏覽器請求一個大文件,而不是請求多個小文件。此外,你應該進行代碼壓縮并為其創(chuàng)建 sourcemap。

要不要壓縮代碼

你可以將一些層面的代碼壓縮應用到你的庫中,這取決于你對你的代碼最終通過開發(fā)者的打包工具后的大小的追求程度。

例如,大多數(shù)編譯器已經(jīng)配置了刪除空白符等其他簡單的優(yōu)化,即使是來自 NPM 模塊的代碼(在這里指的是你的庫)。使用 terser —— 一個流行的 JavaScript 代碼壓縮工具 —— 這類壓縮工具可以將包的最終大小減少 95%。在某些情況下,你可能會對這些優(yōu)化感到滿意,且不需要你來付出任何努力。

但如果在發(fā)布前對你的庫進行代碼壓縮,這可以得到一些額外的好處,但需要深入了解壓縮工具的配置和副作用。壓縮工具通常不會將這類壓縮用于 NPM 模塊,因此,如果你不自己來做的話,你會錯過這些節(jié)省。請參閱這個 issue了解更多信息。

最后,如果你正創(chuàng)建一個不依賴任何打包工具可以直接在瀏覽器中使用的產(chǎn)出(通常是?umd?格式,但也可以是現(xiàn)代的?esm?格式)。在這種情況下,你應該對代碼進行壓縮,并創(chuàng)建 sourcemap,并輸出到一個單文件。

創(chuàng)建 sourcemap

對源代碼進行任何形式的編譯,都將導致未來某個異常的位置,無法與源碼對應起來。為了幫助未來的自己,創(chuàng)建 sourcemap,即使只進行了很少的編譯工作。

創(chuàng)建 TypeScript 類型

隨著使用 TypeScript 的開發(fā)者數(shù)量不斷增長,將類型內(nèi)置到你的庫中將有助于改善開發(fā)體驗 (DX)。此外,不使用 TypeScript 的開發(fā)者在使用支持類型的編輯器(例如 VSCode,它使用類型來支持其 Intellisense 功能)時也會獲得更好的 DX。

但是,創(chuàng)建類型并不意味著你必須使用 TypeScript 來編寫你的庫。

一種選擇是繼續(xù)在源代碼中使用 JavaScript,然后通過 JSDoc 注釋來支持類型。然后,你可以將 TypeScript 配置為僅從你的 JavaScript 源代碼中構(gòu)建類型文件。

另一種選擇是直接在?index.d.ts?文件中編寫 TypeScript 類型文件。

獲得類型文件后,請確保設置了?package.json#exports?和?package.json#types?字段.

外置框架

不要將 React、Vue 等框架打包在你的庫中

當構(gòu)建的庫依賴某個框架(例如 React、Vue 等),或是作為另一個庫的插件,你可能需要將框架配置到“externals”中。這可以使你的庫引用這個框架,但不會將其打包到最終的產(chǎn)出中。這會避免產(chǎn)生一些 bug,并減少庫的體積。

你應該還需要將框架添加到庫的?package.json?的 peer dependencies 中,這將幫助開發(fā)者發(fā)現(xiàn)你依賴于某個框架。

面向現(xiàn)代瀏覽器

使用現(xiàn)代的新特性,如果有需要,讓開發(fā)者支持舊的瀏覽器這篇 web.dev 上的文章提供了一個很好的案例,并提供了相關的指導原則:

  • 當使用你的庫時,能夠讓開發(fā)者去支持老版本的瀏覽器。

  • 輸出多個產(chǎn)出來支持不同版本的瀏覽器。

舉個例子,如果你使用 TypeScript,你可以創(chuàng)建兩個版本的包代碼:

  1. 通過在?tsconfig.json?中設置?"target"="esnext",生成一個用現(xiàn)代 JavaScript 的?esm?版本

  2. 通過在?tsconfig.json?中設置?"target"="es5"?生成一個兼容低版本 JavaScript 的?umd?版本

有了這些設置,大多數(shù)用戶將獲得現(xiàn)代版本的代碼,但那些使用老的打包工具配置或使用?<script>?加載代碼的用戶,將獲得進行了額外編譯來支持老版本瀏覽器的版本。

必要的編譯

編譯 TypeScript、將 JSX 轉(zhuǎn)換為函數(shù)調(diào)用

如果庫的源碼是需要進行編譯的形式,如 TypeScript、React 或 Vue 組件等,那么你庫需要輸出的是編譯后的代碼。

例如:

  • 你的 TypeScript 代碼應該輸出為 JavaScript。

  • 你的 React 組件,例如?<Example />,應該在輸出中使用?jsx()?或?createElement()?來替換 JSX 語法。

進行這樣的編譯時,請確保同時也創(chuàng)建 sourcemap

維護 changelog

記錄更新和變更

只要能讓開發(fā)者了解到有哪些變更和對他們的影響,至于是通過自動化工具還是通過親自動手的方式來處理,這都無關緊要。理想情況下,庫的每次版本變更都應該在 changelog 中進行相應的更新。

拆分出你的 CSS 文件

讓開發(fā)者能夠按需引入 CSS

如果你正在創(chuàng)建一個 CSS 庫(如 Bootstrap、Tailwind 等),最簡單的方式就是提供單一文件,包含庫的所有功能。然而,在這種情況下,你的 CSS 產(chǎn)出最終可能會變得很大,影響開發(fā)者網(wǎng)站的性能。為了避免這種情況,庫通常會提供自定義生成 CSS 產(chǎn)出的功能,讓產(chǎn)出中只包含開發(fā)者正在使用的必要 CSS(例如,參考 Bootstrap 和 Tailwind 是怎么做的)。

如果 CSS 只是你的庫的一部分(例如,具有默認樣式的組件庫),那么最好將 CSS 按組件分離單獨構(gòu)建產(chǎn)出,在使用相應的組件時按需導入。這方面的一個例子是 react-component。

配置?package.json

package.json?中有許多重要的配置字段值得討論;我在這里將著重討論其中最為重要的一些,這還有很多額外的字段,你同樣可以進行配置。

設置?name?字段

給你的庫取一個名

name?字段將決定你的包在?npm?上的名字,開發(fā)者可以通過這個名字去安裝并使用你的庫。

注意,庫的命名是有限制的,如果你的代碼庫屬于某個組織,你還可以創(chuàng)建一個命名空間。更多細節(jié)可以參考 name docs on npm。

name?和 version 的組合為庫每次迭代創(chuàng)建一個唯一標識。

設置?version?字段

通過更改 version 來對你的庫發(fā)布更新

正如 name 部分所說,name?和?version?的組合為你的庫在 npm 上創(chuàng)建一個唯一標識。當你更新庫中的代碼時,你可以更新?version?字段并發(fā)布以允許開發(fā)者獲取該新代碼。

推薦使用 semver 版本控制策略,但要注意的是有些庫選擇 calver 或使用他們自己特有的版本控制策略。無論你選擇使用哪種策略,都應該記錄下來,以便開發(fā)者了解你的庫是如何進行版本控制的。

你還應該在 changelog 中記錄你的更改。

定義你的?exports

exports 為你的庫定義公共 API

package.json?中的?exports?字段 - 有時被稱為“package exports” - 是一個非常有用的補充,盡管它確實引入了一些復雜性。它做的最重要的兩件事是:

  1. 定義哪些東西可以從你的庫中導入,哪些則不可以,以及可導入的內(nèi)容的名字。如果沒有在?exports?中被列出,那么開發(fā)者就不可以?import?或?require?它們。換句話說,exports?的表現(xiàn)像是給你的庫用戶查看的公共 API,幫助定義哪些是外部的哪些是內(nèi)部的。

  2. 允許你根據(jù)不同的條件(你可以定義)去選擇那個文件是被導入的,例如“文件是被?import?還是被?require?開發(fā)人員需要的是?development?版本的庫還是?production?版本等等。

關于這部分的內(nèi)容NodeJS 團隊和Webpack 團隊提供了一些很優(yōu)秀的文檔。在此我列出一個涵蓋大部分常見場景的例子:

{
??"exports":?{
????".":?{
??????"types":?"index.d.ts",
??????"module":?"index.js",
??????"import":?"index.js",
??????"require":?"index.cjs",
??????"default":?"index.js"
????},
????"./package.json":?"./package.json"
??}
}

讓我們深入了解這些字段的含義以及我選擇這個例子的原因:

  • "."?表示你的庫的默認入口

  • 解析過程是「從上往下」的,并在找到匹配的字段后立即停止;所以入口的順序是非常重要的

  • types?字段應始終放在第一位,幫助 TypeScript 查找類型文件

  • module?是一個“非官方”字段,它被 Webpack 和 Rollup 等打包工具所支持。它應該被放在?import?和?require?之前,并且指向?esm?格式的產(chǎn)出 -- 如果你的源代碼是純?esm?的,它也可以指向你的源代碼。正如在格式部分中指出的那樣,它旨在幫助打包工具只包含你的庫的一個副本,無論它是通過?import?還是?require?方式引入的。

  • import?用于當有人通過?import?使用你的庫時

  • require?用于當有人通過?require?使用你的庫時

  • default?字段用于兜底,在沒有任何條件匹配時使用。雖然目前可能并不會匹配到它,但為了面對“未知的未來場景”,使用它是好的

當一個打包工具或者運行時支持?exports?字段的時候,那么?package.json?中的頂級字段 main、types、module 還有 browser 將被忽略,被?exports?取代。但是,對于尚不支持?exports?字段的工具或運行時來說,設置這些字段仍然很重要。

如果你有一個 "development" 和一個 "production" 的產(chǎn)出(例如,你有一些警告在 development 產(chǎn)出中有但在 production 產(chǎn)出中沒有),那么你可以通過在?exports?字段中?"development"?和?"production"?來設置它們。注意一些打包工具例如?webpack?和?vite?將會自動識別這些導出條件,而 Rollup 也可以通過配置來識別它們,你需要提醒開發(fā)者在他們自己打包工具的配置中去做這些事。

列出要發(fā)布的?files

files?定義你的 NPM 包中要包含哪些文件

files?決定?npm?CLI 在打包庫時哪些文件和目錄包含到最終的 NPM 包中。

例如,如果你將代碼從 TypeScript 編譯為 JavaScript,你可能就不想在 NPM 包中包含 TypeScript 的源代碼。(相反,你應該包含 sourcemap)。

files?可以接受一個字符串數(shù)組(如果需要,這些字符串可以包含類似 glob 的語法),例如:

{
??"files":?["dist"]
}

注意,文件數(shù)組不接受相對路徑表示;"files": ["./dist"]?將無法正常工作。

驗證你已正確設置?files?的一種好方法是運行?npm publish --dry-run,它將根據(jù)此設置列出將會包含的文件。

為你的 JS 文件設置默認的模塊?type

type?規(guī)定你的?.js?文件使用哪個模塊系統(tǒng)

運行時和打包工具需要一種方法來確定你的?.js?文件采用哪種模塊系統(tǒng) —— ESM 還是 CommonJS。因為 CommonJS 首先出現(xiàn),所以它被打包工具視為默認的 - 但你可以通過在你的?package.json?中添加?"type"?來控制這種行為。

你可以選擇?"type":"module"?或?"type":"commonjs",也可以不添加該字段(默認為 CommonJS),但仍強烈建議你進行設置,顯式地聲明你正在使用哪一個。

請注意,你可以通過幾個技巧在項目中混用模塊類型:

  • .mjs?文件總是 ESM 模塊,即使你的?package.json?有?"type": "commonjs"(或者沒有?type

  • .cjs?文件總是 CommonJS 模塊,即使你的?package.json?有?"type": "module"

  • 你可以在子目錄下添加其他?package.json?文件;運行時和打包工具將向上遍歷文件目錄,直到尋找到最近的?package.json。這意味著你可以有兩個不同的文件夾,都使用?.js?文件,但每個文件夾都有自己的?package.json?并設置為不同的?type?以獲得基于 CommonJS 和 ESM 的文件夾。

列出哪些模塊有?sideEffects

設置?sideEffects?來允許 treeshaking

創(chuàng)建一個“純模塊”帶來的優(yōu)點與創(chuàng)建一個純函數(shù)十分類似;打包工具能夠?qū)δ愕膸旄玫倪M行 treeshaking。

通過設置?sideEffects?讓打包工具知道你的模塊是否是“純”的。不設置這個字段,打包工具將不得不假設你「所有」的模塊都是有副作用。

sideEffects?可以設為?false,表示沒有任何模塊具有副作用,也可以設置為字符串數(shù)組來列出哪些文件具有副作用。例如:

{
??//?所有模塊都是“純”的
??"sideEffects":?false
}

{
??//?除了?"module.js",所有模塊都是“純”的
??"sideEffects":?["module.js"]
}

所以,什么讓一個模塊具有副作用?例如修改一個全局變量,發(fā)送 API 請求,或?qū)С?CSS,而且開發(fā)人員不需要做任何事情這些動作就會被執(zhí)行。例如:

//?具有副作用的模塊

export?const?myVar?=?"hello";

window.example?=?"testing";

導入?myVar?時,你的模塊自動設置?window.example。

例如:

import?{?myVar?}?from?"library";

console.log(window.example);
//?打印?"testing"

在某些情況下,如 polyfill,這種行為是有意的。然而,如果我們想讓這個模塊是“純”的,我們可以將對?window.example?的賦值移動到一個函數(shù)中。例如:

//?一個“純”模塊

export?const?myVar?=?"hello";

export?function?setExample()?{
??window.example?=?"testing";
}

現(xiàn)在這是一個“純”模塊。注意,從開發(fā)者的角度來看會有不同:

import?{?myVar,?setExample?}?from?"library";

console.log(window.example);
//?打印?"undefined"

setExample();

console.log(window.example);
//?打印?"testing"

設置?main?字段

main?定義 CommonJS 入口

main?是一個當打包工具或運行時不支持?package.json#exports?時的兜底方案;如果打包工具或運行時支持 package exports,則不會使用?main。

main?應該指向一個兼容 CommonJS 格式的產(chǎn)出;它應該與 package exports 中的?require?保持一致。

設置?module?字段

module?定義 ESM 入口

module?是一個當打包工具或運行時不支持?package.json#exports?時的兜底方案;如果打包工具或運行時支持 package exports,則不會使用?module。

module?應該指向一個兼容 ESM 格式的產(chǎn)出;它應該與 package exports 中的?module?或?import?保持一致。

設置給 CDN 使用的附加字段

支持 CDN,例如?unpkg?和?jsdelivr

為讓你的庫在 CDN 上“以默認的方式正常工作”,如 unpkg 和 jsdelivr,你可以設置它們的特定字段指向你的?umd?產(chǎn)出。例如:

{
??"unpkg":?"./dist/index.umd.js",
??"jsdelivr":?"./dist/index.umd.js"
}

設置?browser?字段

browser?指向能在瀏覽器中工作的產(chǎn)出

browser?是一個當打包工具或運行時不支持?package.json#exports?時的兜底方案;如果打包工具或運行時支持 package exports, 則不會使用?browser。

browser?應該指向能在瀏覽器中工作的?esm?產(chǎn)出。但是,只有在為瀏覽器和服務器(等其他非瀏覽器環(huán)境)創(chuàng)建不同的產(chǎn)出時,才需要設置該字段。如果你沒有為多個環(huán)境創(chuàng)建多個產(chǎn)出,或者你的產(chǎn)出是“純 JavaScript”或“通用”的,可以在任何 JavaScript 環(huán)境中運行,那么你就不需要設置?browser?字段。

如果你確實需要設置該字段,這里有一個優(yōu)秀的指南,介紹了配置它的不同方法。

注意,browser?字段不應該指向?umd?產(chǎn)出,因為那樣的話,你的庫就不會被打包工具(如 Webpack)進行 treeshaking,這些打包工具會優(yōu)先考慮這個字段,而不是其他字段,比如 module 和 main。

設置?types?字段

types?定義 TypeScript 類型

types?是一個當打包工具或運行時不支持?package.json#exports?時的兜底方案;如果打包工具或運行時支持 package exports,則不會使用?types。

types?應該指向你的 TypeScript 入口文件,例如?index.d.ts;它應該與 package exports 中的?types?字段指向同一個文件。

列出?peerDependencies

如果你依賴別的框架或庫,將它設置為 peer dependency

你應該外置框架。然而,這樣做后,你的庫只有在開發(fā)人員自行安裝你需要的框架后才能工作。設置?peerDependencies?讓他們知道他們需要安裝的框架。- 例如,如果你在創(chuàng)建一個 React 庫:

{
??"peerDependencies":?{
????"react":?"^18.2.0",
????"react-dom":?"^18.2.0"
??}
}

你應該以書面形式來體現(xiàn)這些依賴;例如,npm v3-v6?不安裝 peer dependencies,而?npm v7+?將自動安裝 peer dependencies。

說明你的庫使用哪個許可證

保護你自己和其他的貢獻者

開源許可證用于保護貢獻者和用戶。沒有這種保護,企業(yè)和有經(jīng)驗的開發(fā)者不會使用該項目。

上述引用自 Choose a License,這也是一篇很好的文章,幫助你來決定哪個許可證適合你的項目。

當你決定了許可證,關于許可證的 npm 文檔中描述了許可證字段的格式。例如:

{
??"license":?"MIT"
}

除此之外,你可以在項目的根目錄下創(chuàng)建一個?LICENSE.txt?文件,并將許可證的文本復制到這里。

如果這篇文章幫助到了你,歡迎點贊和關注。




這才是現(xiàn)代 JavaScript 庫打包指南!的評論 (共 條)

分享到微博請遵守國家法律
商城县| 泰州市| 麦盖提县| 阳东县| 县级市| 邢台市| 乌拉特前旗| 锡林郭勒盟| 陆川县| 衡山县| 宜丰县| 门头沟区| 嘉定区| 淄博市| 普安县| 兴业县| 灵宝市| 唐海县| 保山市| 商城县| 神池县| 公主岭市| 合肥市| 武冈市| 东乌| 邛崃市| 陈巴尔虎旗| 开鲁县| 博罗县| 武川县| 青神县| 德阳市| 手游| 渑池县| 日照市| 巩留县| 额济纳旗| 黄冈市| 商洛市| 盐山县| 许昌县|