Open ixlei opened 6 years ago
上一篇文章webpack详解中介绍了webpack基于事件流编程,是个高度的插件集合,整体介绍了webpack 的编译流程。本文将单独聊一聊最核心的部分,编译&构建。
webpack的构建中总会经历如下几个事件节点。
其中make是整个构建中最核心的部分编译,通过模块工厂函数创建模块,然后对模块进行编译。
make
上图中提到的*ModuleFactory是指模块工厂函数,之所以会有模块工厂这样的函数,还要从webpack中entry的配置说起,在webpack的配置项中entry支持如下类型:
*ModuleFactory
entry
string
[string]
object { <key>: string | [string] }
(function: () => string | [string] | object { <key>: string | [string] })
为了处理以后不同类型的入口模块,所以就需要个模块工厂来处理不同的入口模块类型。
string|object { <key>: string }
[string]|object { <key>: [string] }
上图中为了简单说明构建的流程,就以最直接的singleEntry类型说起,对于此类入口模块,webpack均使用NormalModuleFactory来创建模块,这个创建的模块的类型叫NormalModule,在NormalModule中实现了模块的构建方法build,使用runLoaders对模块进行加载,然后利用进行解析,分析模块依赖,递归构建。
singleEntry
NormalModuleFactory
NormalModule
build
runLoaders
到构建封装阶段时候,代码构建已经完毕,但是如何将这些代码按照依赖引用逻辑组织起来,当浏览器将你构建出来的代码加载到浏览器的时候,仍然能够正确执行。在webpack中通过Manifest记录各个模块的详细要点,通过Runtime来引导,加载执行模块代码,特别是异步加载。
Manifest
Runtime
Runtime 如上所述,我们这里只简略地介绍一下。runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。 Manifest 那么,一旦你的应用程序中,形如 index.html 文件、一些 bundle 和各种资源加载到浏览器中,会发生什么?你精心安排的 /src 目录的文件结构现在已经不存在,所以 webpack 如何管理所有模块之间的交互呢?这就是 manifest 数据用途的由来…… 当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。无论你选择哪种模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。
如上所述,我们这里只简略地介绍一下。runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。
那么,一旦你的应用程序中,形如 index.html 文件、一些 bundle 和各种资源加载到浏览器中,会发生什么?你精心安排的 /src 目录的文件结构现在已经不存在,所以 webpack 如何管理所有模块之间的交互呢?这就是 manifest 数据用途的由来…… 当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "Manifest",当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。无论你选择哪种模块语法,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。
定义了一个立即执行函数,声明了__webpack_require__,对各种模块进行加载。
__webpack_require__
(function(modules) { // webpackBootstrap var installedModules = {}; // cache module function __webpack_require__(moduleId) { // 模块加载 // Check if module is in cache if (installedModules[moduleId]) { return installedModules[moduleId].exports; } // Create a new module (and put it into the cache) var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute the module function modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag the module as loaded module.l = true; // Return the exports of the module return module.exports; } // expose the modules object (__webpack_modules__) __webpack_require__.m = modules; // expose the module cache __webpack_require__.c = installedModules; // define getter function for harmony exports __webpack_require__.d = function(exports, name, getter) { if (!__webpack_require__.o(exports, name)) { Object.defineProperty(exports, name, { configurable: false, enumerable: true, get: getter }); } }; // getDefaultExport function for compatibility with non-harmony modules __webpack_require__.n = function(module) { var getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; __webpack_require__.d(getter, 'a', getter); return getter; }; // Object.prototype.hasOwnProperty.call __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; // __webpack_public_path__ __webpack_require__.p = ""; // Load entry module and return exports return __webpack_require__(__webpack_require__.s = 0); })([/**modules*/])
上面提到的代码片段便是webpack构建后在浏览器中执行的引导代码。也就是上面提到的runtime。它是个立即执行函数,那么入参modules便是上面的Manifest,组织各个模块的依赖逻辑。
runtime
modules
(function(modules){ // ... // runtime function __webpack_require__(moduleId) { // 加载逻辑 } // ... })([function (module, exports, __webpack_require__) { var chunk1 = __webpack_require__(1); var chunk2 = __webpack_require__(2); }, function (module, exports, __webpack_require__) { __webpack_require__(2); var chunk1 = 1; exports.chunk1 = chunk1; }, function (module, exports) { var chunk2 = 1; exports.chunk2 = chunk2; }])
上面说到了runtime和manifest就是在seal阶段注入的
manifest
seal
class Compilation extends Tapable { seal(callback) { this.hooks.seal.call(); // ... if (this.hooks.shouldGenerateChunkAssets.call() !== false) { this.hooks.beforeChunkAssets.call(); this.createChunkAssets(); } // ... } createChunkAssets() { // ... for (let i = 0; i < this.chunks.length; i++) { const chunk = this.chunks[i]; // ... const template = chunk.hasRuntime() ? this.mainTemplate : this.chunkTemplate; // 根据是否有runTime选择模块,入口文件是true, 需要异步加载的文件则没有 const manifest = template.getRenderManifest({ // 生成manifest chunk, hash: this.hash, fullHash: this.fullHash, outputOptions, moduleTemplates: this.moduleTemplates, dependencyTemplates: this.dependencyTemplates }); // [{ render(), filenameTemplate, pathOptions, identifier, hash }] // ... } }
通过template最后将代码组织起来,上面看到的构建后的代码就是mainTemplate生成的。
template
mainTemplate
通过template生成最后代码,构建已经完成,接下来就是将代码输出到dist 目录。
dist
webpack的编译&构建
上一篇文章webpack详解中介绍了webpack基于事件流编程,是个高度的插件集合,整体介绍了webpack 的编译流程。本文将单独聊一聊最核心的部分,编译&构建。
webpack的编译
重要的构建节点
webpack的构建中总会经历如下几个事件节点。
其中
make
是整个构建中最核心的部分编译,通过模块工厂函数创建模块,然后对模块进行编译。在make钩子的编译
上图中提到的
*ModuleFactory
是指模块工厂函数,之所以会有模块工厂这样的函数,还要从webpack中entry
的配置说起,在webpack的配置项中entry
支持如下类型:string
[string]
object { <key>: string | [string] }
(function: () => string | [string] | object { <key>: string | [string] })
为了处理以后不同类型的入口模块,所以就需要个模块工厂来处理不同的入口模块类型。
string|object { <key>: string }
[string]|object { <key>: [string] }
(function: () => string | [string] | object { <key>: string | [string] })
上图中为了简单说明构建的流程,就以最直接的
singleEntry
类型说起,对于此类入口模块,webpack均使用NormalModuleFactory
来创建模块,这个创建的模块的类型叫NormalModule
,在NormalModule
中实现了模块的构建方法build
,使用runLoaders
对模块进行加载,然后利用进行解析,分析模块依赖,递归构建。构建封装seal
到构建封装阶段时候,代码构建已经完毕,但是如何将这些代码按照依赖引用逻辑组织起来,当浏览器将你构建出来的代码加载到浏览器的时候,仍然能够正确执行。在webpack中通过
Manifest
记录各个模块的详细要点,通过Runtime
来引导,加载执行模块代码,特别是异步加载。定义了一个立即执行函数,声明了
__webpack_require__
,对各种模块进行加载。上面提到的代码片段便是webpack构建后在浏览器中执行的引导代码。也就是上面提到的
runtime
。它是个立即执行函数,那么入参modules
便是上面的Manifest
,组织各个模块的依赖逻辑。上面说到了
runtime
和manifest
就是在seal
阶段注入的通过
template
最后将代码组织起来,上面看到的构建后的代码就是mainTemplate
生成的。写在最后
通过
template
生成最后代码,构建已经完成,接下来就是将代码输出到dist
目录。