Webpack 之 basic chunk graph

源文件以及配置文件

有如下文件 a.js / b.js / c.js / d.js 以及 webpack.config.js, 其中 a.js 爲入口文件,它們之間的依賴關係以下圖,實心箭頭表明異步加載。javascript

// a.js - 入口文件
import add from './b.js'
add(1, 2)
import('./c.js').then(del => del(1, 2))
  
// b.js
import mod from './d.js'
export default function add(n1, n2) {
  return n1 + n2
}
mod(100, 11)

// c.js
import mod from './d.js'
mod(100, 11)
import('./b.js').then(add => add(1, 2))
export default function del(n1, n2) {
  return n1 - n2
}

// d.js
export default function mod(n1, n2) {
  return n1 % n2
}

// webpack.js
module.exports = {
  entry: {
    app: './src/a.js'
  },
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].bundle.[chunkhash:8].js',
    publicPath: '/'
  },
  optimization: {
    runtimeChunk: {
      name: 'bundle'
    }
  },
}
複製代碼

概念

chunkGroup:一個 chunkGroup 能夠包含多個 chunk,能夠經過 chunks 字段看出它由哪些 chunk 組成。Webpack 會爲每一個入口建立一個 entrypoint,下圖就是入口 app: './src/a.js' 的 entrypoint,能夠看出 entrypoint 就是一個 chunkGroup。 css

chunk:一個 chunk 能夠包含多個 module,能夠經過 _groups 字段看出它所屬的 chunkGroup,經過 _modules 看出它由哪些 module 組成。Webpack 在爲每一個入口建立 entrypoint 的同時,也會建立一個 chunk,以下圖: java

module:資源文件,如 js / css / 圖片 等,能夠經過 _chunks 看出它所屬的 chunk,經過 blocks 看出它異步加載的模塊。Webpack 會將它封裝成 NormalModule 對象,'./src/a.js' 對應的 NormalModule 對象以下圖: node

block:在模塊中異步加載的模塊,好比:import('./c.js').then(),Webpack 會將它封裝成 ImportDependenciesBlock。在 a.js 中異步加載的 c.js 封裝以後的結構以下: webpack

簡寫說明:

爲了在後續的源碼解析中更清晰的描述,使用以下簡寫。另外:本次源碼解析着重於 module graph & basic chunk graph 的建立,多餘的分支不講,由於我也沒看。web

  • NM('./src/a.js'):'./src/a.js' 模塊文件封裝以後的 NormalModule.chrome

  • chunk('app'):Webpack 爲入口建立的 chunk.npm

  • chunkGroup('app'):Webpack 爲入口建立的 chunkGroup.json

  • block('./c.js'):異步加載的模塊 './c.js' 封裝以後的 ImportDependenciesBlock.數組

Webpack 會爲每一個異步加載的模塊建立一個 chunk & chunkGroup,並關聯它們。

  • chunk('./c.js'):爲異步加載的模塊 './c.js' 建立的 chunk.

  • chunkGroup('./c.js'):爲異步加載的模塊 './c.js' 建立的 chunkGroup.

建立並關聯入口 chunk & chunkGroup

// compilation.js
class Compilation {
  ...
  seal () {
    ...
    // 建立 chunk 以前的 hook
    this.hooks.beforeChunks.call();
    // 根據 addEntry 方法中收集到入口文件組成的 _preparedEntrypoints 數組
    for (const preparedEntrypoint of this._preparedEntrypoints) {
      const module = preparedEntrypoint.module; // 即 NM('./src/a.js')
      const name = preparedEntrypoint.name; // 即 'app'
      const chunk = this.addChunk(name); // 爲每一個入口建立 chunk
      const entrypoint = new Entrypoint(name); // 爲每一個入口建立 entrypoint,它就是 chunkGroup
      entrypoint.setRuntimeChunk(chunk); // 設置爲 runtime chunk
      entrypoint.addOrigin(null, name, preparedEntrypoint.request);
      this.namedChunkGroups.set(name, entrypoint);
      this.entrypoints.set(name, entrypoint);
      this.chunkGroups.push(entrypoint);
        
      // 創建 chunkGroup 和 chunk 之間的關係
      GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
      // 創建 chunk 和 module 之間的關係
      GraphHelpers.connectChunkAndModule(chunk, module);
        
      chunk.entryModule = module;
      chunk.name = name;
        
      this.assignDepth(module);
    }
    this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice()); //接下來的重點
    // 對 module 進行排序
    this.sortModules(this.modules);
    // 建立 chunk 以後的 hook
    this.hooks.afterChunks.call(this.chunks);
    this.hooks.optimize.call();

    while (
      this.hooks.optimizeModulesBasic.call(this.modules) ||
  	  this.hooks.optimizeModules.call(this.modules) ||
	  this.hooks.optimizeModulesAdvanced.call(this.modules)
    ) {
      /* empty */
    }
    // 優化 module 以後的 hook
    this.hooks.afterOptimizeModules.call(this.modules);
    while (
      this.hooks.optimizeChunksBasic.call(this.chunks, this.chunkGroups) ||
	  this.hooks.optimizeChunks.call(this.chunks, this.chunkGroups) ||
	  // 主要涉及到 webpack.config.js optimization 配置
 	  this.hooks.optimizeChunksAdvanced.call(this.chunks, this.chunkGroups)
    ) {
      /* empty */
    }
    // 優化 chunk 以後的 hook
    this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
    ...
  }
  ...
}

// GraphHelpers.js
/** * @param {ChunkGroup} chunkGroup the ChunkGroup to connect * @param {Chunk} chunk chunk to tie to ChunkGroup * @returns {void} */
GraphHelpers.connectChunkGroupAndChunk = (chunkGroup, chunk) => {
  if (chunkGroup.pushChunk(chunk)) {
    chunk.addGroup(chunkGroup);
  }
};
/** * @param {Chunk} chunk Chunk to connect to Module * @param {Module} module Module to connect to Chunk * @returns {void} */
GraphHelpers.connectChunkAndModule = (chunk, module) => {
  if (module.addChunk(chunk)) {
    chunk.addModule(module);
  }
};
複製代碼

Webpack 會遍歷配置文件中的入口,爲每一個入口建立一個 chunk & chunkGroup,此時的 chunk 還沒收集任何 NormalModule,包括入口文件對應的 NormalModule。

將 chunk 設爲 runtimeChunk,當 Webpack 編譯完成後,webpack runtime 代碼會注入到 runtimeChunk。

調用 GraphHelpers.connectChunkGroupAndChunk()GraphHelpers.connectChunkAndModule() 創建 chunk & chunkGroup 之間的關係,以及 chunk & 入口模塊 之間的關係。這裏還未涉及 chunk 和 入口模塊依賴的模塊的關係。

至此,Webpack 已經建立了 chunkGroup('app') & chunk('app'),chunk('app').modules = Set(NM('./src/a.js'))

processDependenciesBlocksForChunkGroups

接下來咱們重點看一下 this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());

該函數主要實現了兩塊功能:

  1. 建立 module graph,保存在 blockInfoMap.
  2. 根據 module graph 建立 basic chunk graph,即每一個 chunk 收集好相應的 modules,保存在 chunkGroups.

建立 module graph

const iteratorDependency = d => {
  // We skip Dependencies without Reference
  const ref = this.getDependencyReference(currentModule, d);
  if (!ref) {
    return;
  }
  // We skip Dependencies without Module pointer
  const refModule = ref.module;
  if (!refModule) {
    return;
  }
  // We skip weak Dependencies
  if (ref.weak) {
    return;
  }

  blockInfoModules.add(refModule);
};

const iteratorBlockPrepare = b => {
  blockInfoBlocks.push(b);
  // blockQueue push b(異步依賴),從而進入到下一次的內層循環
  blockQueue.push(b);
};

// 本次 compilation 包含的全部 module
for (const modules of this.modules) {
  blockQueue = [module];
  currentModule = module;
  while (blockQueue.length > 0) {
    block = blockQueue.pop(); // 同步 module / 異步 block
    blockInfoModules = new Set(); // 保存模塊依賴的同步 module
    blockInfoBlocks = []; // 保存模塊依賴的異步 block

    if (block.variables) {
      iterationBlockVariable(block.variables, iteratorDependency);
    }

    // 在 blockInfoModules 中添加 dependencies 中的普通 module
    if (block.dependencies) {
      iterationOfArrayCallback(block.dependencies, iteratorDependency);
    }

    // 在 blockInfoBlocks 和 blockQueue 數組中添加異步 module,
    // 這樣異步 block 也會進入到內層循環,去獲取異步 block 的依賴
    if (block.blocks) {
      iterationOfArrayCallback(block.blocks, iteratorBlockPrepare);
    }

    const blockInfo = {
      modules: Array.from(blockInfoModules),
      blocks: blockInfoBlocks
    };
    // blockInfoMap 上保存了每一個 module 依賴的同步 modules 及 異步 blocks
    blockInfoMap.set(block, blockInfo);
  }
}
複製代碼
  • this.modules = [NM('./src/a.js'), NM('./b.js'), NM('./c.js'), NM('./d.js')]

  • 第一次外層循環:blockQueue = [NM('./src/a.js')], currentModule = NM('./src/a.js')

    • 第一次內層循環 - blockQueue pop NM('./src/a.js'),開始處理 NM('./src/a.js')。先忽略 if (block.variables),進入 if (block.dependencies),遍歷 NM('./src/a.js').dependencies 執行 iteratorDependency,在 blockInfoModules 中添加 NM('./src/a.js') 的同步依賴;而後進入 if (block.blocks),遍歷 NM('./src/a.js').blocks 執行 iteratorBlockPrepare,在 blockInfoBlocks 中添加 NM('./src/a.js') 的異步依賴,而且 blockQueue push 該異步依賴。此時 blockQueue & blockInfoMap 以下:

      blockQueue = [Block('./c.js')];
      blockInfoMap = Map({
        key: NM('./src/a.js'),
        value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
      })
      複製代碼
    • 第二次內層循環 - blockQueue pop Block('./c.js'),開始處理 Block('./c.js'),步驟同上,此時 blockQueue & blockInfoMap 以下,內層循環結束。

      blockQueue = [];
      blockInfoMap = Map({
        key: NM('./src/a.js'),
        value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
      }, {
        key: Block('./c.js'),
        value: { modules: [NM('./c.js')], blocks: [] }
      })
      複製代碼
  • 第二次外層循環:blockQueue = [NM('./b.js')], currentModule = NM('./b.js')

    • 第一次內層循環 - blockQueue pop NM('./b.js'),開始處理 NM('./b.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:

      blockQueue = [];
      blockInfoMap = Map({
        key: NM('./src/a.js'),
        value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
      }, {
        key: Block('./c.js'),
        value: { modules: [NM('./c.js')], blocks: [] }
      }, {
        key: NM('./b.js'),
        value: { modules: [NM('./d.js')], blocks: [] }
      })
      複製代碼
  • 第三次外層循環:blockQueue = [NM('./c.js')], currentModule = NM('./c.js')

    • 第一次內層循環 - blockQueue pop NM('./c.js'),開始處理 NM('./c.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:

      blockQueue = [Block('./b.js')];
      blockInfoMap = Map({
        key: NM('./src/a.js'),
        value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
      }, {
        key: Block('./c.js'),
        value: { modules: [NM('./c.js')], blocks: [] }
      }, {
        key: NM('./b.js'),
        value: { modules: [NM('./d.js')], blocks: [] }
      }, {
        key: NM('./c.js'),
        value: { modules: [NM('./d.js')], blocks: [Block('./b.js')] }
      })
      複製代碼
    • 第二次內層循環 - blockQueue pop Block('./b.js'),開始處理 Block('./b.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:

      blockQueue = [];
      blockInfoMap = Map({
        key: NM('./src/a.js'),
        value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
      }, {
        key: Block('./c.js'),
        value: { modules: [NM('./c.js')], blocks: [] }
      }, {
        key: NM('./b.js'),
        value: { modules: [NM('./d.js')], blocks: [] }
      }, {
        key: NM('./c.js'),
        value: { modules: [NM('./d.js')], blocks: [Block('./b.js')] }
      }, {
        key: Block('./b.js'),
        value: { modules: [NM('./b.js')], blocks: [] }
      })
      複製代碼
  • 第四次外層循環:blockQueue = [NM('./d.js')], currentModule = NM('./d.js')

    • 第一次內層循環 - blockQueue pop NM('./d.js'),開始處理 NM('./d.js'),步驟同上,此時 blockQueue & blockInfoMap 以下:

      blockQueue = [];
      blockInfoMap = Map({
        key: NM('./src/a.js'),
        value: { modules: [NM('./b.js')], blocks: [Block('./c.js')] }
      }, {
        key: Block('./c.js'),
        value: { modules: [NM('./c.js')], blocks: [] }
      }, {
        key: NM('./b.js'),
        value: { modules: [NM('./d.js')], blocks: [] }
      }, {
        key: NM('./c.js'),
        value: { modules: [NM('./d.js')], blocks: [Block('./b.js')] }
      }, {
        key: Block('./b.js'),
        value: { modules: [NM('./b.js')], blocks: [] }
      }, {
        key: NM('./d.js'),
        value: { modules: [NM('./d.js'), blocks: [] }
      })
      複製代碼

至此,咱們已經把本次 compilation 中全部模塊的同步 & 異步依賴信息保存在 blockInfoMap 中。

建立 chunk graph

// For each async Block in graph
/** * @param {AsyncDependenciesBlock} b iterating over each Async DepBlock * @returns {void} */
const iteratorBlock = b => {
  // 1. We create a chunk for this Block
  // but only once (blockChunkGroups map)
  let c = blockChunkGroups.get(b);
  if (c === undefined) {
    c = this.namedChunkGroups.get(b.chunkName);
    if (c && c.isInitial()) {
      this.errors.push(
        new AsyncDependencyToInitialChunkError(b.chunkName, module, b.loc)
      );
      c = chunkGroup;
    } else {
      // 經過 addChunkInGroup 建立新的 chunkGroup 及 chunk,並返回 chunkGroup
      c = this.addChunkInGroup(
        b.groupOptions || b.chunkName,
        module, // 這個 block 所屬的 module
        b.loc,
        b.request
      );
      chunkGroupCounters.set(c, { index: 0, index2: 0 });
      blockChunkGroups.set(b, c);
      allCreatedChunkGroups.add(c);
    }
  } else {
    // TODO webpack 5 remove addOptions check
    if (c.addOptions) c.addOptions(b.groupOptions);
    c.addOrigin(module, b.loc, b.request);
  }

  // 2. We store the Block+Chunk mapping as dependency for the chunk
  let deps = chunkDependencies.get(chunkGroup);
  if (!deps) chunkDependencies.set(chunkGroup, (deps = []));
  // 當前 chunkGroup 所依賴的 block 及 chunkGroup
  deps.push({
    block: b,
    chunkGroup: c,
    couldBeFiltered: true
  });
  // 異步的 block 使用建立的新的 chunkGroup
  // 3. We enqueue the DependenciesBlock for traversal
  queueDelayed.push({
    action: PROCESS_BLOCK,
    block: b,
    module: module,
    chunk: c.chunks[0], // 獲取新建立的 chunkGroup 中的第一個 chunk,即 block 須要被加入的 chunk
    chunkGroup: c // 異步 block 使用新建立的 chunkGroup
  });
};
...
const ADD_AND_ENTER_MODULE = 0;
const ENTER_MODULE = 1;
const PROCESS_BLOCK = 2;
const LEAVE_MODULE = 3;
...
const chunkGroupToQueueItem = chunkGroup => ({
  action: ENTER_MODULE,
  block: chunkGroup.chunks[0].entryModule,
  module: chunkGroup.chunks[0].entryModule,
  chunk: chunkGroup.chunks[0],
  chunkGroup
});

let queue = inputChunkGroups.map(chunkGroupToQueueItem).reverse()

while (queue.length) {
  while (queue.length) {
    const queueItem = queue.pop();
    module = queueItem.module;
    block = queueItem.block;
    chunk = queueItem.chunk;
    chunkGroup = queueItem.chunkGroup;

    switch (queueItem.action) {
      case ADD_AND_ENTER_MODULE: {
        // We connect Module and Chunk when not already done
        if (chunk.addModule(module)) {
          module.addChunk(chunk);
        } else {
          // already connected, skip it
          break;
        }
      }
      // fallthrough
      case ENTER_MODULE: {
        ...
        queue.push({
          action: LEAVE_MODULE,
          block,
          module,
          chunk,
          chunkGroup
        });
      }
      // fallthrough
      case PROCESS_BLOCK: {
        // get prepared block info
        const blockInfo = blockInfoMap.get(block);
        // Traverse all referenced modules
        for (let i = blockInfo.modules.length - 1; i >= 0; i--) {
          const refModule = blockInfo.modules[i];
          if (chunk.containsModule(refModule)) {
            // skip early if already connected
            continue;
          }
          // enqueue the add and enter to enter in the correct order
          // this is relevant with circular dependencies
          queue.push({
            action: ADD_AND_ENTER_MODULE,
            block: refModule, // 依賴 module
            module: refModule, // 依賴 module
            chunk, // module 所屬的 chunk
            chunkGroup // module 所屬的 chunkGroup
          });
        }

        // Traverse all Blocks
        iterationOfArrayCallback(blockInfo.blocks, iteratorBlock);

        if (blockInfo.blocks.length > 0 && module !== block) {
          blocksWithNestedBlocks.add(block);
        }
        break;
      }
      case LEAVE_MODULE: {
        ...
        break;
      }
    }
  }
  const tempQueue = queue;
  queue = queueDelayed.reverse();
  queueDelayed = tempQueue;
}
複製代碼
  • 初始化 queue = [$1]

    $1: {
      action: ENTER_MODULE(1),
      block: NM('./src/a.js'),
      module: NM('./src/a.js'),
      chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'))},
      chunkGroup: Entrypoint{ chunks: [chunk('app')] }
    }
    複製代碼
  • 第一次外層循環:

    • 第一次內層循環 - queue pop $1,開始處理 chunk('app'),進入到 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:

      $2: {
        action: LEAVE_MODULE(3),
        block: NM('./src/a.js'),
        module: NM('./src/a.js'),
        chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'))},
        chunkGroup: Entrypoint{ chunks: [chunk('app')] }
      }
      複製代碼

      注意 case ENTER_MODULE: 沒有 break,因此會接着進入 case PROCESS_BLOCK:,上面獲得的 blockInfoMap 在這用到了,首先遍歷 NM('./src/a.js') 依賴的 modules - [NM('./b.js')],若是 chunk('app') 還未關聯,就 push queue 以下對象:

      $3: {
        action: ADD_AND_ENTER_MODULE(0),
        block: NM('./b.js'),
        module: NM('./b.js'),
        chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'))},
        chunkGroup: Entrypoint{ chunks: [chunk('app')] }
      }
      複製代碼

      接着遍歷 NM('./src/a.js') 依賴的 blocks - [Block('./c.js')],針對每個 block,建立並關聯 chunkGroup & chunk,而後 queueDelayed.push 如下對象,queueDelayed 用於在第二次外層循環以前從新初始化 queue,後面會用到。至此第一次內層循環結束,此時 queue = [$2, $3], chunk('app').modules = Set(NM('./src/a.js'))

      {
        action: PROCESS_BLOCK(2),
        block: Block('./c.js'),
        module: NM('./src/a.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
        chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')]}
      }
      複製代碼
    • 第二次內層循環 - queue pop $3,進入到 case ADD_AND_ENTER_MODULE:,關聯 chunk('app') & NM('./b.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:

      $4: {
        action: LEAVE_MODULE(3),
        block: NM('./b.js'),
        module: NM('./b.js'),
        chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'), NM('./b.js'))},
        chunkGroup: Entrypoint{ chunks: [chunk('app')] }
      }
      複製代碼

      接着進入 case PROCESS_BLOCK:,遍歷 NM('./b.js') 依賴的 modules - [NM('./d.js')],若是 chunk('app') 還未關聯, 就 push queue 以下對象.

      $5: {
        action: ADD_AND_ENTER_MODULE(0),
        block: NM('./d.js'),
        module: NM('./d.js'),
        chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'), NM('./b.js'))},
        chunkGroup: Entrypoint{ chunks: [chunk('app')] }
      }
      複製代碼

      因爲 NM('./b.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第二次內層循環結束,queue = [$2, $4, $5], chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'))

    • 第三次內層循環 - queue pop $5,進入到 case ADD_AND_ENTER_MODULE:,關聯 chunk('app') & NM('./d.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:

      $6: {
        action: LEAVE_MODULE(3),
        block: NM('./d.js'),
        module: NM('./d.js'),
        chunk: { name: 'app', _groups: Set(Entrypoint), _modules: Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))},
        chunkGroup: Entrypoint{ chunks: [chunk('app')] }
      }
      複製代碼

      遍歷 NM('./d.js') 依賴的 modules & blocks,因爲二者都爲空,因此沒有 queue.push,至此第三次內層循環結束,queue = [$2, $4, $6], chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))

      接下來的三次內層循環都是進入 case LEAVE_MODULE:,先無論裏面的邏輯。至此內層循環結束,此時 queue = [], chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))

  • 使用上次外層循環中賦值的 queueDelayed 從新初始化 queue,queue = [$1],並將 queueDelayed 置空,開始關聯第二個 chunk & modules。

    $1: {
      action: PROCESS_BLOCK(2),
      block: Block('./c.js'),
      module: NM('./src/a.js'),
      chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
      chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')]}
    }
    複製代碼
  • 第二次外層循環:

    • 第一次內層循環 - queue pop $1,進入 case PROCESS_BLOCK: ,遍歷 Block('./c.js') 依賴的 modules - [NM('./d.js')],若是 chunk('./c.js') 還未關聯,就 push queue 以下對象:

      $2: {
        action: ADD_AND_ENTER_MODULE(0),
        block: NM('./c.js'),
        module: NM('./c.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
        chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')]}
      }
      複製代碼

      因爲 Block('./c.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第一次內層循環結束,queue: [$2], chunk('./c.js').modules = Set(0)

    • 第二次內層循環 - queue pop $2,進入 case ADD_AND_ENTER_MODULE:,關聯 chunk('./c.js') & NM('./c.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:

      $3: {
        action: LEAVE_MODULE(3),
        block: NM('./c.js'),
        module: NM('./c.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./c.js'))},
        chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')] }
      }
      複製代碼

      接着進入 case PROCESS_BLOCK:,遍歷 NM('./c.js') 依賴的 modules - [NM('./d.js')],若是 chunk('./c.js') 還未關聯,就 push queue 以下對象:

      $4: {
        action: ADD_AND_ENTER_MODULE(0),
        block: NM('./d.js'),
        module: NM('./d.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./c.js'))},
        chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')] }
      }
      複製代碼

      接着遍歷 NM('./c.js') 依賴的 blocks - [Block('./b.js')],針對每個 block,建立並關聯 chunkGroup & chunk; 而後 queueDelayed.push 如下對象,至此第二次內層循環結束,此時 queue = [$3, $4], chunk('./c.js').modules = Set(NM('./c.js'))

      {
        action: PROCESS_BLOCK(2),
        block: Block('./b.js'),
        module: NM('./c.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
        chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')]}
      }
      複製代碼
    • 第三次內層循環 - queue pop $4,進入 case ADD_AND_ENTER_MODULE:,關聯 chunk('./c.js') & NM('./d.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:

      $6: {
        action: LEAVE_MODULE(3),
        block: NM('./d.js'),
        module: NM('./d.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./c.js'), NM('./d.js'))},
        chunkGroup: ChunkGroup{ chunks: [chunk('./c.js')] }
      }
      複製代碼

      進入 case PROCESS_BLOCK:,NM('./d.js') 沒有依賴的 modules & blocks,至此第三次內層循環結束,此時 queue = [$3, $6], chunk('./c.js').modules = Set(NM('./c.js'), NM('./d.js'))。 接下來的兩次內層循環都是進入 case LEAVE_MODULE:,先無論裏面的邏輯。至此內層循環結束,此時 queue = [], chunk('./c.js').modules = Set(NM('./c.js'), NM('./d.js'))

  • 使用上次外層循環中賦值的 queueDelayed 從新初始化 queue,queue = [$1],並將 queueDelayed 置空,開始關聯第三個 chunk & modules。

    $1: {
      action: PROCESS_BLOCK(2),
      block: Block('./b.js'),
      module: NM('./c.js'),
      chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
      chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')]}
    }
    複製代碼
  • 第三次外層循環:

    • 第一次內層循環 - queue pop $1,進入 case PROCESS_BLOCK: ,遍歷 Block('./b.js') 依賴的 modules - [NM('./b.js')],若是 chunk('./b.js') 還未關聯,就 push queue 以下對象:

      $2: {
        action: ADD_AND_ENTER_MODULE(0),
        block: NM('./b.js'),
        module: NM('./b.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(0)},
        chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')]}
      }
      複製代碼

      因爲 Block('./b.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第一次內層循環結束,queue: [$2], chunk('./b.js').modules = Set(0)

    • 第二次內層循環 - queue pop $2,進入 case ADD_AND_ENTER_MODULE:,關聯 chunk('./b.js') & NM('./b.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:

      $3: {
        action: LEAVE_MODULE(3),
        block: NM('./b.js'),
        module: NM('./b.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./b.js'))},
        chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')] }
      }
      複製代碼

      接着進入 case PROCESS_BLOCK:,遍歷 NM('./b.js') 依賴的 modules - [NM('./d.js')],若是 chunk('./b.js') 還未關聯,就 push queue 以下對象:

      $4: {
        action: ADD_AND_ENTER_MODULE(0),
        block: NM('./d.js'),
        module: NM('./d.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./b.js'))},
        chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')] }
      }
      複製代碼

      因爲 Block('./b.js') 沒有依賴的 blocks,因此不用 push queueDelayed,至此第二次內層循環結束,queue: [$3, $4], chunk('./b.js').modules = Set(NM('./b.js'))。

    • 第三次內層循環 - queue pop $4,進入 case ADD_AND_ENTER_MODULE:,關聯 chunk('./b.js') & NM('./d.js'),接着進入 case ENTER_MODULE:,忽略其餘代碼,直接看 queue.push({}),push 以下對象:

      $5: {
        action: LEAVE_MODULE(3),
        block: NM('./d.js'),
        module: NM('./d.js'),
        chunk: { _groups: Set(ChunkGroup), _modules: Set(NM('./b.js'), NM('./d.js'))},
        chunkGroup: ChunkGroup{ chunks: [chunk('./b.js')] }
      }
      複製代碼

      接着進入 case PROCESS_BLOCK:,NM('./d.js') 沒有依賴的 modules & blocks,至此第三次內層循環結束,此時 queue = [$3, $5], chunk('./b.js').modules = Set(NM('./b.js'), NM('./d.js'))。 接下來的兩次內層循環都是進入 case LEAVE_MODULE:,先無論裏面的邏輯。至此內層循環結束,此時 queue = [], chunk('./b.js').modules = Set(NM('./b.js'), NM('./d.js'))

  • 至此循環結束,咱們獲得了三個 chunkGroup,每一個 chunkGroup 關聯的 chunk 分別是 chunk('app') / chunk('./c.js') / chunk('./b.js'),它們關聯的 modules 以下:

    chunk('app').modules = Set(NM('./src/a.js'), NM('./b.js'), NM('./d.js'))
    chunk('./c.js').modules = Set(NM('./c.js'), NM('./d.js'))
    chunk('./b.js').modules = Set(NM('./b.js'), NM('./d.js'))
    複製代碼

能夠看到 NM('./d.js') 在三個 chunk 中都存在,難道最終生成的每一個 bundle 中都會打包 NM('./d.js') 麼?固然不會,後續 webppack 還會優化 basic chunk graph,生成 chunk graph.

如何 debug Webpack 源碼

  • 在 package.json 中添加一條 script

    "debug": "node --inspect-brk ./node_modules/webpack/bin/webpack.js"

  • 終端運行 npm run debug,在瀏覽器地址欄輸入 chrome://inspect/#devices,界面以下:

  • 點擊 Open dedicated DevTools for Node,會打開 chrome 調試工具,開始調試 Webpack

參考

webpack系列之六chunk圖生成

相關文章
相關標籤/搜索