webpack是如何實現動態導入的

前言

在單頁應用中,常常使用 webpack 的 動態導入 功能來異步加載模塊,從而減小部分文件的體積。咱們能夠經過webpack 提供的 import()require.ensure 兩個 API 來使用該功能。因爲兩個方法根本實現都是相同的,本文的示例都基於 import() 方法javascript

從一個例子開始

基本環境

webpack 4.35.3java

代碼

這邊用一個最簡單的例子,有兩個文件,index.js 爲入口文件,該文件使用 import() 來動態導入 async.js 文件。webpack

index.jsweb


async.jspromise

咱們執行 webpack --mode=development 來得到編譯後文件 main.js0.js。其中,main.js 包含 index.js 代碼,0.js 包含 async.js 代碼。bash

分析

主入口

首先,main.js 文件做爲整個應用的入口,咱們來看看裏面有什麼東西。異步

咱們先忽略詳細的代碼邏輯,總體看下來,發現這個文件其實就是一個自執行函數,該函數把 轉化後的 index.js文件路徑 組成一個 modules 傳給主函數。async

主函數中,會執行 __webpack_require__(__webpack_require__.s = "./src/index.js"); 來初始化入口模塊。函數

這個函數的做用就是加載而且執行指定的模塊,而且返回模塊的 module.exports。 這邊執行模塊調用了 modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 來執行以前傳入的 module,也就是 轉換後的index.jsfetch

咱們抽離當中的代碼看一下。

發現import 被轉化成了 __webpack_require__.e(/*! import() */ 0),咱們順藤摸瓜,繼續來看看 __webpack_require__.e 作了些什麼。

__webpack_require__.e

代碼可能有點眼花,看下來無非就是作了這麼一件事情。

  1. 根據 installedChunks 檢查是否加載過該 chunk
  2. 假如沒加載過,則發起一個 JSONP 請求去加載 chunk
  3. 設置一些請求的錯誤處理,而後返回一個 Promise

當 Promise 返回以後,就會繼續執行咱們以前的異步請求回調

__webpack_require__.e(/*! import() */ 0)
    .then(
        __webpack_require__.bind(null, /*! ./async */ "./src/async.js")
    )...
複製代碼

這裏直接調用了 __webpack_require__ 去加載咱們的 異步模塊

這裏就有兩個問題?

  1. __webpack_require__ 是根據咱們以前傳入的 modules 來獲取 module 的,可是,在 __webpack_require__.e 中並無看到有對 modules 執行操做的代碼。那 modules 究竟是何時被更新的呢?
  2. promiseresolvereject 所有存入了 installedChunks 中, 並無在獲取異步chunk成功的onload 回調中執行 resolve,那麼,resolve 是何時被執行的呢?
var promise = new Promise(function(resolve, reject) {
    installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
複製代碼

帶着疑問,咱們來看一下加載的 0.js 中的內容。

異步Chunk

0.js

這邊邏輯也很簡單,只是往window["webpackJsonp"]裏面 push chunkId 和 含有的modules。咱們搜一下 window["webpackJsonp"], 發如今 main.js 中有這麼一段代碼。

這邊用自定義的 webpackJsonpCallback 函數替換window["webpackJsonp"]push 方法。因此說,咱們以前 0.js 執行的 push 其實就是執行了自定義的 webpackJsonpCallback 函數

webpackJsonpCallback

能夠看到,webpackJsonpCallback 作了2件事情。

  1. 執行 installedChunks 中的 resolve , 讓 import() 得以繼續執行。
  2. chunk 中含有的 模塊所有註冊到 modules 變量中。

如今,咱們終於理清了異步加載的所有流程。

more

其實,以上的代碼只是最簡單的狀況,隨着代碼的不一樣,生成的函數具體內容也會有所差別。好比,咱們添加預加載代碼 import(/* webpackPrefetch: true */'./preload'); , 生成的 main.js 文件中才會有支持該功能的代碼,不難看出,webpack此舉是爲了控制生成對文件大小,對具體細節感興趣的同窗,能夠本地嘗試一下。

相關文章
相關標籤/搜索