Skip to content

Commit

Permalink
changes a bit of the content ( frehner#10 )
Browse files Browse the repository at this point in the history
  • Loading branch information
SyMind committed Sep 2, 2022
1 parent 00cdc52 commit 91ded53
Showing 1 changed file with 54 additions and 34 deletions.
88 changes: 54 additions & 34 deletions README-zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

本指南旨在提供一些大多数库都应该遵循的一目了然的建议。以及一些额外的信息,用来帮助你了解这些建议被提出的原因,或帮助你判断是否不需要遵循某些建议。这个指南仅适用于 **库(libraries)**,不适用于应用(app)。

要强调的是,这只是一些 **建议**,并不是所有库都必须要遵循的。每个库都是独特的,它们可能有充足的理由不采用本文中的任何建议。
要强调的是,这只是一些**建议**,并不是所有库都必须要遵循的。每个库都是独特的,它们可能有充足的理由不采用本文中的任何建议。

最后,这个指南不针对某一个特定的打包工具 —— 已经有许多指南来说明如何在配置特定的打包工具。相反我们聚焦于每个库和打包工具(或不用打包工具)都适用的事项。

Expand All @@ -25,9 +25,19 @@

`esm` 被认为是“未来”,但 `cjs` 仍然在社区和生态系统中占有重要地位。`esm` 对打包工具来说更容易正确地进行 treeshaking,因此对于库来说,拥有这种格式很重要。或许在将来的某一天,你的库只需要输出 `esm`

你可能已经注意到,`umd` 已经兼容 CommonJS 模块加载器 —— 是否要同时指定 `cjs``umd` 完全取决于你。在某些情况下,这并无必要。但另外一些情况下,最好有一个纯 `cjs` 输出,它保留源代码的文件和目录结构,和一个输出到单个文件的 `umd`,这样就可以轻松地将其用于 `<script>` 标签。
你可能已经注意到,`umd` 已经与 CommonJS 模块加载器兼容 —— 所以为什么还要同时具备 `cjs``umd` 输出呢?一个原因是,与 `umd` 文件相比,CommonJS 文件在对依赖进行条件导入时通常表现更好;例如:

最后还需要注意是,开发人员可能会在其应用中同时使用 `cjs``esm` 格式的情况下,发生双包危险。[dual package hazard](https://nodejs.org/api/packages.html#dual-package-hazard) 一文介绍了一些缓解该问题的方法,利用 [`package.json#exports`](#定义你的-exports) 的条件导出也可以帮助防止这种情况的发生。
```js
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 模块,最终可能会将两个包全部引入。有关更多信息,请参阅[此讨论](https://github.com/frehner/modern-guide-to-packaging-js-library/issues/9)

最后还需要注意是,开发者可能会在其应用中同时使用 `cjs``esm`,发生双包危险。[dual package hazard](https://nodejs.org/api/packages.html#dual-package-hazard) 一文介绍了一些缓解该问题的方法,利用 [`package.json#exports`](#定义你的-exports) 进行 package exports 也可以帮助防止这种情况的发生。

</details>

Expand All @@ -36,20 +46,24 @@
<details>
<summary>通过保留文件结构更好地支持 treeshaking</summary>

如果你对你的库使用了打包工具或编译器,可以对其进行配置以保留源文件目录结构。这样可以更容易地对特定文件进行 [side effects](#列出-sideeffects) 标记,有助于开发者的打包工具进行 threeshaking。更多信息,参考[这篇文章](https://levelup.gitconnected.com/code-splitting-for-libraries-bundling-for-npm-with-rollup-1-0-2522c7437697)了解更多信息。
如果你对你的库使用了打包工具或编译器,可以对其进行配置以保留源文件目录结构。这样可以更容易地对特定文件进行 [side effects](#列出-sideeffects) 标记,有助于开发者的打包工具进行 threeshaking。参考[这篇文章](https://levelup.gitconnected.com/code-splitting-for-libraries-bundling-for-npm-with-rollup-1-0-2522c7437697)了解更多信息。

一个例外是,如果你要创建一个无需任何打包工具可以直接在浏览器中使用的产出(通常,这些是 `umd` 格式,但也可能是现代的 `esm` 格式)。在这种情况下,最好让浏览器请求一个大文件,而不是请求多个小文件。此外,你应该 [minify](#不要压缩代码) 产出并为其创建 [sourcemap](#创建-sourcemap)
一个例外是,如果你要创建一个不依赖任何打包工具可以直接在浏览器中使用的产出(通常会是 `umd` 格式,但也可能是现代的 `esm` 格式)。在这种情况下,最好让浏览器请求一个大文件,而不是请求多个小文件。此外,你应该进行[代码压缩](#要不要压缩代码)并为其创建 [sourcemap](#创建-sourcemap)

</details>

## 不要压缩代码
## 要不要压缩代码

<details>
<summary>让开发者自己处理代码压缩</summary>
<summary>确定你期望的代码压缩程度</summary>

你可以将一些层面的代码压缩应用到你的库中,这取决于你对你的代码最终通过开发者的打包工具后的大小的追求程度。

如果你对你的库使用了打包工具或编译器,对其进行配置不要进行代码压缩。压缩后的代码难以被开发者的打包工具进行 threeshaking,而且开发者的打包工具将会对你的库进行压缩。参考[这篇文章](https://levelup.gitconnected.com/code-splitting-for-libraries-bundling-for-npm-with-rollup-1-0-2522c7437697)了解更多信息
例如,大多数编译器已经配置了删除空白符等其他简单的优化,即使是来自 NPM 模块的代码(在这里指的是你的库)。使用 [terser](https://github.com/terser/terser#terser-fast-minify-mode) —— 一个流行的 JavaScript 代码压缩工具 —— 这类的压缩工具可以将包的最终大小减少 95%。在某些情况下,你可能会对这些优化感到满意,且不需要你来付出任何努力。

一个例外是,如果你要创建一个无需任何打包工具可以直接在浏览器中使用的产出(通常,是 `umd` 格式的,但也可以是现代的 `esm` 格式)。在这种情况下,你应该对代码进行压缩,并创建 [sourcemap](#创建-sourcemap),而且可能希望它是个[单文件](#输出多文件)
但如果在发布前对你的库进行代码压缩,这可以得到一些额外的好处,但需要深入了解压缩工具的配置和副作用。压缩工具通常不会将这类压缩用于 NPM 模块,因此,如果你不自己来做的话,你会错过这些节省。请参阅[这个 issue](https://github.com/frehner/modern-guide-to-packaging-js-library/issues/9)了解更多信息。

最后,如果你你正创建一个无需依赖任何打包工具可以直接在浏览器中使用的产出(通常,是 `umd` 格式的,但也可以是现代的 `esm` 格式)。在这种情况下,你应该对代码进行压缩,并创建 [sourcemap](#创建-sourcemap),并输出到一个[单文件](#输出多文件)

</details>

Expand Down Expand Up @@ -100,7 +114,13 @@
- 当使用你的库时,允许开发者支持老版本的浏览器。
- 输出多个产出来支持不同版本的浏览器。

举个例子,如果你使用 TypeScript,你应该在 `tsconfig.json` 中将 `"target"` 设置为 `ESNext`
举个例子,如果你使用 TypeScript,你可以创建两个版本的包代码:

1. 通过在 `tsconfig.json` 中设置 `"target"="ESNExt"`,生成一个用现代 JavaScript 的 `esm` 版本
2. 通过在 `tsconfig.json` 中设置 `"target"="ES5"` 生成一个兼容低版本 JavaScript 的 `umd` 版本

With these settings, most users will get the modern code, but those using older bundler configurations or loading the code using a `<script>` tag will get the version with additional transpilation for older browser support.
有了这些设置,大多数用户将获得现代版本的代码,但那些使用老的打包工具配置或使用 `<script>` 加载代码的用户,将获得进行了额外编译来支持老版本浏览器的版本。

</details>

Expand Down Expand Up @@ -133,7 +153,7 @@

`package.json` 中有许多重要的配置字段值得讨论;我在这里将着重讨论其中最为重要的一些,这还有很多 [additional fields](https://docs.npmjs.com/cli/v8/configuring-npm/package-json),你同样可以进行配置。

### 设置 `name`
### 设置 `name` 字段

<details>
<summary>给你的库取一个名字</summary>
Expand All @@ -146,7 +166,7 @@

</details>

### 设置 `version`
### 设置 `version` 字段

<details>
<summary>通过更改 version 来对你的库发布更新</summary>
Expand All @@ -164,7 +184,7 @@
<details>
<summary><code>exports</code> 为你的库定义公共 API</summary>

`package.json` 中的 `exports` 字段 - 有时被称为“导出映射” - 是一个非常有用的补充,尽管它确实引入了一些复杂性。它做的最重要的两件事是:
`package.json` 中的 `exports` 字段 - 有时被称为“package exports” - 是一个非常有用的补充,尽管它确实引入了一些复杂性。它做的最重要的两件事是:

1. 定义哪些东西可以从你的库中导入,哪些则不可以,以及可导入的内容的名字。如果没有在 `exports` 中被列出,那么开发者就不可以 `import``require` 它们。换句话说,`exports` 的表现像是给你的库用户查看的公共 API,帮助定义哪些是外部的哪些是内部的。

Expand All @@ -177,7 +197,7 @@
"exports": {
".": {
"types": "index.d.ts",
"script": "index.umd.js",
"browser": "index.umd.js",
"module": "index.js",
"import": "index.js",
"require": "index.cjs",
Expand All @@ -188,22 +208,20 @@
}
```

</details>

让我们深入了解这些字段的含义以及我选择这个示例的原因:

- `"."` 表示你的库的默认入口
- 解析过程是 **从上往下** 的,并在找到匹配的字段后立即停止;所以入口的顺序是非常重要的
- `types` 字段应该始终放在第一位,它用来帮助 TypeScript 找到类型文件
- `script` 字段应该填写可以直接在 `<script>` 标签中使用的 `umd` 产出
- `module` “非官方”字段,被例如 Webpack 和 Rollup 等打包工具所支持。它应该被放在 `import``require` 之前,并且指向 `esm` 格式的产出 -- 如果你的源代码是纯 `esm` 的,它也可以是你的源代码。 你可以从[这里](https://github.com/webpack/webpack/issues/11014#issuecomment-641550630)[这里](https://github.com/webpack/webpack/issues/11014#issuecomment-643256943)、还有 [这里](https://github.com/rollup/plugins/pull/540#issuecomment-692078443)了解更多关于 `module` 的内容
- `browser` 字段应该指向可以直接在 `<script>` 标签中使用的 `umd` 产出
- `module` 是一个“非官方”字段,它被 Webpack 和 Rollup 等打包工具所支持。它应该被放在 `import``require` 之前,并且指向 `esm` 格式的产出 -- 如果你的源代码是纯 `esm` 的,它也可以指向你的源代码。正如在[格式部分](#输出-esmcjs-和-umd-格式)中指出的那样,它旨在帮助打包工具只包含你的库的一个副本,无论它是通过 `import` 还是 `require` 方式引入的。你可以从[这里](https://github.com/webpack/webpack/issues/11014#issuecomment-641550630)[这里](https://github.com/webpack/webpack/issues/11014#issuecomment-643256943)、还有 [这里](https://github.com/rollup/plugins/pull/540#issuecomment-692078443)了解更多关于 `module` 的内容
- `import` 用于当有人通过 `import` 使用你的库时
- `require`用于当有人通过 `require` 使用你的库时
- `default` 应该始终放在最后,如果没有其他匹配项,作为兜底方案

当一个打包工具或者运行时支持 `exports` 字段的时候,那么 `package.json` 中的顶级字段 [main](#设置-main)[types](#设置-types)[module](#设置-module) 还有 [browser](#设置-browser) 将被忽略,被 `exports` 取代。但是,对于尚不支持 `exports` 字段的工具或运行时来说,设置这些字段仍然很重要。

如果你有一个 "development" 和一个 "production" 的产出(例如,你有一些 warning 在 development 产出中但在 production 产出中没有),那么你可以在 `exports` 中通过 `"development"``"production"` 来设置它们。 `webpack` 将会自动识别这些导出,Rollup 也可以通过[配置](https://github.com/rollup/plugins/tree/master/packages/node-resolve/#exportconditions)来识别它们。
如果你有一个 "development" 和一个 "production" 的产出(例如,你有一些警告在 development 产出中有但在 production 产出中没有),那么你可以通过在 `exports` 字段中 `"development"``"production"` 来设置它们。注意一些打包工具例如 `webpack` `vite` 将会自动识别这些导出条件,而 Rollup 也可以通过[配置](https://github.com/rollup/plugins/tree/master/packages/node-resolve/#exportconditions)来识别它们,你需要提醒开发者在他们自己打包工具的配置中去做这些事。

</details>

Expand Down Expand Up @@ -249,7 +267,7 @@

</details>

### 列出 `sideEffects`
### 列出哪些模块有 `sideEffects`

<details>
<summary>设置 <code>sideEffects</code> 来允许 treeshaking </summary>
Expand Down Expand Up @@ -291,7 +309,7 @@ window.example = "testing";
例如:

```js
import { myVar } from "";
import { myVar } from "library";

console.log(window.example);
// 打印 "testing"
Expand All @@ -312,7 +330,7 @@ export function setExample() {
现在这是一个“纯”模块。注意,从开发者的角度来看会有不同:

```js
import { myVar, setExample } from "";
import { myVar, setExample } from "library";

console.log(window.example);
// 打印 "undefined"
Expand All @@ -327,47 +345,49 @@ console.log(window.example);

</details>

### 设置 `main`
### 设置 `main` 字段

<details>
<summary><code>main</code> 定义 CommonJS 入口 </summary>

`main` 是一个当打包工具或运行时不支持 [`package.json#exports`](#定义你的-exports) 时的兜底方案;如果打包工具或运行时支持条件导出,则不会使用 `main`
`main` 是一个当打包工具或运行时不支持 [`package.json#exports`](#定义你的-exports) 时的兜底方案;如果打包工具或运行时支持 package exports,则不会使用 `main`

`main` 应该指向一个兼容 CommonJS 格式的产出;它应该与条件导出中的 `require` 保持一致。
`main` 应该指向一个兼容 CommonJS 格式的产出;它应该与 package exports 中的 `require` 保持一致。

</details>

### 设置 `module`
### 设置 `module` 字段

<details>
<summary><code>module</code> 定义 ESM 入口</summary>

`module` 是一个当打包工具或运行时不支持 [`package.json#exports`](#定义你的-exports) 时的兜底方案;如果打包工具或运行时支持条件导出,则不会使用 `module`
`module` 是一个当打包工具或运行时不支持 [`package.json#exports`](#定义你的-exports) 时的兜底方案;如果打包工具或运行时支持 package exports,则不会使用 `module`

`module` 应该指向一个兼容 ESM 格式的产出;它应该与条件导出中的 `module``import` 保持一致。
`module` 应该指向一个兼容 ESM 格式的产出;它应该与 package exports 中的 `module``import` 保持一致。

</details>

### 设置 `browser`
### 设置 `browser` 字段

<details>
<summary><code>browser</code> 定义用于 script 标签的产出 </summary>

`browser` 是一个当打包工具或运行时不支持 [`package.json#exports`](#define-your-exports) 时的兜底方案;如果打包工具或运行时支持条件导出, 则不会使用 `browser`
`browser` 是一个当打包工具或运行时不支持 [`package.json#exports`](#define-your-exports) 时的兜底方案;如果打包工具或运行时支持 package exports, 则不会使用 `browser`

`browser` 应该指向 `umd` 格式的产出;它应该与 package exports 中的 `script` 文字段指向同一个文件。

`browser` 应该指向 `umd` 格式的产出;它应该与条件导出中的 `script` 文件保持一致
此外,一些 CDN 的配置可以复用该字段;例如,你可以配置 `"unpkg"` and `"jsdelivr"` 来让 CDN 指向与 `browser` 相同的产出,当它们没有自动获取到 `browser` 字段时

</details>

### 设置 `types`
### 设置 `types` 字段

<details>
<summary><code>types</code> 定义 TypeScript 类型 </summary>

`types` 是一个当打包工具或运行时不支持 [`package.json#exports`](#定义你的-exports) 时的兜底方案; 如果打包工具或运行时支持条件导出,则不会使用 `types`
`types` 是一个当打包工具或运行时不支持 [`package.json#exports`](#定义你的-exports) 时的兜底方案; 如果打包工具或运行时支持 package exports,则不会使用 `types`

`types` 应该指向你的 TypeScript 入口文件,例如 `index.d.ts`它应该与条件导出中的 `types` 文件保持一致
`types` 应该指向你的 TypeScript 入口文件,例如 `index.d.ts`它应该与 package exports 中的 `types` 字段指向同一个文件

</details>

Expand Down

0 comments on commit 91ded53

Please sign in to comment.