js module bundle 模組捆綁

leslee同學發表於2018-09-20

什麼是模組捆綁

模組捆綁就是把所有的模組依賴捆綁到一個檔案裡面,使各種模組依賴可以按照順序的執行,webpack, rollup 做的都是這個東西.

譯者注(此文所有連結需牆)

為什麼需要模組捆綁

如果專案中使用的模組過多,那麼你就需要一個一個的使用script 標籤引入到html中去,然後html執行的時候會一個一個的去載入你引入的模組,這會導致延遲過久,使用者體驗不好.

模組捆綁的方式

如果你使用標準的模組定義方式定義,那麼使用串聯和壓縮你的檔案 可以很好的解決這個問題,如果你使用了不標準的模組定義方式,那麼你就需要使用一些工具來讓他們繫結在一起, 這就是 browserify requirejs, webpack, 所做的事情.

下面我們來看一些模組捆綁在一起的方法.

commonjs 的繫結方式

使用過nodejs 的應該對commonjs 不陌生了

因為commonjs 是同步的 所以對瀏覽器不怎麼友好 (頁面卡頓)

看個例子, 計算一個數字型別陣列的平均數

var myDependency = require(‘myDependency’);

var myGrades = [93, 95, 88, 0, 91];

var myAverageGrade = myDependency.average(myGrades);
複製程式碼

browserify 是捆綁commonjs模組的一個工具,當你使用

browserify main.js -o bundle.js

時,browserify 會通過 ast(抽象語法樹), 把你的所有依賴找出來並且組織成一張圖(一種資料結構)然後把他們都捆綁進一個js檔案中,然後把這個js 引入到html中,從而實現捆綁功能.

就算你有很多個檔案,有很多個依賴,你只需要告訴browserify 你的入口檔案就可以了.

AMD 的捆綁模式

requirejs 就是一個繫結 AMD 模組的出色的工具.

AMD 跟 browserify 最大的一個區別就是, AMD 是非同步的.

嚴格來說 AMD 的模組捆綁的時候不需要 build 的步驟, 因為AMD 載入模組是非同步的. 也就是說 使用者第一次訪問你的頁面的時候, 他只會載入必須的模組,其他不需要的,並不會去載入.但是為了縮小檔案體積的效能優化,還是有一些工具具備 build 的功能.

webpack 的捆綁模式

webpack 允許你使用任何一種模組化的標準來定義模組,commonjs AMD es6 都可以,你可能奇怪為什麼已經有了其他的工具,為什麼還需要webapack 來捆綁模組呢,webpack 的功能不止捆綁模組 他還提供了其他很多有用的工具, 比如 code splitting

如果你有一些程式碼,是在特定條件下才需要的,你可以使用 code splitting 來提取那部分程式碼,讓他需要的時候再執行,避免程式碼體積過大.

es6 模組捆綁

es6模組跟AMD COMMONJS 最大的不同就是 es6 是設定於內部的靜態語法,也就是說 當你匯入一個模組的時候, 這個匯入是在 編譯時解析的, 並不是動態的,意味著,我們在程式執行之前,可以刪除那些沒有被其他模組依賴的模組,這能節省空間,和減輕瀏覽器的壓力.

那麼問題來了,這樣跟消除那些死程式碼(沒有執行到的程式碼)有什麼不同?

答案是 看情況

注意,死程式碼消除是一個移除沒用的程式碼和變數的優化手段,想象一下你帶了過重的行李,而你不需要用到他們.

有時候死程式碼消除可以和 es6 模組的 移除沒有被依賴的模組是一樣的,有時又是不一樣的,這裡有一個很酷的例子

使es6 模組不同的是 消除死程式碼的不同途徑, 叫 tree shaking . 這個東西實質上是反過來的死程式碼去除,死程式碼去除是找到沒使用的程式碼不要, tree shaking 是隻打包會執行到的程式碼,因為不一定依賴了的程式碼都用得上,所以後者的效能會比前者更好.

來看一個例子

export function each(collection, iterator) {
  if (Array.isArray(collection)) {
    for (var i = 0; i < collection.length; i++) {
      iterator(collection[i], i, collection);
    }
  } else {
    for (var key in collection) {
      iterator(collection[key], key, collection);
    }
  }
 }

export function filter(collection, test) {
  var filtered = [];
  each(collection, function(item) {
    if (test(item)) {
      filtered.push(item);
    }
  });
  return filtered;
}

export function map(collection, iterator) {
  var mapped = [];
  each(collection, function(value, key, collection) {
    mapped.push(iterator(value));
  });
  return mapped;
}

export function reduce(collection, iterator, accumulator) {
    var startingValueMissing = accumulator === undefined;

    each(collection, function(item) {
      if(startingValueMissing) {
        accumulator = item;
        startingValueMissing = false;
      } else {
        accumulator = iterator(accumulator, item);
      }
    });

	return accumulator;
}
複製程式碼

匯入所有的

import * as Utils from ‘./utils.js’;
複製程式碼

然後我們只掉用一個方法

Utils.each([1, 2, 3], function(x) { console.log(x) });
複製程式碼

tree shaking 過後 你們應該知道剩下什麼了吧

// 剩下一個函式打包進來了 因為只是用了一個函式 就算我匯入了所有的函式 他也不會打包進來,但是如果是尋找死程式碼,也就是沒有被依賴的程式碼,那是不會刪除的,因為他們所有的程式碼都被依賴了
function each(collection, iterator) {
  if (Array.isArray(collection)) {
    for (var i = 0; i < collection.length; i++) {
      iterator(collection[i], i, collection);
    }
  } else {
    for (var key in collection) {
      iterator(collection[key], key, collection);
    }
  }
 };

each([1, 2, 3], function(x) { console.log(x) });
複製程式碼

構建 es6 模組

不幸的是,es6 模組還需要做一些額外的工作才可以被構建,因為瀏覽器還沒有很好的實現es6 的模組載入. (個人感覺很難做到,國內的瀏覽器亂象就不說了,感覺我們任重而道遠啊)

這裡有兩個方法讓模組載入可以再瀏覽器使用

  1. 使用babel 去轉譯es6 的程式碼到es5 的 commonjs 格式 或者 AMD 格式, 或者 UMD模式,然後把他們放進webpack 仲去構建
  2. 使用rollup.js 這是一個類似於 webpack 的構建工具, (但是已經有了 webpack 了為什麼後還要rollup呢? 有興趣的可以區瞭解一下,提示一下,vue react 也已經使用了rollup 而放棄了webpack 瞭解一下?)

好咯,大概介紹到這裡,下回見.

相關文章