歡迎訪問昆山寶鼎軟件有限公司網站! 設為首頁 | 網站地圖 | XML | RSS訂閱 | 寶鼎郵箱 | 后臺管理
?

新聞資訊

MENU

軟件開發知識

如何編寫同時用于 Node 和瀏覽器的 JavaScript 包

點擊: 次  來源: 時間:2017-03-13

引用我多次看到大家在這個問題上產生困惑,甚至經驗豐富的 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 上。

原文:How to write a JavaScript package for both Node and the browser
譯者:Viyi
排列三305组选前后关系 专业股票配资平台有哪些 江苏快3双彩网 河南快3历史记录 多江西彩开奖结果 北京赛车开奖预测软件 上海快三在线计划网站 算局七星彩奖表排列五 北京十一选五体彩开 时时彩软件下载安装 北京塞车pk10