webpack源码

打包后源码分析

path: path.resolve(‘dist’) 为输出目录的路径

对输出文件进行分析

1.模块定义

./src/index.js”:   //路径模块id 视为键值对
(function (module, exports) {
console.log(‘index.js内容’)
module.exports = ‘入口文件导出内容’ //模块定义
})
//值是一个函数的调用
//传递模块的参数
  • 打包后的文件就是一个函数自调用,当前函数调用时传入一个对象
  • 这个对象我们为了方便将之称为是模块定义,它就是一个键值对
  • 这个键名就是当前被加载模块的文件名与某个目录的拼接()
  • 这个键值就是一个函数,和 node.js 里的模块加载有一些类似,会将被加载模块中的内容包裹于一个函数中
  • 这个函数在将来某个时间点上会被调用,同时会接收到一定的参数,利用这些参数就可以实现模块的加载操作
  • 针对于上述的代码就相当于是将 {}(模块定义) 传递给了 modules
2.installedModules  缓存  存放将来被加载的模块对象
3.  function __webpack_require__(moduleId)    核心作用是返回模块的导出
		return了 module.exports
		
		1.先判断installedModules缓存里面有没有

4.__webpack_require__.t 里面调用了  3的方法
	return __webpack_require__(__webpack_require__.s = "./src/index.js")  进入方法的调用
	
5.__webpack_require__.s  缓存路径的   这个语句不影响

6.初始化环境
	完成最简单的打包操作
	生成输出文件
	匿名函数自动调用   实参
		路径  模块的id   路径+文件名
		值为函数 被加载模块的内容  外部包裹函数  require函数里面调用
		调用就可以拿到模块的加载内容

单文件打包后源码调试

添加属性和方法
连续赋值 从右往左看
modules[moduleId] 为函数
调用函数

modules[moduleId].call(module.exports, module, module.exports, webpack_require);
  • module.l= true 表示加载过了
  • 得到自调用函数 生成模块定义
  • 模块定义传给modules
  • 调用require方法 传入模块id
  • 调用模块方法 导出module.exports

功能函数说明

引入其他模块分析执行流程
在主js文件里 会有导入附属js 的语句

let name = webpack_require(/*! ./login.js */./src/login.js”)
	模块定义里有多个键值对了
	
1.installedModules  缓存  存放将来被加载的模块对象
2. __webpack_require__(moduleId)    核心作用是返回模块的导出
3.__webpack_require__.m = modules    将模块定义保存一份,通过 m 属性挂载到自定义的方法身上
4. __webpack_require__.c = installedModules    保存缓存
5.__webpack_require__.o   判断被传入的对象 obj 身上是否具有指定的属性 **** ,如果有则返回 true 
6. __webpack_require__.d   通过 o 方法判断是否没有 name 属性 并且添加一个name属性 和访问器
7.__webpack_require__.r
	1.判断是否是esmodules 
		是的话添加一个 值为 module 的Symbol
	2.添加一个 __esModule 属性
8.__webpack_require__.t     未调用
	调用后会拿到模块中的内容value
	对于 value 来说我们可能会直接返回,也可能会处理之后再返回
9.__webpack_require__.n  分会module的default属性	未调用

10.__webpack_require__.p   配置里面的public属性

commonJS模块打包

主js采用 require函数

只是对__webpack_require__  进行了替换  就是上面学习的例子
js里面
	esModules模块
		调用了__webpack_require__.r 方法  判断是否esmodules  并且添加 Module的的Symbol
										添加 __esModule 属性 value 为true 
		 调用了__webpack_require__.d   处理age 属性 作为d方法里面name值 添加age属性和访问器

esModule 打包

主js 采用 import from
调用了__webpack_require__.r 方法 判断是否esmodules 并且添加 Module的的Symbol
添加 _esModule 属性 value 为true
webpack自定义方法 替换

var login_js__WEBPACK_IMPORTED_MODULE_0 = webpack_require(/*! ./login.js */./src/login.js”);
module.exports = 'zce'
	使用 __webpack_require__.n  方法添加default并赋予值   default 和别的值 区别时 多调一次 __webpack_require__.n 方法
		__webpack_require__.d  添加属性值 
			__webpack_require__.o 判断是否esmodule

两种规范都可以使用 作打包处理 CJS处理的结果更少 但还是根据情况使用

功能函数手写

(function (modules) {
  // 01 定义对象用于将来缓存被加载过的模块
  let installedModules = {}

  // 02 定义一个 __webpack_require__ 方法来替换 import require 加载操作
  function __webpack_require__(moduleId) {
    // 2-1 判断当前缓存中是否存在要被加载的模块内容,如果存在则直接返回
    if (installedModules[moduleId]) {
      return installedModules[moduleId].exports
    }

    // 2-2 如果当前缓存中不存在则需要我们自己定义{} 执行被导入的模块内容加载
    let module = installedModules[moduleId] = {
      i: moduleId,
      l: false,
      exports: {}
    }

    // 2-3 调用当前 moduleId 对应的函数,然后完成内容的加载
    modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)

    // 2-4 当上述的方法调用完成之后,我们就可以修改 l 的值用于表示当前模块内容已经加载完成了
    module.l = true

    // 2-5 加载工作完成之后,要将拿回来的内容返回至调用的位置 
    return module.exports
  }

  // 03 定义 m 属性用于保存 modules 
  __webpack_require__.m = modules

  // 04 定义 c 属性用于保存 cache 
  __webpack_require__.c = installedModules

  // 05 定义 o 方法用于判断对象的身上是否存在指定的属性
  __webpack_require__.o = function (object, property) {
    return Object.prototype.hasOwnProperty(object, property)
  }

  // 06 定义 d 方法用于在对象的身上添加指定的属性,同时给该属性提供一个 getter 
  __webpack_require__.d = function (exports, name, getter) {
    if (!__webpack_require__.o(exports, name)) {
      Object.defineProperty(exports, name, { enumerable: true, get: getter })
    }
  }

  // 07 定义 r 方法用于标识当前模块是 es6 类型
  __webpack_require__.r = function (exports) {
    if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
      Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" })
    }
    Object.defineProperty(exports, '__esModule', { value: true })
  }

  // 08 定义 n 方法,用于设置具体的 getter 
  __webpack_require__.n = function (module) {
    let getter = module && module.__esModule ?
      function getDefault() { return module['default'] } :
      function getModuleExports() { return module }

    __webpack_require__.d(getter, 'a', getter)

    return getter
  }

  // 09 定义 P 属性,用于保存资源访问路径
  __webpack_require__.p = ""

  // 10 调用 __webpack_require__ 方法执行模块导入与加载操作
  return __webpack_require__(__webpack_require__.s = './src/index.js')

})
  ({
    "./src/index.js":
      (function (module, __webpack_exports__, __webpack_require__) {

        "use strict";
        __webpack_require__.r(__webpack_exports__);
        var _login_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./login.js */ "./src/login.js");
        console.log('index.js 执行了')
        console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__["default"], '<------')
        console.log(_login_js__WEBPACK_IMPORTED_MODULE_0__["age"], '<------')
      }),
    "./src/login.js":
      (function (module, __webpack_exports__, __webpack_require__) {
        "use strict";
        __webpack_require__.r(__webpack_exports__);
        __webpack_require__.d(__webpack_exports__, "age", function () { return age; });
        __webpack_exports__["default"] = ('xxx');
        const age = 40
      })

  })

懒加载流程梳理

懒加载时,会触发 webpack_require 绑定的 e方法、t方法

  • import()可以实现指定模块的懒加载操作
  • 核心原理是jsonp
  • e方法会返回一个Promise
  • t 方法针对内容进行不同处理 处理方法取决于传入的数值( mode,8 6 7 3 2 1)

T方法分析及实现

  • 接收二个参数,一个是 value 一般用于表示被加载的模块id ,第二个值 mode 是一个二进制的数值
  • t 方法内部做的第一件事情就是调用自定义的 require 方法加载value 对应的模块导出,重新赋值给 value
  • 当获取到了这个 value 值之后余下的 8 4 ns 2 都是对当前的内容进行加工处理,然后返回使用
  • 当mode & 8 成立是直接将 value 返回 ( commonJS )
  • 当 mode & 4 成立时直接将 value 返回(esModule)
  • 如果上述条件都不成立,还是要继续处理 value ,定义一个 ns {}
    • ​ 6-1 如果拿到的 value 是一个可以直接使用的内容,例如是一个字符串,将它挂载到 ns 的 default 属性上
    • ​ 6-2
// 11 定义 t 方法,用于加载指定 value 的模块内容,之后对内容进行处理再返回
  __webpack_require__.t = function (value, mode) {
    // 01 加载 value 对应的模块内容( value 一般就是模块 id )
    // 加载之后的内容又重新赋值给 value 变量
    if (mode & 1) {
      value = __webpack_require__(value)
    }

    if (mode & 8) {  // 加载了可以直接返回使用的内容
      return value
    }

    if ((mode & 4) && typeof value === 'object' && value && value.__esModule) {
      return value
    }

    // 如果 8 和 4 都没有成立则需要自定义 ns 来通过 default 属性返回内容
    let ns = Object.create(null)

    __webpack_require__.r(ns)

    Object.defineProperty(ns, 'default', { enumerable: true, value: value })

    if (mode & 2 && typeof value !== 'string') {
      for (var key in value) {
        __webpack_require__.d(ns, key, function (key) {
          return value[key]
        }.bind(null, key))
      }
    }

    return ns
  }

懒加载源码分析

  • webpackJsonpCallback
    1.模块依赖关系的合并
    2.让当前的promise变成 成功的状态

  • jsonpArray.push = webpackJsonpCallback; 也重写了window[“webpackJsonp”]

  • 实现懒加载的流程
    webpack_require.e 方法 返回promise jsonp
    installedChunkData 为0表示已加载
    undefined 未加载
    promise 正在加载
    undefined的时候
    installedChunkData 添加 resolve reject 和promise
    调用 jsonpScriptSrc 方法

  • document.head.appendChild(script); 添加一个head标签
    返回Promise.all(promises); 但是promise没有进入成功态
    这个时候加载指定的文件内容
    进入webpackJsonpCallback
    获取需要加载的id chunkId = chunkIds[i];
    获取模块的resolve方法
    标记加载过 installedChunks[chunkId] = 0;
    判断模块数据有没有当前要加载的模块数据 并复制
    执行resolve方法
    进入__webpack_require__.t 方法
    加载模块 获得导出模块内容
    对模块内容进行处理 创建一个ns对象 并将模块内容加进去
    返回ns对象(导出内容)

jsonpScriptSrc 路径和命名

webpack 与tapable 底层核心工具库

webpack工作过程

  • 配置初始化

  • 内容编译

  • 编译后输出

    这是一种事件驱动型事件流工作机制,核心的两个部分是负责编译的complier和负责创建bundles的compilation,它们都是tapable的实例对象。

  • tapable 独立的库

    • 实例化hook注册事件监听
    • 通过hook触发事件监听
    • 执行懒编译生成的可执行代码
  • hook本质是tapable实例对象 同步和异步

    • Hook 普通钩子 监听器之间互相独立不干扰
    • BailHook 熔断钩子 某个监听返回非undefined时 后续不执行
    • WaterfallHook 瀑布钩子 上一个监听的返回值可传递至下一个
    • LoopHook 循环钩子 如果当前未返回false 则一直执行
  • 同步钩子

    • SynckHook
    • SyncBailHook
    • SyncWaterfallHook
    • SyncLoopHook
  • 异步串行钩子

    • AsyncSeriesHook
    • AsyncSeriesBailHook
    • AsyncSeriesWaterfallHook
  • 异步并行钩子

    • AsyncParalleHook
    • AsyncParalleBailHook

同步钩子的使用及调试

const { SyncHook } = require('tapable') //依赖tapable,导入

let hook = new SyncHook(['name', 'age']) //实例化Hook,定义示例

hook.tap('fn1', function (name, age) {   //tap设置监听
  console.log('fn1--->', name, age)
})

hook.tap('fn2', function (name, age) {
  console.log('fn2--->', name, age)
})

hook.call('zoe', 18) //call触发监听
  • SyncBailHook
    • 熔断:某一个地方断掉了 就不执行了
      return 非 undefined 后续监听不执行了
  • SyncWaterfallHook
    • return 的东西可以给下一个钩子使用
  • SyncLoopHook
    • 在钩子循环结束前 不会执行之后的钩子 只会执行前面的钩子
    • taps里面放了多个数组,如果当前的钩子返回undefined,则会从头循环钩子,从头开始循环。

异步钩子的使用及调试

const { AsyncParallelHook } = require('tapable')

let hook = new AsyncParallelHook(['name'])

// 对于异步钩子的使用,在添加事件监听时会存在三种方式: tap tapAsync tapPromise
// hook.tap('fn1', function (name) {
//   console.log('fn1--->', name)
// })

// hook.tap('fn2', function (name) {
//   console.log('fn2--->', name)
// })

// hook.callAsync('zoe', function () {
//   console.log('最后执行了回调操作')
// })

/* console.time('time')
hook.tapAsync('fn1', function (name, callback) {
  setTimeout(() => {
    console.log('fn1--->', name)
    callback()
  }, 1000)
})

hook.tapAsync('fn2', function (name, callback) {
  setTimeout(() => {
    console.log('fn2--->', name)
    callback()
  }, 2000)
})

hook.callAsync('lg', function () {
  console.log('最后一个回调执行了')
  console.timeEnd('time')
}) */

// 03 promise 
console.time('time')
hook.tapPromise('fn1', function (name) {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log('fn1--->', name)
      resolve()
    }, 1000)
  })
})

hook.tapPromise('fn2', function (name) {
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      console.log('fn2--->', name)
      resolve()
    }, 2000)
  })
})

hook.promise('foo').then(() => {
  console.log('end执行了')
  console.timeEnd('time')
})


SyncHook源码分析

  • Hook.js是父类
  • HookCodeFactory 钩子代码工厂
  • 先实例化Hook 再SyncHook

构造Hook 简写
this.args = args
this.taps = [] // 将来用于存放组装好的 {} 默认空数组
this._x = undefined // 将来在代码工厂函数中会给 _x = [f1, f2, f3…]
赋值一系列方法
重写 SyncHook 里的方法修饰 主要还是调用Hook的方法
tap方法 将字符串转化为对象
调用_insert 方法 将东西添加到taps里面
_insert
this.taps[this.taps.length] = options 将内容转换成对象存到taps里面

call方法
里面调用_createCall方法
_createCall
返回对象this.compile
taps: this.taps,
args: this.args

到了 SyncHook的compile方法 调了factory的setup方法 返回create方法
factory

​ setup 把taps的函数放在了.x属性里面
​ create 心就是创建一段可执行的代码体然后返回 然后拼接头和身体

SyncHook手写实现

//Hook.js
class Hook {
  constructor(args = []) {
    this.args = args
    this.taps = []  // 将来用于存放组装好的 {}
    this._x = undefined  // 将来在代码工厂函数中会给 _x = [f1, f2, f3....]
  }

  tap(options, fn) {
    if (typeof options === 'string') {
      options = { name: options }
    }
    options = Object.assign({ fn }, options)  // { fn:... name:fn1 }

    // 调用以下方法将组装好的 options 添加至 []
    this._insert(options)
  }

  _insert(options) {
    this.taps[this.taps.length] = options
  }

  call(...args) {
    // 01 创建将来要具体执行的函数代码结构
    let callFn = this._createCall()
    // 02 调用上述的函数(args传入进去)
    return callFn.apply(this, args)
  }

  _createCall() {
    return this.compile({
      taps: this.taps,
      args: this.args
    })
  }
}

module.exports = Hook

//SyncHook.js
let Hook = require('./Hook.js')

class HookCodeFactory {
  args() {
    return this.options.args.join(',')  // ["name", "age"]===> name, age
  }
  head() {
    return `var _x = this._x;`
  }
  content() {
    let code = ``
    for (var i = 0; i < this.options.taps.length; i++) {
      code += `var _fn${i} = _x[${i}];_fn${i}(${this.args()});`
    }
    return code
  }
  setup(instance, options) {  // 先准备后续需要使用到的数据
    this.options = options  // 这里的操作在源码中是通过 init 方法实现,而我们当前是直接挂在了 this 身上
    instance._x = options.taps.map(o => o.fn)   // this._x = [f1, f2, ....]
  }
  create() { // 核心就是创建一段可执行的代码体然后返回
    let fn
    // fn = new Function("name, age", "var _x = this._x, var _fn0 = _x[0]; _fn0(name, age);")
    fn = new Function(
      this.args(),
      this.head() + this.content()
    )
    return fn
  }
}

let factory = new HookCodeFactory()

class SyncHook extends Hook {
  constructor(args) {
    super(args)
  }

  compile(options) {  // {taps: [{}, {}], args: [name, age]}
    factory.setup(this, options)
    return factory.create(options)
  }
}

module.exports = SyncHook

AsyncParalleHook源码分析

核心为factory的 create方法
组装函数 核心为content方法 判断是否执行完最后的事情 再回调
相比同步函数 异步函数需要有一个最后的回调函数

定位webpack打包入口

  • cmd 文件核心的作用就组装了 node *****/webpack/bin/webpack.js

  • webpack.js 中核心的操作就是 require 了 node_modules/webpack-cli/bin/cli.js

  • cli.js

    • 当前文件一般有二个操作,处理参数,将参数交给不同的逻辑(分发业务)
      options
      complier
      complier.run( 至于run 里面做了什么,后续再看,当前只关注代码入口点 )

编译主流程分析

options就是webpack.config.js导出的内容
complier继承了tapable的类
主流程编译流程:
合并默认配置和自定义配置 —》 实例化compiler 设置Node文件读写的能力 通过循环 挂载plugins 处理webpack内部默认的插件(入口文件)
触发配置
Compiler.beforeRun
Compiler.run
Compiler.beforeCompile
Compiler.compile 编译
Compiler.make

EntryOptionPlugin

webpack.config.js里的入口配置插件管理

WebpackOptionsApply实例对象
process(options, compiler) 方法
EntryOptionPlugin 类 apply 方法
里面的 entryOption 是一个钩子实例,
entryOption 在 EntryOptionPlugin 内部的 apply 方法中调用了 tap (注册了事件监听)
上述的事件监听在 new 完了 EntryOptionPlugin 之后就调用了
itemToPlugin, 它是一个函数,接收三个参数( context item 'main)
SingleEntryPlugin
在调用 itemToPlugin, 的时候又返回了一个 实例对象
有一个构造函数,负责接收上文中的 context entry name
compilation 钩子监听
make 钩子监听

手写EntryOptionPlugin 流程

先新建 文件 WebpackOptionsApply.js 声明类 WebpackOptionsApply WebpackOptionsApply.js
调用 process(options, compiler)方法 WebpackOptionsApply.js
里面调用了 EntryOptionPlugin 的 apply方法 EntryOptionPlugin.js
方法里定义了一个钩子 entryOption EntryOptionPlugin.js
钩子调用了 itemToPlugin 函数 EntryOptionPlugin.js
函数返回了 SingleEntryPlugin 的实例的apply方法 SingleEntryPlugin.js
apply方法里监听了make 异步钩子 SingleEntryPlugin.js
compilation 触发

const EntryOptionPlugin = require("./EntryOptionPlugin")


class WebpackOptionsApply {
  process(options, compiler) {
    new EntryOptionPlugin().apply(compiler)

    compiler.hooks.entryOption.call(options.context, options.entry)
  }
}

module.exports = WebpackOptionsApply

const SingleEntryPlugin = require("./SingleEntryPlugin")

const itemToPlugin = function (context, item, name) {
  return new SingleEntryPlugin(context, item, name)
}

class EntryOptionPlugin {
  apply(compiler) {
    compiler.hooks.entryOption.tap('EntryOptionPlugin', (context, entry) => {
      itemToPlugin(context, entry, "main").apply(compiler)
    })
  }
}

module.exports = EntryOptionPlugin

class SingleEntryPlugin {
  constructor(context, entry, name) {
    this.context = context
    this.entry = entry
    this.name = name
  }

  apply(compiler) {
    compiler.hooks.make.tapAsync('SingleEntryPlugin', (compilation, callback) => {
      const { context, entry, name } = this
      console.log("make 钩子监听执行了~~~~~~")
      // compilation.addEntry(context, entry, name, callback)
    })
  }
}

module.exports = SingleEntryPlugin

make前流程回顾

一、步骤
  01 实例化 compiler 对象( 它会贯穿整个webpack工作的过程 )
  02 由 compiler 调用 run 方法

二、compiler 实例化操作
  01 compiler 继承 tapable,因此它具备钩子的操作能力(监听事件,触发事件,webpack是一个事件流)

  02 在实例化了 compiler 对象之后就往它的身上挂载很多属性,其中 NodeEnvironmentPlugin 这个操作就让它具备了
  文件读写的能力(我们的模拟时采用的是 node 自带的 fs )

  03 具备了 fs 操作能力之后又将 plugins 中的插件都挂载到了 compiler 对象身上  

  04 将内部默认的插件与 compiler 建立关系,其中 EntryOptionPlugin 处理了入口模块的 id 

  05 在实例化 compiler 的时候只是监听了 make 钩子(SingleEntryPlugin)
    5-1 在 SingleEntryPlugin 模块的 apply 方法中有二个钩子监听
    5-2 其中 compilation 钩子就是让 compilation 具备了利用 normalModuleFactory 工厂创建一个普通模块的能力
    5-3 因为它就是利用一个自己创建的模块来加载需要被打包的模块 
    5-4 其中 make 钩子 在 compiler.run 的时候会被触发,走到这里就意味着某个模块执行打包之前的所有准备工作就完成了
    5-5 addEntry 方法调用()

三、run 方法执行( 当前想看的是什么时候触发了 make 钩子 )

  01 run 方法里就是一堆钩子按着顺序触发(beforeRun run compile)

  02 compile 方法执行
    1 准备参数(其中 normalModuleFactory 是我们后续用于创建模块的)
    2 触发beforeCompile
    3 将第一步的参数传给一个函数,开始创建一个 compilation (newCompilation)
    4 在调用 newCompilation 的内部
      - 调用了 createCompilation 
      - 触发了 this.compilation 钩子 和 compilation 钩子的监听
  03 当创建了 compilation 对象之后就触发了 make 钩子

  04 当我们触发 make 钩子监听的时候,将 compilation 对象传递了过去 

四、总结 

  1 实例化 compiler  
  2 调用 compile 方法
  3 newCompilation 
  4 实例化了一个compilation 对象(它和 compiler 是有关系)
  5 触发 make 监听 
  6 addEntry 方法(这个时候就带着 context name entry  一堆的东西) 就奔着编译去了.....

    

addEntry流程分析

01 make 钩子在被触发的时候,接收到了 compilation 对象实现,它的身上挂载了很多内容

02 从 compilation 当中解构了三个值 
  entry : 当前需要被打包的模块的相对路径(./src/index.js)
  name: main 
  context: 当前项目的根路径 

03 dep 是对当前的入口模块中的依赖关系进行处理 

04 调用了 addEntry 方法 

05 在 compilation实例的身上有一个 addEntry 方法,然后内部调用了 _addModuleChain 方法,去处理依赖

06 在 compilation 当中我们可以通过 NormalModuleFactory 工厂来创建一个普通的模块对象  

07 在 webpack 内部默认启了一个 100 并发量的打包操作,当前我们看到的是 normalModule.create()

08 在 beforeResolve 里面会触发一个 factory 钩子监听【 这个部分的操作其实是处理 loader, 当前我们重点去看 】

09 上述操作完成之后获取到了一个函数被存在 factory 里,然后对它进行了调用  

10 在这个函数调用里又触发了一个叫 resolver 的钩子( 处理 loader的,拿到了 resolver方法就意味着所有的Loader 处理完毕 )

11 调用 resolver() 方法之后,就会进入到  afterResolve 这个钩子里,然后就会触发  new NormalModule 

12 在完成上述操作之后就将module 进行了保存和一些其它属性的添加  

13 调用 buildModule 方法开始编译---》 调用 build ---》doBuild 

addEntry、_addModuleChain、buildModule 实现

const path = require('path')
const Parser = require('./Parser')
const NormalModuleFactory = require('./NormalModuleFactory')
const { Tapable, SyncHook } = require('tapable')

// 实例化一个 normalModuleFactory parser 
const normalModuleFactory = new NormalModuleFactory()
const parser = new Parser()

class Compilation extends Tapable {
  constructor(compiler) {
    super()
    this.compiler = compiler
    this.context = compiler.context
    this.options = compiler.options
    // 让 compilation 具备文件的读写能力
    this.inputFileSystem = compiler.inputFileSystem
    this.outputFileSystem = compiler.outputFileSystem
    this.entries = []  // 存入所有入口模块的数组
    this.modules = [] // 存放所有模块的数据
    this.hooks = {
      succeedModule: new SyncHook(['module'])
    }
  }

  /**
   * 完成模块编译操作
   * @param {*} context 当前项目的根
   * @param {*} entry 当前的入口的相对路径
   * @param {*} name chunkName main 
   * @param {*} callback 回调
   */
  addEntry(context, entry, name, callback) {
    this._addModuleChain(context, entry, name, (err, module) => {
      callback(err, module)
    })
  }

  _addModuleChain(context, entry, name, callback) {
    let entryModule = normalModuleFactory.create({
      name,
      context,
      rawRequest: entry,
      resource: path.posix.join(context, entry),  // 当前操作的核心作用就是返回 entry 入口的绝对路径
      parser
    })

    const afterBuild = function (err) {
      callback(err, entryModule)
    }

    this.buildModule(entryModule, afterBuild)

    // 当我们完成了本次的 build 操作之后将 module 进行保存
    this.entries.push(entryModule)
    this.modules.push(entryModule)
  }

  /**
   * 完成具体的 build 行为
   * @param {*} module 当前需要被编译的模块
   * @param {*} callback 
   */
  buildModule(module, callback) {
    module.build(this, (err) => {
      // 如果代码走到这里就意味着当前 Module 的编译完成了
      this.hooks.succeedModule.call(module)
      callback(err)
    })
  }
}

module.exports = Compilation

build及parser实现

class NormalModule {
  constructor(data) {
    this.name = data.name
    this.entry = data.entry
    this.rawRequest = data.rawRequest
    this.parser = data.parser // TODO: 等待完成
    this.resource = data.resource
    this._source  // 存放某个模块的源代码
    this._ast // 存放某个模板源代码对应的 ast 
  }

  build(compilation, callback) {
    /**
     * 01 从文件中读取到将来需要被加载的 module 内容,这个
     * 02 如果当前不是 js 模块则需要 Loader 进行处理,最终返回 js 模块 
     * 03 上述的操作完成之后就可以将 js 代码转为 ast 语法树
     * 04 当前 js 模块内部可能又引用了很多其它的模块,因此我们需要递归完成 
     * 05 前面的完成之后,我们只需要重复执行即可
     */
    this.doBuild(compilation, (err) => {
      this._ast = this.parser.parse(this._source)
      callback(err)
    })
  }

  doBuild(compilation, callback) {
    this.getSource(compilation, (err, source) => {
      this._source = source
      callback()
    })
  }

  getSource(compilation, callback) {
    compilation.inputFileSystem.readFile(this.resource, 'utf8', callback)
  }
}

module.exports = NormalModule

const babylon = require('babylon')
const { Tapable } = require('tapable')

class Parser extends Tapable {
  parse(source) {
    return babylon.parse(source, {
      sourceType: 'module',
      plugins: ['dynamicImport']  // 当前插件可以支持 import() 动态导入的语法
    })
  }
}

module.exports = Parser

处理模块依赖

	01 需要将 Index.js 里的 require 方法替换成 __webpack_require__ 
	02 还有将 ./title 替换成 ./src/title.js 

	03 实现递归的操作 ,所以要将依赖的模块信息保存好,方例交给下一次 create 

	操作 _ast 语法树 内容处理
		在 dobuild 方法 转换语法树的时候操作
			generator _ast 改完之后不能运行 ,需要再转换成代码    @babel/generator
			traverse  进到这个语法里面取修改  		@babel/traverse
				// 这里的 _ast 就是当前 module 的语法树,我们可以对它进行修改,最后再将 ast 转回成 code 代码     
		
			替换内容
			    node.arguments = [types.stringLiteral(depModuleId)]  可以直接换数组内容   @babel/types
				
	afterBuild   处理依赖
		通过 processDependencies 方法实现模块依赖加载
			// 01 当前的函数核心功能就是实现一个被依赖模块的递归加载
			// 02 加载模块的思想都是创建一个模块,然后想办法将被加载模块的内容拿进来?
			// 03 当前我们不知道 module 需要依赖几个模块, 此时我们需要想办法让所有的被依赖的模块都加载完成之后再执行 callback?【 neo-async 】

抽离 createModule 方法

解耦创建 NormalModule 达到复用的目的
改写_addModuleChain 方法 单词创建 NormalModule 大部分逻辑转入 createModule 方法

编译依赖模块

完成 processDependencies
可以直接递归导入依赖 调用 createModule 加载依赖
parser, //没有这个语法树无法加载代码

chunk流程分析及实现

所谓封装 chunk 指的就是依据某个入口,然后找到它的所有依赖,将它们的源代码放在一起,之后再做合并
不同的模块 生成不同的代码块 chunk
make 钩子触发的时候吹chunk
compilation.seal 封装chunk
保存 chunk 信息和modules里对应的module的模块

生成chunk代码

生成打包文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值