Webpack 编译流程

619 阅读5分钟

本文以此为例子

entry: ['main.js']

// main.js
import { a } from './a.js'
import('./b.js').then(res => {
    console.log(res, a)
})

// a.js
export const a = 'a'

// b.js
export const b = 'b'

生成 moduleGraph

数据结构

moduleGraph : {
    _dependencyMap: Map<Dependency, ModuleGraphConnection>
    _moduleMap: Map<NormalModule, ModuleGraphModule>
}

Module: {
    dependencies: Dependency[]
    block: block[]
}

Dependency: {
    _parentModule: Module,
}

ModuleGraphModule: {
    incomingConnections: Set<ModuleGraphConnection>
    outgoingConnections:Set<ModuleGraphConnection>
}

ModuleGraphConnection: {
    module: Module,
    originModule: Module,
    dependency: HarmonyImportSideEffectDependency
}

moduleGraph

  1. 通过一个依赖,可以知道这个依赖是谁提供,被谁使用。
  2. 通过一个模块,可以知道它提供的依赖,和使用的依赖

Module

模块,包含文件源代码,dependencies 为其同步依赖,block 为其异步依赖。

Dependency

依赖,是某个模块提供的导出,_parentModule 为使用它的模块

ModuleGraphConnection

  1. module:提供该依赖的模块
  2. originModule:使用该依赖的模块

ModuleGraphModule

  1. 通过 incomingConnections 获取模块提供的内部依赖
  2. 通过 outgoingConnections 获取模块使用的外部依赖

输出

moduleGraph:{
  _dependencyMap:{
      EntryDependency(main.js): ModuleGraphConnection(main.js),
      HarmonyImportSideEffectDependency(a.js): ModuleGraphConnection(a.js),
      HarmonyImportSpecifierDependency(a.js|a): ModuleGraphConnection(a.js|a),
      ImportDependency(b.js): ModuleGraphConnection(b.js)
  },
  _moduleMap: {
      NormalModule(main.js): ModuleGraphModule(main.js),
      NormalModule(a.js): ModuleGraphModule(a.js),
      NormalModule(b.js): ModuleGraphModule(b.js),
      null: ModuleGraphModule(null)
  }
}

生成 chunkGraph

初始化 ChunkGraph

数据结构

ChunkGraph:{
    _chunks:Map<Chunk, ChunkGraphModule>
    _modules: Map<Chunk, ChunkGraphChunk>
}

Chunk: {
    _group: [entrypoint(main)]
}

ChunkGroup: {
    chunks: Chunk[]
}

ChunkGraphModule: {
    entryModules: Map<Module, ChunkGroup>,
    modules: Module[] 
}

ChunkGraphChunk: {
    entryInChunks: Set<Chunk>,
    chunks: Chunk[] 
}

chunkGraph

  1. 存储 chunk 和 module 的关系。
  2. 通过一个 chunk,可以知道该 chunk 包含的模块,是否对应一个或多个入口 module
  3. 通过一个 module,可以知道该 module 被哪些 chunk 使用,是否对应一个或多个入口 chunk

Chunk

chunk 表示一个待生成的文件,通过 _group 字段与 ChunkGroup 关联

ChunkGroup

单个入口或异步模块对应一个 ChunkGroup,其可能产生一个或多个(splitChunk) chunk,存于 chunks 字段。

ChunkGraphModule

  1. modules 是被该 chunk 直接和间接使用的所有模块。
  2. 根据入口生成的 entry chunk 对应一个或多个 entry module,取决于入口配置中的入口对应的文件数量。
  3. 异步模块生成的 chunk 没有 entry module

ChunkGraphChunk

  1. 一个 module 可能被多个 chunk 使用
  2. 一个 entry module 可能被多个 entry chunk 使用,取决于入口配置被多个入口设置为入口文件

输出

ChunkGraph: {
    _chunks: {},
    _modules: {}
}

生成 entryPoint、entry chunk 和 chunkGraphInit

流程

遍历入口配置 entries

  1. 生成 entry chunk 和 entryPoint(即是chunkGroup),将两者建立关联
  2. 通过入口的依赖和moduleGraph,获取 entry module,将 entry chunk 和 entry module 建立关联
  3. 收集 entryPoint 和对应的 entry module,生成 chunkGraphInit

输出

ChunkGraph: {
    _chunks: {
        chunk(main): {
            entryModules: {
                module(main): chunkGroup(main)
            },
            modules: []
        }
    },
    _modules: {
        module(main): {
            entryInChunks: [chunk(main)],
            chunks: []
        }
    }
}

chunkGroup(main): {
    chunks: [chunk(main)]
}

chunk(main): {
    _group: [chunkGroup(main)]
}

chunkGraphInit: {
    chunkGroup(main): [module(main)]
}

生成 blockModulesMap

数据结构:

blockModulesMap: Map<module, Map<module, ModuleGraphConnection[]>>

moduleMap: Map<Dependency, ModuleGraphConnection>

blockModulesMap

储存模块和该模块依赖的模块和其依赖的关系

moduleMap

储存模块和其有效依赖的关系

流程

由 moduleGraph 获取 module(main.js) 的 outgoingConnections,

outgoingConnections(main.js): [ModuleGraphConnection(a.js), ModuleGraphConnection(a.js|a), ModuleGraphConnection(b.js)]

由 ModuleGraphConnection 可获取依赖,ast分析模块代码时,会将依赖打上是否活跃的标记。根据这个标记我们最后输出 moduleMap

moduleMap: {
    dep(a.js|a): ModuleGraphConnection(a.js|a),
    dep(b): ModuleGraphConnection(b.js),
}

根据 module(main) 的 dependencies 字段获取其同步依赖: [dep(a.js), dep(a.js|a)], ,ModuleGraphConnection(a.js|a) 在 moduleMap 存在,获取该依赖的模块为 module(a.js)。

将这些关系填充进 blockModulesMap:

blockModulesMap: {
    module(main.js): {
        module(a.js): [ModuleGraphConnection(a.js|a]
    }
}

获取 module(main.js) 的异步模块 [block(b.js)], block(b.js) 的同步依赖为 [dep(b.js)], dep(b.js) 在 moduleMap 存在,获取该依赖的模块为 module(b.js)。

将这些关系填充进 blockModulesMap

blockModulesMap: {
    module(main.js): {
        module(a): [ModuleGraphConnection(a.js|a)]
    },
    block(b): {
        module(b): [ModuleGraphConnection(b.js)]
    }
}
  1. module(a.js) 和 module(b.js)无外部依赖跳过

输出:

blockModulesMap: {
    module(main.js): {
        module(a.js): [ModuleGraphConnection(a.js|a)]
    },
    block(b.js): {
        module(b.js): [ModuleGraphConnection(b.js)]
    }
}

连接 chunk 和 module

流程

  1. 遍历 chunkGraphInit 生成 jobs
jobs: [{
    action: ADD_AND_ENTER_MODULE,
    block: module(main.js),
    module: module(main.js),
    chunk: chunk(main),
    chunkGroup: chunkGroup(main),
    chunkGroupInfo: chunkGroupInfo(main)
}]
  1. 遍历 jobs,
  • 将 module(main.js) 与 chunk(main.js) 塞入 ChunkGraph
ChunkGraph: {
    _chunks: {
        chunk(main): {
            entryModules: {
                module(main.js): chunkGroup(main)
            },
            modules: [module(main.js)]
        }
    },
    _modules: {
        module(main.js): {
            entryInChunks: [chunk(main)],
            chunks: [chunk(main)]
        }
    }
}
  • 获取 module(main.js) 的同步依赖,根据 blockModulesMap 将 module(a) 加入job
jobs: [{
    action: ADD_AND_ENTER_MODULE,
    block: module(main.js),
    module: module(main.js),
    chunk: chunk(main),
    chunkGroup: chunkGroup(main),
    chunkGroupInfo: chunkGroupInfo(main)
}, {
    action: ADD_AND_ENTER_MODULE,
    block: module(a.js),
    module: module(a.js),
    chunk: chunk(main),
    chunkGroup: chunkGroup(main),
    chunkGroupInfo: chunkGroupInfo(main)  
}]
  • 获取 module(main.js) 的异步依赖,根据 blockModulesMap 生成一个暂存任务数组 queueDelayed。 这一步为 block(b) 生成了一个新的 chunk 和 chunkGroup
queueDelayed: {
    action: PROCESS_BLOCK,
    block: block(b.js),
    module: module(main.js),
    chunk: chunk(b),
    chunkGroup: chunkGroup(b),
    chunkGroupInfo: chunkGroupInfo(b)
}
  • 将 module(a) 和 chunk(main.js) 塞入 ChunkGraph
ChunkGraph: {
    _chunks: {
        chunk(main): {
            entryModules: {
                module(main): chunkGroup(main)
            },
            modules: [module(main.js), module(a.js)]
        }
    },
    _modules: {
        module(main): {
            entryInChunks: [chunk(main)],
            chunks: [chunk(main)]
        }
    }
}
  • 用 queueDelayed 覆盖 jobs,继续遍历 jobs
jobs: [{
    action: PROCESS_BLOCK,
    block: block(b.js),
    module: module(main.js),
    chunk: chunk(b),
    chunkGroup: chunkGroup(b),
    chunkGroupInfo: chunkGroupInfo(b)
}]
  • 获取 block(b.js) 的同步依赖,根据 blockModulesMap,将其加入 jobs
jobs: [{
    action: PROCESS_BLOCK,
    block: block(b.js),
    module: module(main.js),
    chunk: chunk(b),
    chunkGroup: chunkGroup(b),
    chunkGroupInfo: chunkGroupInfo(b)
}, {
    action: ADD_AND_ENTER_MODULE,
    block: module(b.js),
    module: module(b.js),
    chunk: chunk(b),
    chunkGroup: chunkGroup(b),
    chunkGroupInfo: chunkGroupInfo(b)
}]
  • 将 module(b.js) 和 chunk(b) 塞入 ChunkGraph
ChunkGraph: {
    _chunks: {
        chunk(main): {
            entryModules: {
                module(main): chunkGroup(main)
            },
            modules: [module(main.js), module(a.js)]
        },
        chunk(b): {
            entryModules: {},
            modules: [module(b.js)]
        }
    },
    _modules: {
        module(main.js): {
            entryInChunks: [chunk(main)],
            chunks: [chunk(main)]
        },
        module(b.js): {
            entryInChunks: [],
            chunks: [chunk(b)]
        }
    }
}
  • module(b.js) 无外部依赖,至此遍历结束 输出
ChunkGraph: {
    _chunks: {
        chunk(main): {
            entryModules: {
                module(main): chunkGroup(main)
            },
            modules: [module(main.js), module(a.js)]
        },
        chunk(b): {
            entryModules: {},
            modules: [module(b.js)]
        }
    },
    _modules: {
        module(main.js): {
            entryInChunks: [chunk(main)],
            chunks: [chunk(main)]
        },
        module(b.js): {
            entryInChunks: [],
            chunks: [chunk(b)]
        }
    }
}

链接 chunkGroup

我们为异步模块 block(b) 单独生成了一个 chunk 和 chunkGroup。通过 chunkGroup 的 _parents 和 _children 字段为它们建立关系

chunkGroup(main): {
    chunks: [chunk(main)]
    _children: [chunkGroup(b)]
}


chunkGroup(b): {
    chunks: [chunk(b)]
    _parents: [chunkGroup(main)]
}

处理空的 chunkGroup

遍历上一步中新生成的所有chunkGroup,若其 _parents 为空,则将其和其产生的 chunk 从 ChunkGraph 中删除

假如是如下配置

// main.js
import { a } from './a.js'
import { b } from './b.js'
import('./b.js').then(res => {
    console.log(res, a)
})

// a.js
export const a = 'a'

// b.js
export const b = 'b'

会生成如下 ChunkGraph

ChunkGraph: {
    _chunks: {
        chunk(main): {
            entryModules: {
                module(main): chunkGroup(main)
            },
            modules: [module(main.js), module(a.js)]
        },
        chunk(b): {
            entryModules: {},
            modules: []
        }
    },
    _modules: {
        module(main.js): {
            entryInChunks: [chunk(main)],
            chunks: [chunk(main)]
        },
        module(b.js): {
            entryInChunks: [],
            chunks: [chunk(main)]
        }
    }
}

chunks(b) 是空的,代码会被直接打包进 chunk(main),chunkGroup(b) 也不回和 chunkGroup(main) 建立关联,chunks 和 chunkGroup(b) 都将被删除

去重 chunks

针对目前所有的chunks,去掉所使用的依赖全部相同的chunk,针对新生成的除入口 chunk 之外的 chunk

对于如下配置

// one.js
import('./a.js').then(res => {
    console.log(res)
})

// two.js
import('./a.js').then(res => {
    console.log(res, a)
})

// a.js
export const a = 'a'

经过去重,最终会生成 [chunk(one). chunk(two), chunk(a)]

合并 Module

遍历所有 modules

为 module(main) 生成

ConcatenatedModule: {
    _modules: [module(main), module(a)],
    block: [block(b)]
}

目前所有 modules [ConcatenatedModule(main,a), module(b)]

生成 module - code 代码映射

{
    ConcatenatedModule(main,a): codeObject,
    module(b): codeObject
}

生成mainfest

[{
    filename: 'main.js'
    render: () => { ...generate code }
 },
    filename: 'module_id.js'
    render: () => { ...generate code }
}]

注意,
1. 如果是入口 chunk,会在这里的 render 函数中加入运行时代码
2. 根据 ChunkGraph 可以知道该 chunk 直接或间接依赖的所有 module。然后将他们的代码拼接

生成最终代码

{
    main.js: code,
    module_id.js: code
}

这里会使用 terser 进行 tree shake 去除无用代码

输出代码

输出文件