解析 Webpack中import、require、按需加載的執行過程

最近因爲一篇分享手淘過年項目中採用到的前端技術的影響,從新研究了一下項目中CSS的架構.原本打算寫一篇文章,可是寫到一半忽然發現本身像在寫文檔介紹同樣,因此後來就放棄了。可是以爲過程當中研究的 Webpack 卻是能夠單獨拿出來說一講css

在這裏很是感謝 印記中文 團隊翻譯的 Webpack 文檔.

搭建一個簡單環境

  1. npm init
  2. npm install css-loader html-webpack-plugin style-loader webpack webpack-cli
// Webpack 4.0
const htmlPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    filename: "[name].js",
    path: __dirname + "/dist"
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
          },
        ]
      }
    ]
  },
  plugins: [
    new htmlPlugin({
      title: "Test Webpack",
      filename: "index.html"
    })
  ]
};

一個基本的配置就搭建好了,詳細的配置內容我就不介紹了, 而後咱們在 src/index.js 上面寫咱們的測試代碼, 在 dist/main.js 看一下 webpack 實現的原理,那麼目前咱們的項目結構是這樣子的html

|-- project
    |-- dist
    |-- src
        |-- index.js
    |-- node_modules
    |-- webpack.config.js

webpack 中 require 和 import 的執行過程

在進入按需加載的講解以前,咱們須要看一個問題 requireimportwebpack 的執行過程是怎樣的呢 ?如今咱們在 src創建兩個文件 index.jsmodule-es6.jsmodule-commonjs.js。咱們經過這三個文件解析 requireimport 的執行過程前端

首先咱們要區分的是 CommonJSES6 模塊導出之間的區別,在 CommonJS 中你導出模塊方式是改變 module.exports,可是對於 ES6 來講並不存在 module 這個變量,他的導出方式是經過一個關鍵詞 export來實現的。在咱們書寫 JS文件的時候,咱們發現不管是以 CommomJS 仍是 ES6 的形式導出均可以實現,這是由於 Webpack作了一個兼容處理node

咱們創建一個小 DEMO 來查看一下,咱們如今上面創建的三個文件的代碼以下webpack

// index.js
// import moduleDefault, { moduleValue } from "./module-es6.js";
// import moduleDefault, { moduleValue1, moduleValue2 } from "./module-commanjs.js";
// module-es6.js
export let moduleValue = "moduleValue" //ES6模塊導出
export default "ModuleDefaultValue"
// module-commonjs.js
exports.moduleValue1 = "moduleValue1"
exports.moduleValue2 = "moduleValue2"

如今咱們打開 index.js 中加載 module-commonjs.js 的代碼,首先會先給當前模塊打上 ES6模塊的標識符,在 index 則會產生兩個變量 AB. A 保存 module-commonjs 的導出的結果,B 則是兼容 CommonJs中沒有 ES6經過 export default導出的結果,其值跟 A同樣. 用B來兼容 export default 的結果es6

而後咱們從新註釋代碼,再打開 index.js 中加載 module-es6.js 的代碼web

此次和上面同樣會先給當前模塊打上 ES6模塊的標識符,而後去加載 module-es6,獲取他的導出值。可是瀏覽器是不識別 export 這個關鍵詞的因此 Webpack 會對的代碼進行解釋,首先給 module.exports 設定導出的值,若是是 export default 會直接賦值給 module.exports,若是是其餘形式,則給module.exports的導出的key設定一個 getter,該 getter 的返回值就是導出的結果npm

而對於require來講整個執行過程其實過程和import是同樣的。數組

對於 webpack 來講只要你使用了 import 或者 export等關鍵字, 他就會給 module.exports添加一個__esModule : true 來識別這是一個 ES6的模塊,經過這個值來作一些特殊處理promise

若是以爲我上面講的不太明白 那能夠看看下面這些代碼

let commonjs = {
  "./src/index.js": function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    //給當前模塊打上 `ES6`模塊的標識符
    __webpack_require__.r(__webpack_exports__); //給當前模塊打上 `ES6`模塊的標識符

    // 執行 ./src/module-commonjs.js 的代碼 獲取導出值
    var A = __webpack_require__("./src/module-commonjs.js");

    // 根據 ./src/module-commonjs.js 是否爲ES6模塊 給返回值增長不一樣的 getter函數
    var B = __webpack_require__.n(A);
  },
  "./src/module-commonjs.js": function(module, exports) {
    exports.moduleValue1 = "moduleValue1";
    exports.moduleValue2 = "moduleValue2";
  }
};

let es6 = {
  "./src/index.js": function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    //給當前模塊打上 `ES6`模塊的標識符
    __webpack_require__.r(__webpack_exports__);

    // 執行 ./src/module-commonjs.js 的代碼 獲取導出值
    var A = __webpack_require__("./src/module-es6.js");
  },

  "./src/module-es6.js": function(module, __webpack_exports__, __webpack_require__) {
    //給當前模塊打上 `ES6`模塊的標識符
    __webpack_require__.r(__webpack_exports__);

    // 設置 __webpack_exports__.moduleValue 的 getter
    __webpack_require__.d(__webpack_exports__, "moduleValue", function() {
      return moduleValue;z
    });

    __webpack_exports__["default"] = "ModuleDefaultValue";

    let moduleValue = "moduleValue";
  }
};

按需加載的執行過程

看完上面的 requireimport,咱們回到 按需加載 這個執行過程. webpack 的按需加載是經過 import() 或者 require.ensure()來實現的,有些讀者可能對於 require.ensure 比較熟悉,因此咱們先看看 require.ensure 的執行過程,
如今咱們修改創建一個 module-dynamic.js文件,而後修改 index.js文件

這裏吐槽一個問題,require.ensure 第一個參數是一個尷尬的存在,寫和不寫根本沒差,若是你填了的這個參數,webpack 會幫你把文件加載近來,可是不執行。一堆不執行的代碼是沒有意義的,你想讓他執行就必須 require() 一遍,可是執行力 require 也會幫你加載文件。因此根本沒差
// index.js
setTimeout(function() {
  require.ensure([], function() {
    let d = require("./module2")
  });
}, 1000);

// module2.js
module.exports = {
  name : "Jason"
}

執行 require.ensure(dependencies,callback,errorCallback,chunkName) 實際上會返回一個 promise , 裏面的實現邏輯是 先判斷 dependencies 是否已經被加載過,若是加載過則取緩存值的 promise, 若是沒有被加載過 則生成一個 promise 並將 promise 裏面的 resolve,rejectpromise自己 存入一個數組,而後緩存起來.接着生成一個 script 標籤,填充完信息以後添加到HTML文件上,其中的 scriptsrc屬性 就是咱們按需加載的文件(module2),webpack 會對這個 script 標籤監聽 errorload時間,從而作相應的處理。

webpack打包過程當中會給 module2 添加一些代碼,主要就是主動觸發 window["webpackJsonp"].push這個函數,這個函數會傳遞
兩個參數 文件ID文件內容對象,其中 文件標示若是沒有配置的話,會按載入序號自動增加,文件內容對象實際上就是上文說的 require.ensure第一個參數dependencies的文件內容,或者是 callback,errorCallback裏面須要加載的文件,以 key(文件路徑) --- value(文件內容)的形式出現.裏面執行的事情其實就是執行上面建立的promiseresolve函數,讓require.ensure裏面的callback執行,以後的執行狀況就跟我上面將 requirimport 同樣了

固然其實講了那麼長的 require.ensure並無什麼用,由於這個函數已經被 import() 取代了,可是考慮到以前的版本應該有不少人都是用 require.ensure 方法去加載的,因此仍是講一下,並且其實 import 的執行過程跟 require.ensure 是同樣的,只不過用了更友好的語法而已,因此關於 import 的執行流程我也沒啥好講的了,感興趣的人看一下二者的 API介紹就行了。

到這裏就正式講完了,若是有大牛路過看到有不對的地方,但願能幫我指出來.很是謝謝!!!

而後再次感謝印記中文 團隊翻譯的 Webpack 文檔

相關文章
相關標籤/搜索