如何编写同时用于 Node 和浏览器的 JavaScript 包
<p>我多次看到大家在这个问题上产生困惑,甚至经验丰富的 JavaScript 开发者都可能错过它的一些微妙之处。所以我认为应该写这么一个简短的教程。</p> <p>假设有一个 JavaScript 模块想发布在 npm 中,它既能在 Node 中运行,又能在浏览器中运行。这会产生一个问题!这个特定的模块对于 Node 和浏览器的运行,会有一点不同的实现。</p> <p>这种情况相当常见,因为这 Node 和浏览器之间存在许多微小的环境差异。如何正确实现相当棘手,尤其是想在针对浏览的实现中极尽可能地减少依赖库的时候。</p> <h3>构建一个 JS 包</h3> <p>来写一个很小的,称为 base64-encode-string 的 JavaScript 包。它的作用是将输入的字符串以 base64 编码之后输出。</p> <p>对浏览器来说,使用内置的 btoa 函数很容易就能实现:</p> <pre> <code class="language-javascript">module.exports = function (string) { return btoa(string); };</code></pre> <p>但 Node 中没有 btoa 函数,所以我们要创建一个 Buffer,然后调用 buffer.toString() :</p> <pre> <code class="language-javascript">module.exports = function (string) { return Buffer.from(string, 'binary').toString('base64'); };</code></pre> <p>两种方法都能输出正确的 base64 编码,比如:</p> <pre> <code class="language-javascript">var b64encode = require('base64-encode-string'); b64encode('foo'); // Zm9v b64encode('foobar'); // Zm9vYmFy</code></pre> <p>现在我们需要一些方法来检验它是运行在浏览器中还是运行在 Node 中,然后我们才能调用正确的版本。Browserify 和 Webpack 都定义了 process.browser,在浏览器中它返回 true,而在 Node 中它返回 false。所以我们很容易做到:</p> <pre> <code class="language-javascript">if (process.browser) { module.exports = function (string) { return btoa(string); }; } else { module.exports = function (string) { return Buffer.from(string, 'binary').toString('base64'); }; }</code></pre> <p>我们把文件命名为 index.js,键入 npm publish,然后一切都搞定了。但这种方法存在一个巨大的性能问题。</p> <p>index.js 中包含了对 Node 内建的 process 和 Buffer 的引用,Browserify 和 Webpack 都会在打包的时候自动包含相应的 polyfill。</p> <p>虽然这个模块只有 9 行,但 Browserify 和 Webpack 最小化并打包出来有 24.7KB (7.6KB min+gz)。在浏览器中只需要 btoa 就能解决的问题居然需要引用这么大的东西!</p> <h3>超爱“browser” 选项</h3> <p style="text-align:center">如果在 Browserify 和 Webpack 的文件中寻找解决办法,最终会找到 node-browser-resolve 。这涉及到package.json 中的 "browser" 选项。它定义在为浏览器构建模块时的行为。</p> <p>使用这个技术,需要在 package.json 中添加:</p> <pre> <code class="language-javascript">{ /* ... */ "browser": { "./index.js": "./browser.js" } }</code></pre> <p>然后将两个函数分拆到 index.js 和 browser.js 两个文件中:</p> <pre> <code class="language-javascript">// index.js module.exports = function (string) { return Buffer.from(string, 'binary').toString('base64'); };</code></pre> <pre> <code class="language-javascript">// browser.js module.exports = function (string) { return btoa(string); };</code></pre> <p>这之后,Browserify 和 Webpack 会产生 更合适的结果 :Browserify 最小只有 511 字段(315 min+gz),Webpack 则是 550 字节(297 min+gz)。</p> <p>这个包发布到 npm 之后,在 Node 中 运行 require('base64-encode-string') 都是引用 Node 版本,而使用 Browerify 或 Webpack 则会引用浏览器版本。成功!</p> <p>对于 Rollup 来说会更复杂一点。Rollup 用户需要使用 rollup-plugin-node-resolve 并在选项中设置 browser 为 ture。</p> <p>对于 jspm 来说就很不幸了, 它不支持 “browser” 选项 。不过 jspm 用户可以通过如下方法绕过去:require('base64-encode-string/browser') 或者 jspm install npm:base64-encode-string -o "{main:'browser.js'}"。另外,包作者可以在 package.json 中 指定“jspm”选项 。</p> <h3>高级技巧</h3> <p>直接使用“browser”的方法很好,但对于大型项目来说,package.json 和代码的耦合就很尴尬了。例如,package.json 很快会变成下面这个样子:</p> <pre> <code class="language-javascript">{ /* ... */ "browser": { "./index.js": "./browser.js", "./widget.js": "./widget-browser.js", "./doodad.js": "./doodad-browser.js", /* etc. */ } }</code></pre> <p>你每需要一个浏览器模块,就必须创建两个单独的文件,然后在 “browser” 选项中添加一行来关联它们。还得小心不要写错什么!</p> <p>而且你会发现自己需要将部分代码提取为单独的模块,因为你不想使用 if (process.browser) {} 来进行检查。当这些 *-browser.js 文件逐渐积累起来,就会使代码导航越来越困难。</p> <p>解决这个问题有几个不同的解决方案。我个人喜欢使用 Rollup 来作为构建工具,它会自动将一个代码库中的代码拆分成 index.js 和 browser.js 文件, 节约空间和时间 。</p> <p>想这样做需要安装 rollup 和 rollup-plugin-replace,然后定义 rollup.cofnig.js 文件:</p> <pre> <code class="language-javascript">import replace from 'rollup-plugin-replace'; export default { entry: 'src/index.js', format: 'cjs', plugins: [ replace({ 'process.browser': !!process.env.BROWSER }) ] };</code></pre> <p>(我们会使用 process.env.BROWSER 来切换针对浏览器的构建和针对 Node 的构建。)</p> <p>接下来,创建 src/index.js 文件,它包含一个单独的函数,其中用到了 process.browser 条件:</p> <pre> <code class="language-javascript">export default function base64Encode(string) { if (process.browser) { return btoa(string); } else { return Buffer.from(string, 'binary').toString('base64'); } }</code></pre> <p>然后在 package.json 中添加 prepublish 步骤,用于生成文件:</p> <pre> <code class="language-javascript">{ /* ... */ "scripts": { "prepublish": "rollup -c > index.js && BROWSER=true rollup -c > browser.js" } }</code></pre> <p>生成的文件相当简单而且易读:</p> <pre> <code class="language-javascript">// index.js 'use strict'; function base64Encode(string) { { return Buffer.from(string, 'binary').toString('base64'); } } module.exports = base64Encode;</code></pre> <pre> <code class="language-javascript">// browser.js 'use strict'; function base64Encode(string) { { return btoa(string); } } module.exports = base64Encode;</code></pre> <p>你会发现 Rollup 根据需要自动将 process.browser 转变为 true 或 false,然后去掉无用的代码。因此在针对浏览器的生成结果中不会引用 process 或 Buffer。</p> <p>这种技术让你可以在代码中任意使用 process.browser 条件,发布出来的结果总是两个小文件,一个 index.js,一个 browser.js。在 Node 环境只有 Node 相关的代码,而在浏览器环境则只有浏览器相关的代码。</p> <p>你还可以配置 Roolup 生成 ES 模块构建、IIFE 构建,或 UMD 构建。比如我的 marky 项目就是一个拥有多个 Rollup 构建目标的简单库。</p> <p> </p> <p> </p> <p>来自:https://www.oschina.net/translate/how-to-write-a-javascript-package-for-both-node-and-the-browser</p> <p> </p>
本文由用户 Tinfky242 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!