淺析webpack原始碼之Compilation.js粗解(九)

織雪紗奈發表於2019-02-16

我們先看一下 compilation是什麼?
是一個很大的物件
列印key值

[ `_pluginCompat`,
  `hooks`,
  `name`,
  `compiler`,
  `resolverFactory`,
  `inputFileSystem`,
  `requestShortener`,
  `options`,
  `outputOptions`,
  `bail`,
  `profile`,
  `performance`,
  `mainTemplate`,
  `chunkTemplate`,
  `hotUpdateChunkTemplate`,
  `runtimeTemplate`,
  `moduleTemplates`,
  `semaphore`,
  `entries`,
  `_preparedEntrypoints`,
  `entrypoints`,
  `chunks`,
  `chunkGroups`,
  `namedChunkGroups`,
  `namedChunks`,
  `modules`,
  `_modules`,
  `cache`,
  `records`,
  `additionalChunkAssets`,
  `assets`,
  `errors`,
  `warnings`,
  `children`,
  `dependencyFactories`,
  `dependencyTemplates`,
  `childrenCounters`,
  `usedChunkIds`,
  `usedModuleIds`,
  `fileTimestamps`,
  `contextTimestamps`,
  `compilationDependencies`,
  `_buildingModules`,
  `_rebuildingModules`

我們看有熟悉的chunks,entries,options,
這些是Compilation定義的屬性

const EntryModuleNotFoundError = require("./EntryModuleNotFoundError");
const ModuleNotFoundError = require("./ModuleNotFoundError");
const ModuleDependencyWarning = require("./ModuleDependencyWarning");
const ModuleDependencyError = require("./ModuleDependencyError");
const ChunkGroup = require("./ChunkGroup");
const Chunk = require("./Chunk");
const Entrypoint = require("./Entrypoint");
const MainTemplate = require("./MainTemplate");
const ChunkTemplate = require("./ChunkTemplate");
const HotUpdateChunkTemplate = require("./HotUpdateChunkTemplate");
const ModuleTemplate = require("./ModuleTemplate");
const RuntimeTemplate = require("./RuntimeTemplate");
const ChunkRenderError = require("./ChunkRenderError");
const AsyncDependencyToInitialChunkError = require("./AsyncDependencyToInitialChunkError");
const Stats = require("./Stats");
const Semaphore = require("./util/Semaphore");
const createHash = require("./util/createHash");
const Queue = require("./util/Queue");
const SortableSet = require("./util/SortableSet");
const GraphHelpers = require("./GraphHelpers");
const ModuleDependency = require("./dependencies/ModuleDependency");
const compareLocations = require("./compareLocations");

從這一堆的模組引入裡,也可以猜出Compilation主要做了Modules變成Chunks編譯的過程

裡面有一些方法

1.對模組的處理,建立搜尋新增構建/模組依賴處理,新增模組依賴,分類模組等
addModule/getModule/ findModule/buildModule/sortModules
2.對Chunk的處理,建立Chunk
3.雜湊值

Compilation.prototype.applyPlugins

在原型上新增了applyPlugins方法

Compilation.js也是對Tapable 的擴充
主要處理Chunk、Module等

我們學習一下

webpack術語表定義

模組:離散的功能塊,提供比完整程式更小的表面積。編寫良好的模組提供了可靠的抽象和封裝邊界,構成了一致的設計和明確的目的。

塊:此Webpack特定術語在內部用於管理捆綁過程。捆綁包由塊組成,其中有幾種型別(例如入口和子)。通常,塊直接與輸出束相對應,但是,有些配置不會產生一對一的關係。

Bundle:由許多不同的模組生成,bundle包含已經過載入和編譯過程的原始檔的最終版本。

總結
a chunk is a group of modules within the webpack process, a bundle is an emitted chunk or set of chunks.

一個塊是webpack程式中的一組模組,一個bundle是一個發出的塊或一組塊。

{
  entry: {
    foo: ["webpack/hot/only-dev-server.js","./src/foo.js"],
    bar: ["./src/bar.js"]
  },
  output: {
    path: "./dist",
    filename: "[name].js"
  }
}
Modules: "webpack/hot/only-dev-server.js", "./src/foo.js", "./src/bar.js" ( + any other modules that are dependencies of these entry points!)
Chunks: foo, bar
Bundles: foo, bar

通俗的解釋一下,Modules是引入的模組,Chunks就是編譯的模組,Bundles是提交的Chunks ,Chunks和Bundles是1:1的關係,配置map會有例外

總結

Compilation主要做了Modules變成Chunks編譯的過程

chunks

不細看邏輯,好像太虛了,我們看一下this.chunks

它是一個陣列,目前Chunk的長度是1,因為只是引入了一個模組

[ Chunk {
    id: `main`,
    ids: [ `main` ],
    debugId: 1000,
    name: `main`,
    preventIntegration: false,
    entryModule:
     NormalModule {
       dependencies: [],
       blocks: [],
       variables: [],
       type: `javascript/auto`,
       context: `/Users/orion/Desktop/react-beauty-highcharts/src`,
       debugId: 1000,
       hash: `a6388d29fa15bd58c6cffb10246992a5`,
       renderedHash: `a6388d29fa15bd58c6cf`,
       resolveOptions: {},
       factoryMeta: {},
       warnings: [],
       errors: [],
       buildMeta: [Object],
       buildInfo: [Object],
       reasons: [Array],
       _chunks: [SortableSet],
       id: `./src/index.js`,
       index: 0,
       index2: 0,
       depth: 0,
       issuer: null,
       profile: undefined,
       prefetched: false,
       built: true,
       used: null,
       usedExports: null,
       optimizationBailout: [],
       _rewriteChunkInReasons: undefined,
       useSourceMap: true,
       _source: [SourceMapSource],
       request:
        `/Users/orion/Desktop/react-beauty-highcharts/node_modules/babel-loader/lib/index.js!/Users/orion/Desktop/react-beauty-highcharts/src/index.js`,
       userRequest: `/Users/orion/Desktop/react-beauty-highcharts/src/index.js`,
       rawRequest: `./src/index.js`,
       binary: false,
       parser: [Parser],
       generator: JavascriptGenerator {},
       resource: `/Users/orion/Desktop/react-beauty-highcharts/src/index.js`,
       matchResource: undefined,
       loaders: [Array],
       error: null,
       _buildHash: `488efbd43aa05371d3f44d94c89abd57`,
       buildTimestamp: 1547884969828,
       _cachedSources: Map {},
       lineToLine: false,
       _lastSuccessfulBuildMeta: [Object],
       _ast: null },
    _modules:
     SortableSet [Set] {
       [NormalModule],
       _sortFn: [Function: sortByIdentifier],
       _lastActiveSortFn: null,
       _cache: undefined,
       _cacheOrderIndependent: undefined },
    filenameTemplate: undefined,
    _groups:
     SortableSet [Set] {
       [Entrypoint],
       _sortFn: [Function: sortChunkGroupById],
       _lastActiveSortFn: null,
       _cache: undefined,
       _cacheOrderIndependent: undefined },
    files: [],
    rendered: false,
    hash: `0988e8454f1915ec05fee482db8d0a6f`,
    contentHash: { javascript: `4b8695ca3c1d42e76c52` },
    renderedHash: `0988e8454f1915ec05fe`,
    chunkReason: undefined,
    extraAsync: false,
    removedModules: undefined } ]

我們看到在entryModule下,記錄了入口的絕對地址,相對地址,編譯的hash,檔案型別等資訊

//檔案寫入,檔案輸出,檔案快取,裡面具體的template.getRenderManifest,chunk.hasRuntime(),CachedSource具體的邏輯不能夠一一的去研究詳解,但是從名字能知道這個函式是做什麼的
createChunkAssets() {
        const outputOptions = this.outputOptions;
        const cachedSourceMap = new Map();
        /** @type {Map<string, {hash: string, source: Source, chunk: Chunk}>} */
        const alreadyWrittenFiles = new Map();
        for (let i = 0; i < this.chunks.length; i++) {
            const chunk = this.chunks[i];
            chunk.files = [];
            let source;
            let file;
            let filenameTemplate;
            try {
                const template = chunk.hasRuntime()
                    ? this.mainTemplate
                    : this.chunkTemplate;
                const manifest = template.getRenderManifest({
                    chunk,
                    hash: this.hash,
                    fullHash: this.fullHash,
                    outputOptions,
                    moduleTemplates: this.moduleTemplates,
                    dependencyTemplates: this.dependencyTemplates
                }); // [{ render(), filenameTemplate, pathOptions, identifier, hash }]
                for (const fileManifest of manifest) {
                    const cacheName = fileManifest.identifier;
                    const usedHash = fileManifest.hash;
                    filenameTemplate = fileManifest.filenameTemplate;
                    file = this.getPath(filenameTemplate, fileManifest.pathOptions);

                    // check if the same filename was already written by another chunk
                    const alreadyWritten = alreadyWrittenFiles.get(file);
                    if (alreadyWritten !== undefined) {
                        if (alreadyWritten.hash === usedHash) {
                            if (this.cache) {
                                this.cache[cacheName] = {
                                    hash: usedHash,
                                    source: alreadyWritten.source
                                };
                            }
                            chunk.files.push(file);
                            this.hooks.chunkAsset.call(chunk, file);
                            continue;
                        } else {
                            throw new Error(
                                `Conflict: Multiple chunks emit assets to the same filename ${file}` +
                                    ` (chunks ${alreadyWritten.chunk.id} and ${chunk.id})`
                            );
                        }
                    }
                    if (
                        this.cache &&
                        this.cache[cacheName] &&
                        this.cache[cacheName].hash === usedHash
                    ) {
                        source = this.cache[cacheName].source;
                    } else {
                        source = fileManifest.render();
                        // Ensure that source is a cached source to avoid additional cost because of repeated access
                        if (!(source instanceof CachedSource)) {
                            const cacheEntry = cachedSourceMap.get(source);
                            if (cacheEntry) {
                                source = cacheEntry;
                            } else {
                                const cachedSource = new CachedSource(source);
                                cachedSourceMap.set(source, cachedSource);
                                source = cachedSource;
                            }
                        }
                        if (this.cache) {
                            this.cache[cacheName] = {
                                hash: usedHash,
                                source
                            };
                        }
                    }
                    if (this.assets[file] && this.assets[file] !== source) {
                        throw new Error(
                            `Conflict: Multiple assets emit to the same filename ${file}`
                        );
                    }
                    this.assets[file] = source;
                    chunk.files.push(file);
                    this.hooks.chunkAsset.call(chunk, file);
                    alreadyWrittenFiles.set(file, {
                        hash: usedHash,
                        source,
                        chunk
                    });
                }
            } catch (err) {
                this.errors.push(
                    new ChunkRenderError(chunk, file || filenameTemplate, err)
                );
            }
        }
    }
modules
[ {
    dependencies: [],
    blocks: [],
    variables: [],
    type: `javascript/auto`,
    context: `/Users/orion/Desktop/react-beauty-highcharts/src`,
    debugId: 1000,
    hash: `a6388d29fa15bd58c6cffb10246992a5`,
    renderedHash: `a6388d29fa15bd58c6cf`,
    resolveOptions: {},
    factoryMeta: {},
    warnings: [],
    errors: [],
    buildMeta: { providedExports: true },
    buildInfo:
     { cacheable: true,
       fileDependencies: [Set],
       contextDependencies: Set {},
       temporaryProvidedExports: false },
    reasons: [ [ModuleReason] ],
    _chunks:{
       [Chunk],
       _sortFn: [Function: sortById],
       _lastActiveSortFn: null,
       _cache: undefined,
       _cacheOrderIndependent: undefined },
    id: `./src/index.js`,
    index: 0,
    index2: 0,
    depth: 0,
    issuer: null,
    profile: undefined,
    prefetched: false,
    built: true,
    used: null,
    usedExports: null,
    optimizationBailout: [],
    _rewriteChunkInReasons: undefined,
    useSourceMap: true,
    _source:{
       _value:
        `module.exports = {
  doubleLine: function doubleLine(arr) {
    if (arr && !Array.isArray(arr)) {
      console.error(`the first params type must be Array`);
      return;
    }

    return {
      credits: {
        enabled: false // 禁用版權資訊

      },
      chart: {
        width: `400`,
        height: `400`,
        type: `area`,
        backgroundColor: {
          linearGradient: [0, 0, 500, 500],
          stops: [[0, `rgba(14, 8, 55,1)`], [1, `rgba(14, 8, 55,1)`]]
        }
      },
      title: {
        text: ``,
        style: {
          color: `#a6aed2`,
          font: `bold 16px "Trebuchet MS", Verdana, sans-serif`
        }
      },
      xAxis: {
        categories: [`10:00`, `10:30`, `11:00`, `11:30`, `12:00`, `12:30`, `13:00`, `13:30`, `14:00`, `14:30`, `15:00`, `15:30`, `16:00`, `16:30`, `16:30`, `17:00`, `17:30`, `18:00`, `18:30`, `19:00`],
        labels: {
          format: `{value} `,
          style: {
            color: `#746f95`,
            fontSize: `12px`,
            fontFamily: `微軟雅黑`
          }
        },
        maxPadding: 0.05,
        showLastLabel: true,
        // tickmarkPlacement:`on`,
        tickColor: `#746f95`,
        lineWidth: 1,
        lineColor: `#746f95`,
        tickLength: 5,
        minRange: 5,
        tickInterval: 1 // 座標軸刻度間隔為一星期

      },
      yAxis: {
        gridLineWidth: `0px`,
        startOnTick: true,
        endOnTick: false,
        maxPadding: 0.35,
        title: {
          text: null
        },
        labels: {
          // format: `{value} m`,
          style: {
            color: `#746f95`,
            fontSize: `12px`,
            fontFamily: `微軟雅黑`
          }
        },
        lineWidth: 1,
        lineColor: `#746f95`
      },
      legend: {
        itemStyle: {
          font: `9pt Trebuchet MS, Verdana, sans-serif`,
          color: `#a6aed2`
        },
        itemHoverStyle: {
          color: `#fff`
        }
      },
      tooltip: {
        pointFormat: `{series.name}:  <b>{point.y:,.0f}</b>人`
      },
      plotOptions: {
        area: {
          // pointStart: 1920,
          marker: {
            enabled: false,
            symbol: `circle`,
            radius: 2,
            states: {
              hover: {
                enabled: true
              }
            }
          }
        }
      },
      series: arr && arr[0] === `pink` ? [{
        // data: [  110, 235, 369, 640,
        //        1005, 1436, 2063, 3057, 4618, 6444, 9822, 15468, 20434, 24126,
        //        27387, 29459, 31056, 31982, 32040, 31233, 29224, 27342, 26662,
        //        26956, 27912, 28999, 28965, 27826, 25579, 25722, 24826, 24605,
        //        24304, 23464, 23708, 24099, 24357, 24237, 24401, 24344, 23586,
        //        22380, 21004, 17287, 14747, 13076, 12555, 12144, 11009, 10950,
        //        10871, 10824, 10577, 10527, 10475, 10421, 10358, 10295, 10104],
        lineColor: `#e88eb3`,
        color: {
          linearGradient: {
            x1: 0,
            x2: 0,
            y1: 0,
            y2: 1
          },
          stops: [[0, `rgba(231,142,179,0.8)`], [1, `rgba(135,56,89,0.5)`]]
        },
        fillOpacity: 0.5,
        name: `進`,
        marker: {
          enabled: false
        } // threshold: null // 是否顯示負數

      }, {
        // data: ,
        lineColor: `#b946ff`,
        color: {
          linearGradient: {
            x1: 0,
            x2: 0,
            y1: 0,
            y2: 1
          },
          stops: [[0, `rgba(152,60,210,0.8)`], [1, `rgba(65,25,90,0.35)`]]
        },
        fillOpacity: 0.5,
        name: `出`,
        marker: {
          enabled: false
        },
        threshold: null
      }] : [{
        // data: [  110, 235, 369, 640,
        //        1005, 1436, 2063, 3057, 4618, 6444, 9822, 15468, 20434, 24126,
        //        27387, 29459, 31056, 31982, 32040, 31233, 29224, 27342, 26662,
        //        26956, 27912, 28999, 28965, 27826, 25579, 25722, 24826, 24605,
        //        24304, 23464, 23708, 24099, 24357, 24237, 24401, 24344, 23586,
        //        22380, 21004, 17287, 14747, 13076, 12555, 12144, 11009, 10950,
        //        10871, 10824, 10577, 10527, 10475, 10421, 10358, 10295, 10104],
        lineColor: `#b946ff`,
        color: {
          linearGradient: {
            x1: 0,
            x2: 0,
            y1: 0,
            y2: 1
          },
          stops: [[0, `rgba(152,60,210,0.8)`], [1, `rgba(65,25,90,0.35)`]]
        },
        fillOpacity: 0.5,
        name: `進`,
        marker: {
          enabled: false
        } // threshold: null // 是否顯示負數

      }, {
        // data: ,
        lineColor: `#68d5ee`,
        color: {
          linearGradient: {
            x1: 0,
            x2: 0,
            y1: 0,
            y2: 1
          },
          stops: [[0, `rgba(104,213,238,0.8)`], [1, `rgba(7,44,96,0.5)`]]
        },
        fillOpacity: 0.5,
        name: `出`,
        marker: {
          enabled: false
        },
        threshold: null
      }]
    };
  },
  columns: function columns() {
    return {
      credits: {
        enabled: false
      },
      chart: {
        type: `column`,
        backgroundColor: {
          linearGradient: [0, 0, 500, 500],
          stops: [[0, `rgb(14, 8, 55)`], [1, `rgb(14, 8, 55)`]]
        }
      },
      title: {
        text: `月平均降雨量`,
        style: {
          color: `#a6aed2`,
          font: `bold 16px "Trebuchet MS", Verdana, sans-serif`
        }
      },
      xAxis: {
        gridLineWidth: `0px`,
        startOnTick: true,
        endOnTick: false,
        maxPadding: 0.35,
        categories: [`一月`, `二月`, `三月`, `四月`, `五月`, `六月`, `七月`, `八月`, `九月`, `十月`, `十一月`, `十二月`],
        labels: {
          format: `{value} `,
          style: {
            color: `#746f95`,
            fontSize: `12px`,
            fontFamily: `微軟雅黑`
          }
        },
        crosshair: true,
        lineWidth: 1,
        lineColor: `#746f95`,
        tickLength: 5,
        tickColor: `#746f95`
      },
      yAxis: {
        gridLineWidth: `0px`,
        startOnTick: true,
        endOnTick: false,
        maxPadding: 0.35,
        min: 0,
        labels: {
          format: `{value} `,
          style: {
            color: `#746f95`,
            fontSize: `12px`,
            fontFamily: `微軟雅黑`
          }
        },
        title: {
          // 平均時長 (min)
          text: ``,
          style: {
            color: `#746f95`,
            fontSize: `12px`,
            fontFamily: `微軟雅黑`
          }
        },
        lineWidth: 1,
        lineColor: `#746f95`
      },
      tooltip: {
        // head + 每個 point + footer 拼接成完整的 table
        headerFormat: `<span style="font-size:10px">{point.key}</span><table>`,
        pointFormat: `<tr><td style="color:{series.color};padding:0">{series.name}: </td>` + `<td style="padding:0"><b>{point.y} </b></td></tr>`,
        footerFormat: `</table>`,
        shared: true,
        useHTML: true
      },
      plotOptions: {
        column: {
          borderWidth: 0
        }
      },
      legend: {
        itemStyle: {
          font: `9pt Trebuchet MS, Verdana, sans-serif`,
          color: `#a6aed2`
        },
        itemHoverStyle: {
          color: `#fff`
        }
      },
      series: [{
        color: `#3453d4`,
        name: `>120s`,
        data: [1, 2, 3, 4, 5, 6, 7]
      }, {
        color: `#ff2674`,
        name: `60~120s`,
        data: [3, 4, 3, 1, 3, 2, 2]
      }, {
        color: `#66c3e3`,
        // color:`#66c3e3`,
        name: `<60s`,
        data: [7, 4, 3, 4, 2, 6, 8]
      }]
    };
  }
};`,
       _name:
        `/Users/orion/Desktop/react-beauty-highcharts/node_modules/babel-loader/lib/index.js!/Users/orion/Desktop/react-beauty-highcharts/src/index.js`,
       _sourceMap: [Object],
       _originalSource: undefined,
       _innerSourceMap: undefined },
    request:
     `/Users/orion/Desktop/react-beauty-highcharts/node_modules/babel-loader/lib/index.js!/Users/orion/Desktop/react-beauty-highcharts/src/index.js`,
    userRequest: `/Users/orion/Desktop/react-beauty-highcharts/src/index.js`,
    rawRequest: `./src/index.js`,
    binary: false,
    parser:{
       _pluginCompat: [SyncBailHook],
       hooks: [Object],
       options: {},
       sourceType: `auto`,
       scope: undefined,
       state: undefined,
       comments: undefined },
    generator: JavascriptGenerator {},
    resource: `/Users/orion/Desktop/react-beauty-highcharts/src/index.js`,
    matchResource: undefined,
    loaders: [ [Object] ],
    error: null,
    _buildHash: `488efbd43aa05371d3f44d94c89abd57`,
    buildTimestamp: 1547885885262,
    _cachedSources: Map {},
    lineToLine: false,
    _lastSuccessfulBuildMeta: { providedExports: true },
    _ast: null } ]

我們看到module的長度也是一個,一樣有型別,路徑,hash
其中
_source的_value下的module.exports,已經是略微壓縮版的了,裡面還有n

裡面值得分析的還有很多,關於怎麼壓縮,壓縮演算法是怎麼處理的,二次讀原始碼再詳解

週六快樂,剛剛抓娃娃機一個都沒抓上,哈哈哈哈,但是我抓的激動開心啊?

相關文章