怎样写一个能同时用于Node和浏览器的JavaScript包?

简介:

我在这个问题上见过很多困惑,即使是很有经验的 JavaScript 开发者也可能难以把握其中的巧妙之处。因此我认为值得为它书写一小段教程。

我在这个问题上见过很多困惑,即使是很有经验的 JavaScript 开发者也可能难以把握其中的巧妙之处。因此我认为值得为它书写一小段教程。

假设你有一个 JavaScript 的模块想要发布到 npm 上,它是同时适用于 Node 和浏览器的。但是请注意!这个特殊的模块在 Node 版本和浏览器版本上的实现有着细微的区别。

这种情况出现得实在频繁,因为在 Node 和浏览器间有着很多微小的环境差别。在这种情况下,可以用比较巧妙的方法来正确地实现,尤其是当你在尝试着使用最小的 browser 包(bundle)来优化的时候。

让我们构建一个 JS 包

因此让我们来写一个小的 JavaScript 包,叫做 base64-encode-string。它所做的只是接收一个字符串作为输入,输出其 base64 编码的版本。

对于浏览器来说,这很简单:我们只需要使用自带的 btoa 函数:


 
 
  1. module.exports = function (string) { 
  2.   return btoa(string); 
  3. }; 

然而在 Node 里并没有 btoa 函数。因此,作为替代,我们需要自己创建一个 Buffer,然后在上面调用 buffer.toString()


 
 
  1. module.exports = function (string) { 
  2.   return Buffer.from(string, 'binary').toString('base64'); 
  3. }; 

对于一个字符串,这两者都应提供其正确的 base64 编码版本,比如:


 
 
  1. var b64encode = require('base64-encode-string'); 
  2. b64encode('foo');    // Zm9v 
  3. b64encode('foobar'); // Zm9vYmFy 

现在我们只需要一些方法来检测我们究竟是在浏览器上运行还是在 Node 上,好让我们能保证使用正确的版本。Browserify 和 Webpack 都定义了一个叫 process.browser 的字段,它会返回 true(译者注:即浏览器环境下),然而在 Node 上这个字段返回 false。所以我们只需要简单地:


 
 
  1. if (process.browser) { 
  2.   module.exports = function (string) { 
  3.     return btoa(string); 
  4.   }; 
  5. else { 
  6.   module.exports = function (string) { 
  7.     return Buffer.from(string, 'binary').toString('base64'); 
  8.   }; 

现在我们只需要把我们的文件命名为 index.js,键入 npm publish,我们就完成了,对不对?好的吧,这个方法有效,但不幸的是,这种实现有一个巨大的性能问题。

因为我们的 index.js 文件包含了对 Node 自带的 processBuffer 模块的引用,Browserify 和 Webpack 都会自动引入 polyfill,来将它们打包进这些模块。

对于这个简单的九行模块,我算了一下, Browserify 和 Webpack 会创建 一个压缩后有 24.7KB 的包 (7.6KB min+gz)。对于这种东西,用掉的空间实在是太多,因为在浏览器里,只需要 btoa 就能表示这个。

“browser” 字段,我该如何爱你

如果你在 Browserify 或者 Webpack 文档里找解决这个问题的提示,你可能最后会发现 node-browser-resolve。这是一个对于 package.json"browser" 字段的规范,可以被用于定义在浏览器版本构建时需要被换掉的东西。

使用这种技术,我们可以将接下来这段加入我们的 package.json


 
 
  1.   /* ... */ 
  2.   "browser": { 
  3.     "./index.js""./browser.js" 
  4.   } 

然后将函数分割成两个不同的文件:index.jsbrowser.js


 
 
  1. // index.js 
  2. module.exports = function (string) { 
  3.   return Buffer.from(string, 'binary').toString('base64'); 
  4. }; 
  5.  
  6. // browser.js 
  7. module.exports = function (string) { 
  8.   return btoa(string); 
  9. }; 

有了这次改进以后,Browserify 和 Webpack 会给出 更加合理的包:Browserify 的包压缩后是 511 字节(315 min+gz),Webpack 的包压缩后是 550 字节(297 min+gz)。

当我们将我们的包发布到 npm 时,在 Node 里运行 require('base64-encode-string') 的人将得到 Node 版的代码,在 Browserfy 和 Webpack 里跑的人会得到浏览器版的代码。

对于 Rollup 来说,这就有点复杂了,但也不需要太多额外的工作。Rollup 用户需要使用 rollup-plugin-node-resolve 并在选项里将 browser 设置为 true

对 jspm 来说,很不幸地,没有对 “browser” 字段的支持,但是 jspm 用户可以通过 require('base64-encode-string/browser') 或者 jspm install npm:base64-encode-string -o "{main:'browser.js'}" 来迂回地解决问题。另一种方法是,包的作者可以在他们的 package.json指定一个 “jspm” 字段

进阶技巧

这种直接使用的 "browser" 方法可以工作得很好,但是对于大型项目来说,我发现它在 package.json 和代码库间引入了一种尴尬的耦合。比如说,我们的 package.json 会很快长成这样:


 
 
  1.   /* ... */ 
  2.   "browser": { 
  3.     "./index.js""./browser.js"
  4.     "./widget.js""./widget-browser.js"
  5.     "./doodad.js""./doodad-browser.js"
  6.     /* etc. */ 
  7.   } 

在这种情况下,任何时候你想要一个适配于浏览器的模块,都需要分别创建两个文件,并且要记住在 "browser" 字段上添加额外行来将它们连接起来。还要注意不能拼错任何东西!

并且,你会发现你在费尽心机地将微小的代码提取到分离的模块里,仅仅是因为你想要避免 if (process.browser) {} 检查。当这些 *-browser.js 文件积累起来的时候,它们会开始让代码库变得很难跳转。

如果这种情况变得实在太笨重了,有一些别的解决方案。我自己的偏好是使用 Rollup 作为构建工具,来自动地将单个代码库分割到不同的 index.jsbrowser.js 文件里。这对于将你提供给用户的代码的解模块化有额外的价值,节省了空间和时间

要这样做的话,先安装 rolluprollup-plugin-replace,然后定义一个 rollup.config.js 文件:


 
 
  1. import replace from 'rollup-plugin-replace'
  2. export default { 
  3.   entry: 'src/index.js'
  4.   format: 'cjs'
  5.   plugins: [ 
  6.     replace({ 'process.browser': !!process.env.BROWSER }) 
  7.   ] 
  8. }; 

(我们将使用 process.env.BROWSER 作为一种方便地在浏览器构建和 Node 构建间切换的方式。)

接下来,我们可以创建一个带有单个函数的 src/index.js 文件,使用普通的 process.browser 条件:


 
 
  1. export default function base64Encode(string) { 
  2.   if (process.browser) { 
  3.     return btoa(string); 
  4.   } else { 
  5.     return Buffer.from(string, 'binary').toString('base64'); 
  6.   } 

然后将 prepublish 步骤添加到 package.json 内,来生成文件:


 
 
  1.   /* ... */ 
  2.   "scripts": { 
  3.     "prepublish""rollup -c > index.js && BROWSER=true rollup -c > browser.js" 
  4.   } 

生成的文件都相当直白易读:


 
 
  1. // index.js 
  2. 'use strict'
  3.  
  4. function base64Encode(string) { 
  5.   { 
  6.     return Buffer.from(string, 'binary').toString('base64'); 
  7.   } 
  8.  
  9. module.exports = base64Encode; 
  10.  
  11. // browser.js 
  12. 'use strict'
  13.  
  14. function base64Encode(string) { 
  15.   { 
  16.     return btoa(string); 
  17.   } 
  18.  
  19. module.exports = base64Encode; 

你将注意到,Rollup 会按需自动地将 process.browser 转换成 true 或者 false,然后去掉那些无用代码。所以在生成的浏览器包里不会有对于 process 或者 Buffer 的引用。

使用这个技巧,在你的代码库里可以有任意个的 process.browser 切换,并且发布的结果是两个小的集中的 index.jsbrowser.js 文件,其中对于 Node 只有 Node 相关的代码,对于浏览器只有浏览器相关的代码。

作为附带的福利,你可以配置 Rollup 来生成 ES 模块构建,IIFE 构建,或者 UMD 构建。如果你想要示例的话,可以查看我的项目 marky,这是一个拥有多个 Rollup 构建目标的简单库。

在这篇文章里描述的实际项目(base64-encode-string)也同样被 发布到 npm 上 ,你可以审视它,看看它是怎么做到的。源码 在 GitHub 上


来源:51CTO

相关文章
|
2月前
|
JavaScript 前端开发 安全
【逆向】Python 调用 JS 代码实战:使用 pyexecjs 与 Node.js 无缝衔接
本文介绍了如何使用 Python 的轻量级库 `pyexecjs` 调用 JavaScript 代码,并结合 Node.js 实现完整的执行流程。内容涵盖环境搭建、基本使用、常见问题解决方案及爬虫逆向分析中的实战技巧,帮助开发者在 Python 中高效处理 JS 逻辑。
|
Web App开发 JavaScript 前端开发
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念,包括事件驱动、单线程模型和模块系统;探讨其安装配置、核心模块使用、实战应用如搭建 Web 服务器、文件操作及实时通信;分析项目结构与开发流程,讨论其优势与挑战,并通过案例展示 Node.js 在实际项目中的应用,旨在帮助开发者更好地掌握这一强大工具。
379 1
|
7月前
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
234 23
|
7月前
|
存储 JavaScript 前端开发
在NodeJS中使用npm包进行JS代码的混淆加密
总的来说,使用“javascript-obfuscator”包可以帮助我们在Node.js中轻松地混淆JavaScript代码。通过合理的配置,我们可以使混淆后的代码更难以理解,从而提高代码的保密性。
674 9
|
10月前
|
Web App开发 前端开发 JavaScript
折腾之王:JavaScript之父Brave浏览器与BAT的诞生
2015年,JavaScript之父Brendan Eich再次创业,推出Brave浏览器和加密货币Basic Attention Token(BAT),旨在颠覆传统广告行业。Brave屏蔽广告、保护隐私,加载速度快;BAT则通过奖励机制让用户、内容创作者和广告主三方受益。尽管面临用户习惯和巨头竞争的挑战,Brave已拥有超4000万月活跃用户,成为全球增长最快的隐私浏览器,引领Web3生态发展。
374 22
折腾之王:JavaScript之父Brave浏览器与BAT的诞生
|
算法 开发者
Moment.js库是如何处理不同浏览器的时间戳格式差异的?
总的来说,Moment.js 通过一系列的技术手段和策略,有效地处理了不同浏览器的时间戳格式差异,为开发者提供了一个稳定、可靠且易于使用的时间处理工具。
328 57
|
11月前
|
Linux Go iOS开发
怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev
本文介绍了如何在 VSCode 中禁用点击 Go 包名时自动打开浏览器跳转到 pkg.go.dev 的功能。通过将 gopls 的 `ui.navigation.importShortcut` 设置为 "Definition",可以实现仅跳转到定义处而不打开链接。具体操作步骤包括:打开设置、搜索 gopls、编辑 settings.json 文件并保存更改,最后重启 VSCode 使设置生效。
443 8
怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev
|
11月前
|
存储 JavaScript NoSQL
Node.js新作《循序渐进Node.js企业级开发实践》简介
《循序渐进Node.js企业级开发实践》由清华大学出版社出版,基于Node.js 22.3.0编写,包含26个实战案例和43个上机练习,旨在帮助读者从基础到进阶全面掌握Node.js技术,适用于初学者、进阶开发者及全栈工程师。
207 9
|
JSON JavaScript 前端开发
使用JavaScript和Node.js构建简单的RESTful API
使用JavaScript和Node.js构建简单的RESTful API
|
4月前
|
JavaScript Unix Linux
nvm与node.js的安装指南
通过以上步骤,你可以在各种操作系统上成功安装NVM和Node.js,从而在不同的项目中灵活切换Node.js版本。这种灵活性对于管理不同项目的环境依赖而言是非常重要的。
1071 11

热门文章

最新文章

下一篇
oss云网关配置