- Published on
重学webpack(四) -- webpack 源码阅读 run 方法
- Authors
- Name
- Et cetera
前言
compiler
和 compilation
的区别?
webpack 中 compiler
是webpack
的编译器, 用于编译整个项目compilation
是每次编译的上下文, 用于存储编译过程中的各种信息
执行周期的区别
compiler
的执行周期是整个项目的编译周期 (整个项目只有一个compiler
)compilation
的执行周期是每次编译的周期 (每次编译都会生成一个新的compilation
)
run 方法
// beforeRun => run => compile
class Compiler {
this.idle = false
run(callback) {
if (this.running) {
return callback(new ConcurrentCompilationError())
}
let logger
const finalCallback = (err, stats) => {
if (logger) logger.time('beginIdle')
this.idle = true
this.cache.beginIdle()
this.idle = true
if (logger) logger.timeEnd('beginIdle')
this.running = false
if (err) {
this.hooks.failed.call(err)
}
if (callback !== undefined) callback(err, stats)
this.hooks.afterDone.call(stats)
}
const startTime = Date.now()
this.running = true
// 等待 compilation 将所有模块编译完成后执行的回调函数
const onCompiled = (err, compilation) => {
if (err) return finalCallback(err)
if (this.hooks.shouldEmit.call(compilation) === false) {
compilation.startTime = startTime
compilation.endTime = Date.now()
const stats = new Stats(compilation)
this.hooks.done.callAsync(stats, (err) => {
if (err) return finalCallback(err)
return finalCallback(null, stats)
})
return
}
// 使用 process.nextTick() 将 emitAssets 放到下一个事件循环中执行,为了防止阻塞
process.nextTick(() => {
logger = compilation.getLogger('webpack.Compiler')
logger.time('emitAssets')
// 输出构建产物,将编译后的文件写入到文件系统中(build文件夹)
this.emitAssets(compilation, (err) => {
logger.timeEnd('emitAssets')
if (err) return finalCallback(err)
if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true
compilation.startTime = startTime
compilation.endTime = Date.now()
logger.time('done hook')
const stats = new Stats(compilation)
this.hooks.done.callAsync(stats, (err) => {
logger.timeEnd('done hook')
if (err) return finalCallback(err)
this.hooks.additionalPass.callAsync((err) => {
if (err) return finalCallback(err)
this.compile(onCompiled)
})
})
return
}
logger.time('emitRecords')
// 将资源和模块的记录信息写入到记录文件(record file)
this.emitRecords((err) => {
logger.timeEnd('emitRecords')
if (err) return finalCallback(err)
compilation.startTime = startTime
compilation.endTime = Date.now()
logger.time('done hook')
const stats = new Stats(compilation)
this.hooks.done.callAsync(stats, (err) => {
logger.timeEnd('done hook')
if (err) return finalCallback(err)
this.cache.storeBuildDependencies(compilation.buildDependencies, (err) => {
if (err) return finalCallback(err)
return finalCallback(null, stats)
})
})
})
})
})
}
// 自己的 build.js 文件里执行的run() 实际上就是这里的 run
const run = () => {
this.hooks.beforeRun.callAsync(this, (err) => {
if (err) return finalCallback(err)
this.hooks.run.callAsync(this, (err) => {
if (err) return finalCallback(err)
this.readRecords((err) => {
if (err) return finalCallback(err)
// 然后进入 compile 方法, onCompiled 是 compile 方法的回调函数 (编译完成后执行的回调函数)
this.compile(onCompiled)
})
})
})
}
// 判断是否是闲置状态
if (this.idle) {
this.cache.endIdle((err) => {
if (err) return finalCallback(err)
this.idle = false
run()
})
} else {
run()
}
}
}
compile 方法
// beforeCompile => compile => make => finishMake => afterCompile => done
compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
// 创建 compilation
const compilation = this.newCompilation(params);
const logger = compilation.getLogger("webpack.Compiler");
logger.time("make hook");
// 真正开始编译
// 这一步通过在创建 compiler 时候注册插件时是通过在 lib/EntryPlugin 里注册的 apply 方法注册的,所以执行 make.callAsync 就会触发,具体源码看下一段代码
this.hooks.make.callAsync(compilation, err => {
logger.timeEnd("make hook");
if (err) return callback(err);
logger.time("finish make hook");
this.hooks.finishMake.callAsync(compilation, err => {
logger.timeEnd("finish make hook");
if (err) return callback(err);
process.nextTick(() => {
logger.time("finish compilation");
compilation.finish(err => {
logger.timeEnd("finish compilation");
if (err) return callback(err);
logger.time("seal compilation");
compilation.seal(err => {
logger.timeEnd("seal compilation");
if (err) return callback(err);
logger.time("afterCompile hook");
this.hooks.afterCompile.callAsync(compilation, err => {
logger.timeEnd("afterCompile hook");
if (err) return callback(err);
return callback(null, compilation);
});
});
});
});
});
});
});
}
class EntryPlugin {
constructor(context, entry, options) {
this.context = context
this.entry = entry
this.options = options || ''
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
apply(compiler) {
compiler.hooks.compilation.tap(
'EntryPlugin',
(compilation, { normalModuleFactory }) => {
compilation.dependencyFactories.set(EntryDependency, normalModuleFactory)
}
)
const { entry, options, context } = this
const dep = EntryPlugin.createDependency(entry, options)
// 在 make 阶段执行, 通过 addEntry 方法将入口模块添加到 compilation 中
compiler.hooks.make.tapAsync('EntryPlugin', (compilation, callback) => {
// 这里就开始进入从入口开始模块编译了,生成 webpack 核心的模块图谱 (moduleGraph)
compilation.addEntry(context, dep, options, (err) => {
callback(err)
})
})
}
}