如何编写同时用于 Node 和浏览器的 JavaScript 包

更新日期: 2019-04-19阅读: 1.5k标签: 浏览器

我多次看到大家在这个问题上产生困惑,甚至经验丰富的 JavaScript 开发者都可能错过它的一些微妙之处。所以我认为应该写这么一个简短的教程

假设有一个 JavaScript 模块想发布在 npm 中,它既能在 Node 中运行,又能在浏览器中运行。这会产生一个问题!这个特定的模块对于 Node 和浏览器的运行,会有一点不同的实现。

这种情况相当常见,因为这 Node 和浏览器之间存在许多微小的环境差异。如何正确实现相当棘手,尤其是想在针对浏览的实现中极尽可能地减少依赖库的时候。


构建一个 JS 包

来写一个很小的,称为 base64-encode-string 的 JavaScript 包。它的作用是将输入的字符串以 base64 编码之后输出。

对浏览器来说,使用内置的 btoa 函数很容易就能实现:

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

但 Node 中没有 btoa 函数,所以我们要创建一个  Buffer,然后调用 buffer.toString()

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

两种方法都能输出正确的 base64 编码,比如:

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

现在我们需要一些方法来检验它是运行在浏览器中还是运行在 Node 中,然后我们才能调用正确的版本。Browserify 和 webpack 都定义了 process.browser,在浏览器中它返回 true,而在 Node 中它返回 false。所以我们很容易做到:

if (process.browser) {
  module.exports = function (string) {
    return btoa(string);
  };
} else {
  module.exports = function (string) {
    return Buffer.from(string, 'binary').toString('base64');
  };
}

我们把文件命名为 index.js,键入 npm publish,然后一切都搞定了。但这种方法存在一个巨大的性能问题。

index.js 中包含了对 Node 内建的 process 和 Buffer 的引用,Browserify 和 Webpack 都会在打包的时候自动包含相应的 polyfill(引1引2)。

虽然这个模块只有 9 行,但 Browserify 和 Webpack 最小化并打包出来有 24.7KB(7.6KB min+gz)。在浏览器中只需要 btoa 就能解决的问题居然需要引用这么大的东西!


超爱“browser” 选项

如果在 Browserify 和 Webpack 的文件中寻找解决办法,最终会找到 node-browser-resolve。这涉及到package.json 中的 “browser” 选项。它定义在为浏览器构建模块时的行为。

使用这个技术,需要在 package.json 中添加:

{
  /* ... */
  "browser": {
    "./index.js": "./browser.js"
  }
}

然后将两个函数分拆到 index.js 和 browser.js 两个文件中:

// index.js
module.exports = function (string) {
  return Buffer.from(string, 'binary').toString('base64');
};
// browser.js
module.exports = function (string) {
  return btoa(string);
};

这之后,Browserify 和 Webpack 会产生更合适的结果:Browserify 最小只有 511 字段(315 min+gz),Webpack 则是 550 字节(297 min+gz)。

这个包发布到 npm 之后,在 Node 中 运行 require(‘base64-encode-string’) 都是引用 Node 版本,而使用 Browerify 或 Webpack 则会引用浏览器版本。成功!

对于 Rollup 来说会更复杂一点。Rollup 用户需要使用 rollup-plugin-node-resolve 并在选项中设置 browser 为 ture。

对于 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 很快会变成下面这个样子:

{
  /* ... */
  "browser": {
    "./index.js": "./browser.js",
    "./widget.js": "./widget-browser.js",
    "./doodad.js": "./doodad-browser.js",
    /* etc. */
  }
}

你每需要一个浏览器模块,就必须创建两个单独的文件,然后在 “browser” 选项中添加一行来关联它们。还得小心不要写错什么!

而且你会发现自己需要将部分代码提取为单独的模块,因为你不想使用 if (process.browser) {} 来进行检查。当这些 *-browser.js 文件逐渐积累起来,就会使代码导航越来越困难。

解决这个问题有几个不同的解决方案。我个人喜欢使用 Rollup 来作为构建工具,它会自动将一个代码库中的代码拆分成 index.js 和 browser.js 文件,节约空间和时间。

想这样做需要安装 rollup 和 rollup-plugin-replace,然后定义 rollup.cofnig.js 文件:

import replace from 'rollup-plugin-replace';
export default {
  entry: 'src/index.js',
  format: 'cjs',
  plugins: [
    replace({ 'process.browser': !!process.env.BROWSER })
  ]
};

(我们会使用 process.env.BROWSER 来切换针对浏览器的构建和针对 Node 的构建。)

接下来,创建 src/index.js 文件,它包含一个单独的函数,其中用到了 process.browser 条件:

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

然后在 package.json 中添加 prepublish 步骤,用于生成文件:

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

生成的文件相当简单而且易读:

// index.js
'use strict';

function base64Encode(string) {
  {
    return Buffer.from(string, 'binary').toString('base64');
  }
}

module.exports = base64Encode;
// browser.js
'use strict';

function base64Encode(string) {
  {
    return btoa(string);
  }
}

module.exports = base64Encode;

你会发现 Rollup 根据需要自动将 process.browser 转变为 true 或 false,然后去掉无用的代码。因此在针对浏览器的生成结果中不会引用 process 或 Buffer。

这种技术让你可以在代码中任意使用 process.browser 条件,发布出来的结果总是两个小文件,一个 index.js,一个 browser.js。在 Node 环境只有 Node 相关的代码,而在浏览器环境则只有浏览器相关的代码。

你还可以配置 Roolup 生成 ES 模块构建、IIFE 构建,或 UMD 构建。比如我的 marky 项目就是一个拥有多个 Rollup 构建目标的简单库。

本文描述的项目(base64-encode-string) 已经发布到 npm 了,你可以去深入了解它。源代码在 GitHub 上。


链接: https://www.fly63.com/article/detial/8697

浏览器禁用了javascript,各种浏览器如何开启javascript的方法总汇

您的浏览器禁用了JS脚本运行,请启用该功能。怎么解除浏览器禁用js?这篇文章将总结整理各个浏览器如何开启、禁用javascript的方法总汇。

浏览器的回流与重绘 (Reflow & Repaint)

浏览器使用流式布局模型 (Flow Based Layout)。浏览器会把HTML解析成DOM,把CSS解析成CSSOM,DOM和CSSOM合并就产生了Render Tree。有了RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。

IE6浏览器有哪些常见的bug,以及解决IE6常用bug的方法

IE6不支持min-height,解决办法使用css hack,ol内li的序号全为1,不递增。解决方法:为li设置样式display: list-item;定位父元素overflow: auto;,包含position: relative;子元素,子元素高于父元素时会溢出。解决办法:

css重设样式_清除浏览器的默认样式

由于不同的浏览器默认的样式也不同,所以在网页开发前设置一个公用样式,来清除各个浏览器的默认样式,已达到做的网页在各个浏览器中达到统一。

浏览器访问一个网站所经历的步骤

浏览器访问网站的步骤:Chrome搜索自身的DNS缓存、读取本地HOST文件、浏览器发起一个DNS的一个系统调用、浏览器获得域名对应的IP地址后,发起HTTP三次握手、TCP/IP连接建立起来、服务器端接受到了这个请求、浏览器根据拿到的资源对页面进行渲染

一个新式的基于文本的浏览器 Browsh

Browsh是一个纯文本浏览器,可以运行在大多数的TTY终端环境和任何浏览器。目前,终端客户端比浏览器客户端更先进。终端客户端即时更新和交付,以便于体验新的功能,例如,你可以观看视频。

浏览器内核有哪些?主流浏览器的所使用的内核介绍

一般说的浏览器内核是指浏览器最重要的核心部分,RenderingEngine,翻译成中文大概意思就是“解释引擎”,我们一般称为浏览器内核。由于不同的内核各自有一套自己的渲染网页和解释页面代码的机制,所以就会有一些问题存在。

程序员眼中的浏览器是什么样的?IE:有本事你卸了我啊

主流浏览器之争从上个世纪开就开始,已经持续了很长的时间。人们都在笑话IE,纷纷转向其它浏览器。今天,我向大家分享一下针对IE的搞笑图片,只是逗乐而已,喝杯咖啡,坐下来慢慢享受吧。

精打细算浏览器空闲时间

有时候我们希望在浏览器中执行一些低优先级的任务,比如记录统计数据、做一些耗时的数据处理等,暂且将其称为后台任务。这些任务跟动画计算、合成帧、响应用户输入等高优先级的任务共享主线程

深入浏览器事件循环的本质

浏览器的事件循环,前端再熟悉不过了,每天都会接触的东西。但我以前一直都是死记硬背:事件任务队列分为macrotask和microtask,浏览器先从macrotask取出一个任务执行,再执行microtask内的所有任务,接着又去macrotask取出一个任务执行

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!