webpack-cli
執行過程
1.webpack.config.js,shell options參數解析
2.new webpack(options)
3.run() 編譯的入口方法
4.compile() 出發make事件
5.addEntry() 找到js文件,進行下一步模塊綁定
6._addModuleChain() 解析js入口文件,建立模塊
7.buildModule() 編譯模塊,loader處理與acorn處理AST語法樹
8.seal() 每個chunk對應一個入口文件
9.createChunkAssets() 生成資源文件
10.MainTemplate.render() __webpack__require()引入
11.ModuleTemplate.render() 生成模版
12.module.source() 將生成好的js保存在compilation.assets中
13.Compiler.emitAssets()經過emitAssets將最終的js輸出到output的path中
參數解析
(function(){
yargs.options({...})
yargs.parse(process.argv.slice(2), (err, argv, output) => {...})
})()
加載webpack.config.js
(function(){
...
yargs.parse(process.argv.slice(2), (err, argv, output) => {
...
//解析argv,拿到配置文件參數
let options = require("./convert-argv")(argv);
function processOptions(options){
...
}
processOptions(options);
})
})()
執行webpack()
(function(){
...
yargs.parse(process.argv.slice(2), (err, argv, output) => {
...
//解析argv,拿到配置文件參數
let options = require("./convert-argv")(argv);
function processOptions(options){
...
const webpack = require("webpack");
compiler = webpack(options);
}
processOptions(options);
})
})()
webpack.js
const webpack = (options, callback) => {
//驗證webpack.config.js合法性
const webpackOptionsValidationErrors = validateSchema(
webpackOptionsSchema,
options
);
/*
[
{ entry: './index1.js', output: { filename: 'bundle1.js' } },
{ entry: './index2.js', output: { filename: 'bundle2.js' } }
]
*/
if (Array.isArray(options)) {
compiler = new MultiCompiler(options.map(options => webpack(options)));
} else if(typeof options === "object"){
...
//建立一個comiler對象
compiler = new Compiler(options.context);
//往comiler中註冊插件
new NodeEnvironmentPlugin().apply(compiler);
//執行config中配置的插件
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
//執行插件environment生命週期鉤子方法
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
//執行webpack內置插件
compiler.options = new
WebpackOptionsApply().process(options, compiler);
}else {
throw new Error("Invalid argument: options");
}
if (callback) {
...
//調用compiler.run開始編譯
compiler.run(callback);
}
//將compiler對象返回
return compiler
}
//NodeEnvironmentPlugin.js
class NodeEnvironmentPlugin {
apply(compiler) {
...
compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
});
}
}
module.exports = NodeEnvironmentPlugin;
class WebpackOptionsApply extends OptionsApply {
constructor() {
super();
}
process(options, compiler) {
//掛載配置,執行插件
let ExternalsPlugin;
compiler.outputPath = options.output.path;
compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
compiler.recordsOutputPath =
options.recordsOutputPath || options.recordsPath;
compiler.name = options.name;
new EntryOptionPlugin().apply(compiler);
new HarmonyModulesPlugin(options.module).apply(compiler);
new LoaderPlugin().apply(compiler);
...
}
}
module.exports = WebpackOptionsApply;
compiler.run() 開始編譯
class Compiler extends Tapable{
constructor(context){
...
}
watch(){...}
run(callback){
...
const onCompiled = (err, compilation){
...
}
//執行生命週期鉤子
this.hooks.beforeRun.callAsync(this, err => {
...
this.hooks.run.callAsync(this, err =>{
this.readRecords(err =>{
...
//開始編譯
this.compile(onCompiled);
})
}
}
}
compile(callback) {
//拿到參數
const params = this.newCompilationParams();
//執行編譯前鉤子
this.hooks.beforeCompile.callAsync(params, err => {
...
//建立compilation對象
const compilation = this.newCompilation(params);
//開始構建模塊對象
this.hooks.make.callAsync(compilation, err =>{
})
}
}
createCompilation() {
//建立comilation對象
return new Compilation(this);
}
newCompilation(params) {
//調用建立compilation對象方法
const compilation = this.createCompilation();
}
}
module.exports = Compiler;
建立 Compilation()
class Compilation extends Tapable {
constructor(compiler) {
super();
...
//初始化配置
this.compiler = compiler;
this.resolverFactory = compiler.resolverFactory;
this.inputFileSystem = compiler.inputFileSystem;
this.requestShortener = compiler.requestShortener;
//初始化模版
this.mainTemplate = new MainTemplate(this.outputOptions);
this.chunkTemplate = new ChunkTemplate(this.outputOptions);
this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
this.outputOptions
);
}
}
class MainTemplate extends Tapable {
this.hooks.requireExtensions.tap("MainTemplate", (source, chunk, hash) => {
const buf = [];
const chunkMaps = chunk.getChunkMaps();
// Check if there are non initial chunks which need to be imported using require-ensure
if (Object.keys(chunkMaps.hash).length) {
buf.push("// This file contains only the entry chunk.");
buf.push("// The chunk loading function for additional chunks");
buf.push(`${this.requireFn}.e = function requireEnsure(chunkId) {`);
buf.push(Template.indent("var promises = [];"));
buf.push(
Template.indent(
this.hooks.requireEnsure.call("", chunk, hash, "chunkId")
)
);
buf.push(Template.indent("return Promise.all(promises);"));
buf.push("};");
} else if (
chunk.hasModuleInGraph(m =>
m.blocks.some(b => b.chunkGroup && b.chunkGroup.chunks.length > 0)
)
) {
// There async blocks in the graph, so we need to add an empty requireEnsure
// function anyway. This can happen with multiple entrypoints.
buf.push("// The chunk loading function for additional chunks");
buf.push("// Since all referenced chunks are already included");
buf.push("// in this file, this function is empty here.");
buf.push(`${this.requireFn}.e = function requireEnsure() {`);
buf.push(Template.indent("return Promise.resolve();"));
buf.push("};");
}
buf.push("");
buf.push("// expose the modules object (__webpack_modules__)");
buf.push(`${this.requireFn}.m = modules;`);
buf.push("");
buf.push("// expose the module cache");
buf.push(`${this.requireFn}.c = installedModules;`);
buf.push("");
buf.push("// define getter function for harmony exports");
buf.push(`${this.requireFn}.d = function(exports, name, getter) {`);
buf.push(
Template.indent([
`if(!${this.requireFn}.o(exports, name)) {`,
Template.indent([
"Object.defineProperty(exports, name, { enumerable: true, get: getter });"
]),
"}"
])
);
buf.push("};");
buf.push("");
buf.push("// define __esModule on exports");
buf.push(`${this.requireFn}.r = function(exports) {`);
buf.push(
Template.indent([
"if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {",
Template.indent([
"Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });"
]),
"}",
"Object.defineProperty(exports, '__esModule', { value: true });"
])
);
buf.push("};");
buf.push("");
buf.push("// create a fake namespace object");
buf.push("// mode & 1: value is a module id, require it");
buf.push("// mode & 2: merge all properties of value into the ns");
buf.push("// mode & 4: return value when already ns object");
buf.push("// mode & 8|1: behave like require");
buf.push(`${this.requireFn}.t = function(value, mode) {`);
buf.push(
Template.indent([
`if(mode & 1) value = ${this.requireFn}(value);`,
`if(mode & 8) return value;`,
"if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
"var ns = Object.create(null);",
`${this.requireFn}.r(ns);`,
"Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
"if(mode & 2 && typeof value != 'string') for(var key in value) " +
`${this.requireFn}.d(ns, key, function(key) { ` +
"return value[key]; " +
"}.bind(null, key));",
"return ns;"
])
);
buf.push("};");
buf.push("");
buf.push(
"// getDefaultExport function for compatibility with non-harmony modules"
);
buf.push(this.requireFn + ".n = function(module) {");
buf.push(
Template.indent([
"var getter = module && module.__esModule ?",
Template.indent([
"function getDefault() { return module['default']; } :",
"function getModuleExports() { return module; };"
]),
`${this.requireFn}.d(getter, 'a', getter);`,
"return getter;"
])
);
buf.push("};");
buf.push("");
buf.push("// Object.prototype.hasOwnProperty.call");
buf.push(
`${
this.requireFn
}.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };`
);
const publicPath = this.getPublicPath({
hash: hash
});
buf.push("");
buf.push("// __webpack_public_path__");
buf.push(`${this.requireFn}.p = ${JSON.stringify(publicPath)};`);
return Template.asString(buf);
});
}
make開始構建
//開始構建模塊對象
this.hooks.make.callAsync(compilation, err =>{
})
//SingleEntryPlugin 監聽make
class SingleEntryPlugin {
apply(compiler) {
compiler.hooks.make.tapAsync(
"SingleEntryPlugin",
(compilation, callback) => {
const { entry, name, context } = this;
//建立依賴
const dep = SingleEntryPlugin.createDependency(entry, name);
//添加入口文件
compilation.addEntry(context, dep, name, callback);
}
);
}
}
//Compilation.js
class Compilation extends Tapable {
addEntry(context, entry, name, callback) {
...
this._addModuleChain(
context,
entry,
module => {
this.entries.push(module);
},
(err, module) => {
...
}
);
}
_addModuleChain(context, dependency, onModule, callback) {
...
//獲取模塊工廠
const moduleFactory = this.dependencyFactories.get(Dep);
this.semaphore.acquire(() => {
...
//建立模塊
moduleFactory.create(
{
contextInfo: {
issuer: "",
compiler: this.compiler.name
},
context: context,
dependencies: [dependency]
},...)
}
}
}
class NormalModuleFactory extends Tapable {
...
create(data, callback) {
...
this.buildModule(module, false, null, null, err => {
}
}
buildModule(module, optional, origin, dependencies, thisCallback) {
...
//開始編譯
module.build(
this.options,
this,
this.resolverFactory.get("normal", module.resolveOptions),
this.inputFileSystem,...)
}
}
//NodmalModule
doBuild(options, compilation, resolver, fs, callback) {
const loaderContext = this.createLoaderContext(
resolver,
options,
compilation,
fs
);
...
//開始運行loader
runLoaders(
{
resource: this.resource,
loaders: this.loaders,
context: loaderContext,
readResource: fs.readFile.bind(fs)
},
(err, result) => {
);
) }
總結
初始化階段
讀取命令行參數 |
從命令行中讀取用戶輸入的參數 |
require('./convert-argv')(argv) |
實例化Compiler |
1.用上一步獲得的參數初始化Compiler實例2.Compiler負責文件監聽和啓動編譯3.Compiler實例中包含了完整的Webpack配置,全局只有一個Compiler實例 |
compiler = webpack(options) |
加載插件 |
1.依次調用插件的apply方法,讓插件能夠監聽後續的全部事件節點,同時給插件傳入Compiler實例的引用,以方便插件經過compiler調用webpack提供的API |
plugin.apply(compiler) |
處理入口 |
讀取配置的Entry,爲每一個Entry實例化一個對應的EntryPlugin,爲後面該Entry的遞歸解析工做作準備 |
new EntryOptionsPlugin() new SingleEntryPlugin(context,item,name) compiler.hooks.make.tapAsync |
編譯階段
run |
啓動一次新的編譯 |
this.hooks.run.callAsync |
compile |
該事件是爲了告訴插件一次新的編譯將要啓動,同時會給插件傳入compiler對象 |
compiler(callback) |
compilation |
當webpack以開發模式運行時,每當監測到文件變化,一次新的,Compilation將被建立一個Compilation對象包含了當前的模塊資源,編譯生成資源,變化的文件,Compilation對象也提供了不少事件回調供插件擴展 |
newCompilation(params) |
make |
一個新的Compilation建立完畢開始編譯 |
this.hooks.make.callAsync |
addEntry |
即將從Entry開始讀取文件 |
compilation.addEntry this._addModuleChain |
moduleFactory |
建立模塊工廠 |
const moduleFactory = this.dependencyFactories.get(Dep) |
create |
開始建立模塊 |
factory(result,(err,module) this.hooks.resolver.tap("NormalModule") |
resolveRequestArray |
解析loader路徑 |
resolveRequestArray |
resolve |
解析資源文件路徑 |
resolve |
userRequest |
獲得包括loader在內的資源文件的絕對路徑用!拼起來的字符串 |
userRequest |
ruleSet.exec |
它能夠根據模塊路徑名,匹配出模塊所需的loader |
this.ruleSet.exec |
_run |
它能夠根據模塊路徑名,匹配出模塊所需的loader |
_run |
loaders |
獲得全部的loader數組 |
results[0].concat(loaders,results[1],results[2]) |
getParser |
獲取AST解析器 |
this.getParser(type,setting.parser) |
buildModule |
開始編譯模塊 |
thislbuildModule(module) buildModule(module,optional,origin,dependencies,thisCallback) |
build |
開始編譯 |
build |
doBuild |
開始編譯 |
doBuild |
loader |
使用loader進行轉換 |
runLoaders |
iteratePitchingLoaders |
開始遞歸執行pitchloader |
iteratePitchingLoaders |
loadLoader |
加載loader |
loadLoader |
runSyncOrAsync |
執行loader |
runSyncOrAsync |
processResource |
開始處理資源 |
processResource options.readResource iterateNormalLoaders |
createSource |
建立源碼對象 |
this.createSource |
parse |
使用parser轉換抽象語法樹 |
this.parser.parse |
parse |
繼續抽象語法樹 |
parse(source,initialState) |
acorn.parse |
繼續語法樹 |
acorn.parse(code,parserOptions) |
ImportDependency |
遍歷添加依賴 |
parser.state.module.addDependency |
succeedModule |
生成語法樹後就表示一個模塊編譯完成 |
this.hooks.successdModule.call(module) |
processModuleDependencies |
遞歸編譯依賴模塊 |
this.processModuleDependencies |
make後 |
結束make |
this.hooks.make.callAsync |
finish |
編譯完成 |
compilation.finishi() |
結束階段
seal |
封裝 |
compilation.seal |
addChunk |
生成資源 |
addChunk(name) |
createChunkAssets |
建立資源 |
this.createChunkAssets() |
getRenderManifest |
獲取要渲染的描述文件 |
getRenderManifest(options) |
render |
渲染源碼 |
source = fileManifest.render() |
afterCompile |
編譯結束 |
this.hooks.afterCompile |
shouldemit |
全部屬性輸出的文件已經生成好,詢問插件哪些文件須要輸出,哪些不須要 |
this.hooks.shouldEmit |
emit |
肯定後要輸出哪些文件後,執行文件輸出,能夠在這裏獲取和修改輸出內容 |
this.emitAssets(compilation,this.hooks.emit.callAsync) const emitFiles = err this.outputFileSystem.writeFile |
this.emitRecords |
寫入記錄 |
this.emitRecords |
done |
所有完成 |
this.hooks.done.callAsync |