21、定位 webpack 打包入口
01 cmd 文件外围的作用就组装了 node */webpack/bin/webpack.js
02 webpack.js 中外围的操作就是 require 了 node_modules/webpack-cli/bin/cli.js
03 cli.js
01 以后文件个别有二个操作,解决参数,将参数交给不同的逻辑(散发业务)
02 options
03 complier
04 complier.run(至于 run 外面做了什么,后续再看,以后只关注代码入口点)
wepack 的一个流程
合并配置 compilers.beforerun
实例化 compiler compilers.run
设置 node 文件读写的能力 compilers.beforecompile
通过循环挂载 plugins compilers.compile
解决 webpack 外部默认的插件(入口文件)compilers.make
22、webpack 手写实现
./webpack.js
const Compiler = require('./Compiler')
const NodeEnvironmentPlugin = require('./node/NodeEnvironmentPlugin')
const webpack = function (options) {
// 01 实例化 compiler 对象
let compiler = new Compiler(options.context)
compiler.options = options
// 02 初始化 NodeEnvironmentPlugin(让 compiler 具体文件读写能力)
new NodeEnvironmentPlugin().apply(compiler)
// 03 挂载所有 plugins 插件至 compiler 对象身上
if (options.plugins && Array.isArray(options.plugins)) {for (const plugin of options.plugins) {plugin.apply(compiler)
}
}
// 04 挂载所有 webpack 内置的插件(入口)// compiler.options = new WebpackOptionsApply().process(options, compiler);
// 05 返回 compiler 对象即可
return compiler
}
module.exports = webpack
const {
Tapable,
AsyncSeriesHook
} = require('tapable')
class Compiler extends Tapable {constructor(context) {super()
this.context = context
this.hooks = {done: new AsyncSeriesHook(["stats"]),//
}
}
run(callback) {
callback(null, {toJson() {
return {entries: [], // 以后次打包的入口信息
chunks: [], // 以后次打包的 chunk 信息
modules: [], // 模块信息
assets: [], // 以后次打包最终生成的资源}
}
})
}
}
module.exports = Compiler
23、entryOptionPlugin
WebpackOptionsApply
process(options, compiler)
EntryOptionPlugin
entryOption 是一个钩子实例,
entryOption 在 EntryOptionPlugin 外部的 apply 办法中调用了 tap(注册了事件监听)
上述的事件监听在 new 完了 EntryOptionPlugin 之后就调用了
itemToPlugin,它是一个函数,接管三个参数(context item‘main)
SingleEntryPlugin
在调用 itemToPlugin,的时候又返回了一个 实例对象
有一个构造函数,负责接管上文中的 context entry name
compilation 钩子监听
make 钩子监听
./EntryOptionPlugin.js
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
./WebpackOptionsApply.js
const EntryOptionPlugin = require("./EntryOptionPlugin")
class WebpackOptionsApply {process(options, compiler) {new EntryOptionPlugin().apply(compiler)
compiler.hooks.entryOption.call(options.context, options.entry)
}
}
module.exports = WebpackOptionsApply
./SingleEntryPlugin.js
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
24、实现 run 办法
run(callback) {console.log('run 办法执行了~~~~')
const finalCallback = function (err, stats) {callback(err, stats)
}
const onCompiled = function (err, compilation) {console.log('onCompiled~~~~')
finalCallback(err, {toJson() {
return {entries: [],
chunks: [],
modules: [],
assets: []}
}
})
}
this.hooks.beforeRun.callAsync(this, (err) => {this.hooks.run.callAsync(this, (err) => {this.compile(onCompiled)
})
})
}
25、实现 compile 办法
newCompilationParams 办法调用,返回 params,normalModuleFactory
上述操作是为了获取 params
接着调用 beforeCompile 钩子监听,在它的回调中又触发了 compile 监听
调用 newCompilation 办法,传入下面的 params,返回一个 compilation
调用了 createCompilation
上述操作之后登程 make 钩子监听
const {
Tapable,
SyncHook,
SyncBailHook,
AsyncSeriesHook,
AsyncParallelHook
} = require('tapable')
const NormalModuleFactory = require('./NormalModuleFactory')
const Compilation = require('./Compilation')
class Compiler extends Tapable {constructor(context) {super()
this.context = context
this.hooks = {done: new AsyncSeriesHook(["stats"]),
entryOption: new SyncBailHook(["context", "entry"]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
thisCompilation: new SyncHook(["compilation", "params"]),
compilation: new SyncHook(["compilation", "params"]),
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
make: new AsyncParallelHook(["compilation"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
}
}
run(callback) {console.log('run 办法执行了~~~~')
const finalCallback = function (err, stats) {callback(err, stats)
}
const onCompiled = function (err, compilation) {console.log('onCompiled~~~~')
finalCallback(err, {toJson() {
return {entries: [],
chunks: [],
modules: [],
assets: []}
}
})
}
this.hooks.beforeRun.callAsync(this, (err) => {this.hooks.run.callAsync(this, (err) => {this.compile(onCompiled)
})
})
}
compile(callback) {const params = this.newCompilationParams()
this.hooks.beforeRun.callAsync(params, (err) => {this.hooks.compile.call(params)
const compilation = this.newCompilation(params)
this.hooks.make.callAsync(compilation, (err) => {console.log('make 钩子监听触发了~~~~~')
callback()})
})
}
newCompilationParams() {
const params = {normalModuleFactory: new NormalModuleFactory()
}
return params
}
newCompilation(params) {const compilation = this.createCompilation()
}
createCompilation() {return new Compilation(this)
}
}
module.exports = Compiler
26、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 一堆的货色)就奔着编译去了 …..
WebpackOptionsApply
process(options, compiler)
EntryOptionPlugin
entryOption 是一个钩子实例,
entryOption 在 EntryOptionPlugin 外部的 apply 办法中调用了 tap(注册了事件监听)
上述的事件监听在 new 完了 EntryOptionPlugin 之后就调用了
itemToPlugin,它是一个函数,接管三个参数(context item ‘main)
SingleEntryPlugin
在调用 itemToPlugin,的时候又返回了一个 实例对象
有一个构造函数,负责接管上文中的 context entry name
compilation 钩子监听
make 钩子监听
27、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
28、addEntry 办法实现
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
NormalModuleFactory
const NormalModule = require("./NormalModule")
class NormalModuleFactory {create(data) {return new NormalModule(data)
}
}
module.exports = NormalModuleFactory
./NormalModule
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
./parser
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
const {
Tapable,
SyncHook,
SyncBailHook,
AsyncSeriesHook,
AsyncParallelHook
} = require('tapable')
const Stats = require('./Stats')
const NormalModuleFactory = require('./NormalModuleFactory')
const Compilation = require('./Compilation')
class Compiler extends Tapable {constructor(context) {super()
this.context = context
this.hooks = {done: new AsyncSeriesHook(["stats"]),
entryOption: new SyncBailHook(["context", "entry"]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
thisCompilation: new SyncHook(["compilation", "params"]),
compilation: new SyncHook(["compilation", "params"]),
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
make: new AsyncParallelHook(["compilation"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
}
}
run(callback) {console.log('run 办法执行了~~~~')
const finalCallback = function (err, stats) {callback(err, stats)
}
const onCompiled = function (err, compilation) {console.log('onCompiled~~~~')
finalCallback(err, new Stats(compilation))
}
this.hooks.beforeRun.callAsync(this, (err) => {this.hooks.run.callAsync(this, (err) => {this.compile(onCompiled)
})
})
}
compile(callback) {const params = this.newCompilationParams()
this.hooks.beforeRun.callAsync(params, (err) => {this.hooks.compile.call(params)
const compilation = this.newCompilation(params)
this.hooks.make.callAsync(compilation, (err) => {console.log('make 钩子监听触发了~~~~~')
callback(err, compilation)
})
})
}
newCompilationParams() {
const params = {normalModuleFactory: new NormalModuleFactory()
}
return params
}
newCompilation(params) {const compilation = this.createCompilation()
this.hooks.thisCompilation.call(compilation, params)
this.hooks.compilation.call(compilation, params)
return compilation
}
createCompilation() {return new Compilation(this)
}
}
module.exports = Compiler
29、模块依赖
01 须要将 Index.js 里的 require 办法替换成 webpack_require
02 还有将 ./title 替换成 ./src/title.js
03 实现递归的操作,所以要将依赖的模块信息保留好,不便交给下一次 create
./NormalModule.js
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)
// 这里的 _ast 就是以后 module 的语法树,咱们能够对它进行批改,最初再将 ast 转回成 code 代码
traverse(this._ast, {CallExpression: (nodePath) => {
let node = nodePath.node
// 定位 require 所在的节点
if (node.callee.name === 'require') {
// 获取原始申请门路
let modulePath = node.arguments[0].value // './title'
// 取出以后被加载的模块名称
let moduleName = modulePath.split(path.posix.sep).pop() // title
// [以后咱们的打包器只解决 js]
let extName = moduleName.indexOf('.') == -1 ? '.js' : ''
moduleName += extName // title.js
//【最终咱们想要读取以后 js 里的内容】所以咱们须要个绝对路径
let depResource = path.posix.join(path.posix.dirname(this.resource), moduleName)
//【将以后模块的 id 定义 OK】let depModuleId = './' + path.posix.relative(this.context, depResource) // ./src/title.js
// 记录以后被依赖模块的信息,不便前面递归加载
this.dependencies.push({
name: this.name, // TODO: 未来须要批改
context: this.context,
rawRequest: moduleName,
moduleId: depModuleId,
resource: depResource
})
// 替换内容
node.callee.name = '__webpack_require__'
node.arguments = [types.stringLiteral(depModuleId)]
}
}
})
// 上述的操作是利用 ast 按要求做了代码批改,上面的内容就是利用 .... 将批改后的 ast 转回成 code
let {code} = generator(this._ast)
this._source = code
callback(err)
})
./compilation
const path = require('path')
const async = require('neo-async')
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) {
this.createModule({
parser,
name: name,
context: context,
rawRequest: entry,
resource: path.posix.join(context, entry),
moduleId: './' + path.posix.relative(context, path.posix.join(context, entry))
}, (entryModule) => {this.entries.push(entryModule)
}, callback)
}
/**
* 定义一个创立模块的办法,达到复用的目标
* @param {*} data 创立模块时所须要的一些属性值
* @param {*} doAddEntry 可选参数,在加载入口模块的时候,将入口模块的 id 写入 this.entries
* @param {*} callback
*/
createModule(data, doAddEntry, callback) {let module = normalModuleFactory.create(data)
const afterBuild = (err, module) => {
// 应用箭头函数能够保障 this 指向在定义时就确定
// 在 afterBuild 当中咱们就须要判断一下,以后次 module 加载实现之后是否须要解决依赖加载
if (module.dependencies.length > 0) {
// 以后逻辑就示意 module 有须要依赖加载的模块,因而咱们能够再独自定义一个办法来实现
this.processDependencies(module, (err) => {callback(err, module)
})
} else {callback(err, module)
}
}
this.buildModule(module, afterBuild)
// 当咱们实现了本次的 build 操作之后将 module 进行保留
doAddEntry && doAddEntry(module)
this.modules.push(module)
}
/**
* 实现具体的 build 行为
* @param {*} module 以后须要被编译的模块
* @param {*} callback
*/
buildModule(module, callback) {module.build(this, (err) => {
// 如果代码走到这里就意味着以后 Module 的编译实现了
this.hooks.succeedModule.call(module)
callback(err, module)
})
}
processDependencies(module, callback) {
// 01 以后的函数外围性能就是实现一个被依赖模块的递归加载
// 02 加载模块的思维都是创立一个模块,而后想方法将被加载模块的内容拿进来?
// 03 以后咱们不晓得 module 须要依赖几个模块,此时咱们须要想方法让所有的被依赖的模块都加载实现之后再执行 callback?【neo-async】let dependencies = module.dependencies
async.forEach(dependencies, (dependency, done) => {
this.createModule({
parser,
name: dependency.name,
context: dependency.context,
rawRequest: dependency.rawRequest,
moduleId: dependency.moduleId,
resource: dependency.resource
}, null, done)
}, callback)
}
}
module.exports = Compilation
30、chunk 流程剖析