webpack 4 源碼主流程分析(十一):文件的生成

原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!node

資源寫入文件

回到 seal。執行:webpack

this.summarizeDependencies();
複製代碼

獲得 this.fileDependencies, this.contextDependencies, this.missingDependencies 後,觸發了一系列處理資源,優化資源的鉤子以後,回到 Compiler.jscompile 裏的 compilation.seal 回調,執行:web

this.hooks.afterCompile.callAsync(compilation, err => {
  //...
  return callback(null, compilation);
});
複製代碼

該鉤子會觸發插件 CachePlugin 相關的事件,給 compiler 的屬性 _lastCompilationFileDependencies,_lastCompilationContextDependencies 分別賦值 fileDependencies,contextDependencies緩存

建立目標文件夾

而後執行回調即 onCompiled,方法裏執行:async

this.emitAssets(compilation, err => {
  //...
});
複製代碼

進入 this.emitAssetsemitAssets 負責的是構建資源輸出的過程。在方法裏觸發了 Compiler.hooks:emit,在回調裏執行:函數

//...
outputPath = compilation.getPath(this.outputPath); // 獲取資源輸出的路徑
this.outputFileSystem.mkdirp(outputPath, emitFiles); // 遞歸建立輸出目錄並輸出資源
複製代碼

outputPath 爲配置裏的 output.path,而後調用 mkdirp 建立文件夾。性能

建立目標文件並寫入

建立目標文件夾後,執行回調 emitFiles,在回調裏經過 asyncLib.forEachLimit 並行執行對每一個 file 資源文件進行路徑拼接後,將每一個 source 源碼轉換爲 buffer 後(性能提高),寫入真實路徑的 file優化

asyncLib.forEachLimit(
  compilation.getAssets(),
  15,
  ({ name: file, source }, callback) => {
    //...
    const writeOut = err => {
      //...
      const targetPath = this.outputFileSystem.join(outputPath, targetFile); // 路徑拼接,獲得真實路徑
      if (this.options.output.futureEmitAssets) {
        //...判斷重寫入 及 gc釋放內存(this.assets相關重寫SizeOnlySource)
      } else {
        //...
        let content = source.source(); //source爲 CachedSource 實例,content爲獲得的資源

        if (!Buffer.isBuffer(content)) {
          content = Buffer.from(content, 'utf8'); //buffer轉換,在node中提高性能
        }
        //...寫入文件
        this.outputFileSystem.writeFile(targetPath, content, err => {
          //...
          this.hooks.assetEmitted.callAsync(file, content, callback);
        });
      }
    };
    // 若目標文件路徑包含/或\,先建立文件夾再寫入
    if (targetFile.match(/\/|\\/)) {
      const dir = path.dirname(targetFile);
      this.outputFileSystem.mkdirp(this.outputFileSystem.join(outputPath, dir), writeOut);
    } else {
      writeOut();
    }
  },
  // 遍歷完成的回調函數
  err => {
    //...回調
  }
);
複製代碼

其中:ui

let content = source.source();
複製代碼

sourceCachedSource 實例,source.source 作了緩存判斷,執行 this._source.sourcethis._sourceConcatSource 實例,該方法會遍歷 children,若是子項不是字符串,則執行其 source 方法。this

對於 ReplaceSource 實例來講,會執行其 _replaceString 方法,該方法裏會循環處理替換在以前 資源的構建 -> 生成 chunk 資源 -> chunkTemplate -> 生成主體 chunk 代碼 -> 生成每一個 module 代碼 push 進去的 replacements,獲得替換後的字符串,合併返回 resultStr

全部文件都建立寫入完成後而後執行回調:

this.hooks.afterEmit.callAsync(compilation, err => {
  if (err) return callback(err);
  return callback();
});
複製代碼

在回調裏觸發 Compiler.afterEmit:hooks,在回調裏執行 callbackthis.emitAssets 的回調,即執行:

//...
this.emitRecords(err => {
  //...
  const stats = new Stats(compilation);
  stats.startTime = startTime;
  stats.endTime = Date.now();
  this.hooks.done.callAsync(stats, err => {
    if (err) return finalCallback(err);
    return finalCallback(null, stats);
  });
});
複製代碼

設置 stats 並打印構建信息

執行 this.emitRecords,而後在其回調裏設置相關 stats,而後在 Compiler.done:hooks 的回調裏執行 finalCallback,即執行文件 webpack-cli/bin/cli.js 裏的 compiler.run 的回調,即 compilerCallback

方法裏清除緩存以後,執行:

const statsString = stats.toString(outputOptions);
const delimiter = outputOptions.buildDelimiter ? `${outputOptions.buildDelimiter}\n` : '';
if (statsString) stdout.write(`${statsString}\n${delimiter}`);
複製代碼

cli 裏打印出構建相關的信息。至此,構建所有結束,下一章分析打包後的文件!

本章小結

  1. 建立目標文件夾及文件並將資源寫入;
  2. 寫入的時候,會循環處理 source 中的 ReplaceSource 實例中的 replacements,將其替換爲真實字符串;
  3. 設置 stats 並打印構建信息。
相關文章
相關標籤/搜索