首页 存档 技术 查看内容

javascript包:一个浏览器与Node可以共用的JS包

2018-3-30 13:00 |来自: 互联网 323 0

摘要: 我看到很多在这个问题上发生混淆的情况,甚至经验丰富的 JavaScript 开发者都有可能错过了它的一些微妙之处。因此我觉得有必要写一篇简短的教程。 假设你有一个 JavaScript 模块想发布在 npm,这个模块既能在 Node ...

我看到很多在这个问题上发生混淆的情况,甚至经验丰富的 JavaScript 开发者都有可能错过了它的一些微妙之处。因此我觉得有必要写一篇简短的教程。

假设你有一个 JavaScript 模块想发布在 npm,这个模块既能在 Node 中使用也能在浏览器中使用。现在有一个问题,实现这个独特的模块时遇到一点困难,因为它针对 Node 和浏览器有不同的实现。

这种情况相当频繁,因为 Node 和浏览器间存在很多细小的环境差异。要正确实现显得有些棘手,尤其是在你想将其优化到最小以用于浏览器的情况下。

来构建一个 JS 包

先来写一个小小的 JavaScript 包,我们称之为base64-encode-string。它接受一个字符串输出,对其进行 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'); // Zm9vb64encode('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 内置的processBuffer模块,Browserify 和 Webpack 都会在结果中包含thepolyfills

这个只有 9 行的模块,Browserify 和 Webpack 却创建了最小化 24.7KB 的文件(最小化 gz 后是 7.6KB)。 那是一个很大的东西,而在浏览器中人需要一个使用btoa的表达式!

“browser”配置,我是多么爱你

如果你在 Browserify 和 Webpack 文件中搜索解决这个问题的技巧,你一定会发现node-browser-resolve。这是一个关于package.json内的"browser"配置的说明,它用来定义针对浏览器构建时的替代模块。

使用这个技术,可以在package.json中添加:

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

两个函数分别放在两个结果文件中,index.jsbrowser.js

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

// browser.jsmodule.exports = function (string) {
return btoa(string);};

进行了这样的修正之后,Browserify 和 Webpack 产生了更容易阅读的结果:Browserify 的最小化后是 511 字节 (最小化 gz 后是 315 字节),Webpack 的最小化后是 550 字节(最小化 gz 后是 297 字节)。

当我们把包发布到 npm 上后,在 Node 中使用require('base64-encode-string')会取得 Node 版本,而使用 Browserify 或 Webapck 会取得浏览器版本。成功!

对于 Rollup 来说要麻烦一些,但不会有太多额外的工作。Rollup 用户需要使用rollup-plugin-node-resolve并在选项中设置browsertrue

对于 jspm 来说,很不幸,它不支持“browser”选项,不过 jspm 用户可以绕过它,这样做:require('base64-encode-string/browser')或者jspm install npm:base64-encode-string -o "{main:'browser.js'}"。 或者,包作者可以在package.json指定一个“jspm”选项

高级技巧 Advanced techniques

直接使用"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.jsbrowser.js文件。它在打包你发布给客户的代码时带来了节约空间和时间的好处。

想这样做的话,安装rolluprollup-plugin-replace,然后定义一个rollup.config.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

声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部