Heim > Artikel > Web-Frontend > Detaillierte Einführung in die Erstellung eines Webpacks
Derzeit wird Webpack in fast allen Geschäftsentwicklungen und -konstruktionen verwendet. Daher stellt Ihnen der folgende Artikel hauptsächlich relevante Informationen zum detaillierten Prozess der Webpack-Erstellung vor. Der Artikel bietet einen gewissen Referenz-Lernwert für alle, die ihn zum Lernen oder Arbeiten benötigen . Schauen Sie mal rein.
Als Artefakt zum Laden und Verpacken von Modulen müssen Sie nur ein paar Dateien konfigurieren und verschiedene Lader laden, um eine problemlose Prozessentwicklung zu genießen. Aber für eine hochkomplexe Plug-in-Sammlung wie Webpack sind der Gesamtprozess und die Ideen für uns immer noch sehr transparent.
Der Zweck dieses Artikels besteht darin, den Prozess der Eingabe des Webpack-Befehls über die Befehlszeile oder der Konfiguration des npm-Skripts und der anschließenden Ausführung des Befehls in package.json zu verstehen, bis die gepackte Bundle-Datei im Projektverzeichnis angezeigt wird . Welche Arbeit haben Sie für uns erledigt?
Die zum Testen verwendete Webpack-Version ist webpack@3.4.1
Die relevanten Konfigurationen sind in webpack.config.js definiert, einschließlich Eintrag, Ausgabe, Modul, Plugins usw. und die Die Befehlszeile führt den Webpack-Befehl webpack aus. Die Datei wird gemäß der Konfiguration in der Konfigurationsdatei gepackt und verarbeitet, und die endgültige gepackte Datei wird generiert.
Wenn beim Ausführen von Webpack über die Befehlszeile der Webpack-Befehl nicht in der globalen Befehlszeile gefunden wird, wird die lokale Datei node-modules/bin/webpack.js ausgeführt.
Verwenden Sie die yargs-Bibliothek in bin/webpack.js, um die Befehlszeilenparameter zu analysieren, die Webpack-Konfigurationsobjektoptionen zu verarbeiten und die Funktion processOptions()
aufzurufen.
// 处理编译相关,核心函数 function processOptions(options) { // promise风格的处理,暂时还没遇到这种情况的配置 if(typeof options.then === "function") {...} // 处理传入的options为数组的情况 var firstOptions = [].concat(options)[0]; var statsPresetToOptions = require("../lib/Stats.js").presetToOptions; // 设置输出的options var outputOptions = options.stats; if(typeof outputOptions === "boolean" || typeof outputOptions === "string") { outputOptions = statsPresetToOptions(outputOptions); } else if(!outputOptions) { outputOptions = {}; } // 处理各种现实相关的参数 ifArg("display", function(preset) { outputOptions = statsPresetToOptions(preset); }); ... // 引入lib下的webpack.js,入口文件 var webpack = require("../lib/webpack.js"); // 设置最大错误追踪堆栈 Error.stackTraceLimit = 30; var lastHash = null; var compiler; try { // 编译,这里是关键,需要进入lib/webpack.js文件查看 compiler = webpack(options); } catch(e) { // 错误处理 var WebpackOptionsValidationError = require("../lib/WebpackOptionsValidationError"); if(e instanceof WebpackOptionsValidationError) { if(argv.color) console.error("\u001b[1m\u001b[31m" + e.message + "\u001b[39m\u001b[22m"); else console.error(e.message); process.exit(1); // eslint-disable-line no-process-exit } throw e; } // 显示相关参数处理 if(argv.progress) { var ProgressPlugin = require("../lib/ProgressPlugin"); compiler.apply(new ProgressPlugin({ profile: argv.profile })); } // 编译完后的回调函数 function compilerCallback(err, stats) {} // watch模式下的处理 if(firstOptions.watch || options.watch) { var watchOptions = firstOptions.watchOptions || firstOptions.watch || options.watch || {}; if(watchOptions.stdin) { process.stdin.on("end", function() { process.exit(0); // eslint-disable-line }); process.stdin.resume(); } compiler.watch(watchOptions, compilerCallback); console.log("\nWebpack is watching the files…\n"); } else // 调用run()函数,正式进入编译过程 compiler.run(compilerCallback); }
Wie in der Abbildung unten gezeigt, ist die Schlüsselfunktion in lib/webpack.js Webpack, wobei Definiert einige Vorgänge im Zusammenhang mit der Kompilierung.
"use strict"; const Compiler = require("./Compiler"); const MultiCompiler = require("./MultiCompiler"); const NodeEnvironmentPlugin = require("./node/NodeEnvironmentPlugin"); const WebpackOptionsApply = require("./WebpackOptionsApply"); const WebpackOptionsDefaulter = require("./WebpackOptionsDefaulter"); const validateSchema = require("./validateSchema"); const WebpackOptionsValidationError = require("./WebpackOptionsValidationError"); const webpackOptionsSchema = require("../schemas/webpackOptionsSchema.json"); // 核心方法,调用该方法,返回Compiler的实例对象compiler function webpack(options, callback) {...} exports = module.exports = webpack; // 设置webpack对象的常用属性 webpack.WebpackOptionsDefaulter = WebpackOptionsDefaulter; webpack.WebpackOptionsApply = WebpackOptionsApply; webpack.Compiler = Compiler; webpack.MultiCompiler = MultiCompiler; webpack.NodeEnvironmentPlugin = NodeEnvironmentPlugin; webpack.validate = validateSchema.bind(this, webpackOptionsSchema); webpack.validateSchema = validateSchema; webpack.WebpackOptionsValidationError = WebpackOptionsValidationError; // 对外暴露一些插件 function exportPlugins(obj, mappings) {...} exportPlugins(exports, {...}); exportPlugins(exports.optimize = {}, {...});
Sehen wir uns die Hauptoperationen an, die in der Webpack-Funktion definiert sind
// 核心方法,调用该方法,返回Compiler的实例对象compiler function webpack(options, callback) { // 验证是否符合格式 const webpackOptionsValidationErrors = validateSchema(webpackOptionsSchema, options); if(webpackOptionsValidationErrors.length) { throw new WebpackOptionsValidationError(webpackOptionsValidationErrors); } let compiler; // 传入的options为数组的情况,调用MultiCompiler进行处理,目前还没遇到过这种情况的配置 if(Array.isArray(options)) { compiler = new MultiCompiler(options.map(options => webpack(options))); } else if(typeof options === "object") { // 配置options的默认参数 new WebpackOptionsDefaulter().process(options); // 初始化一个Compiler的实例 compiler = new Compiler(); // 设置context的默认值为进程的当前目录,绝对路径 compiler.context = options.context; // 定义compiler的options属性 compiler.options = options; // Node环境插件,其中设置compiler的inputFileSystem,outputFileSystem,watchFileSystem,并定义了before-run的钩子函数 new NodeEnvironmentPlugin().apply(compiler); // 应用每个插件 if(options.plugins && Array.isArray(options.plugins)) { compiler.apply.apply(compiler, options.plugins); } // 调用environment插件 compiler.applyPlugins("environment"); // 调用after-environment插件 compiler.applyPlugins("after-environment"); // 处理compiler对象,调用一些必备插件 compiler.options = new WebpackOptionsApply().process(options, compiler); } else { throw new Error("Invalid argument: options"); } if(callback) { if(typeof callback !== "function") throw new Error("Invalid argument: callback"); if(options.watch === true || (Array.isArray(options) && options.some(o => o.watch))) { const watchOptions = Array.isArray(options) ? options.map(o => o.watchOptions || {}) : (options.watchOptions || {}); return compiler.watch(watchOptions, callback); } compiler.run(callback); } return compiler; }
Die Webpack-Funktion führt hauptsächlich die folgenden zwei Operationen aus:
Instanziieren Sie die Compiler-Klasse. Diese Klasse erbt von der Tapable-Klasse, einer Plug-in-Architektur, die auf Publish und Subscribe basiert. Webpack ist der gesamte Prozess, der auf der Grundlage des Publish-Subscribe-Modells von Tapable implementiert wird. In Tapable werden Plugins verwendet, um den Plug-In-Namen und die entsprechende Rückruffunktion zu registrieren. Unter einem bestimmten Plug-In registrierte Rückrufe werden auf unterschiedliche Weise über Funktionen wie apply, applyPlugins, applyPluginsWater und applyPluginsAsync aufgerufen.
Verarbeiten Sie das Webpack-Compiler-Objekt über WebpackOptionsApply und rufen Sie einige erforderliche Plug-Ins über compiler.apply
auf Rufen Sie einige Plug-Ins auf, um einige Prozesse abzuwickeln.
run() ruft auf
run Die Funktion Löst hauptsächlich das Before-Run-Ereignis aus, und das Run-Ereignis wird in der Rückruffunktion des Before-Run-Ereignisses ausgelöst. Im Run-Ereignis wird die Funktion readRecord zum Lesen der Datei und die Funktion compile()
zum Kompilieren aufgerufen .
compile() ruft die Kompilierungsfunktion
auf, um die relevanten Kompilierungsprozesse zu definieren, die hauptsächlich die folgenden Prozesse umfassen:
Erstellen Der Kompilierungsparameter
löst das Vorkompilierungsereignis aus,
löst das Kompilierungsereignis aus und startet die Kompilierung
Erstellen Sie ein Kompilierungsobjekt, das für die spezifischen Details des gesamten Kompilierungsprozesses verantwortlich ist
Lösen Sie das Make-Ereignis aus, um mit der Erstellung von Modulen zu beginnen und ihre Abhängigkeiten zu analysieren
Gemäß dem Eintrag bestimmt die Art der Konfiguration, welcher Make-Event-Callback des Plugins aufgerufen wird. Beispielsweise ruft ein Eintrag mit einem Eintrag die für das Make-Ereignis unter SingleEntryPlugin.js registrierte Rückruffunktion auf. Das Gleiche gilt für andere Einträge mit mehreren Einträgen.
Rufen Sie die Funktion addEntry des Kompilierungsobjekts auf, um Module und Abhängigkeiten zu erstellen.
In der Rückruffunktion des Make-Ereignisses wird die in der Ausführungsmethode definierte onCompiled-Rückruffunktion aufgerufen, um den Emissionsvorgang abzuschließen und die Ergebnisse in die Zieldatei zu schreiben
Definition der Kompilierungsfunktion
compile(callback) { // 创建编译参数,包括模块工厂和编译依赖参数数组 const params = this.newCompilationParams(); // 触发before-compile 事件,开始整个编译过程 this.applyPluginsAsync("before-compile", params, err => { if(err) return callback(err); // 触发compile事件 this.applyPlugins("compile", params); // 构建compilation对象,compilation对象负责具体的编译细节 const compilation = this.newCompilation(params); // 触发make事件,对应的监听make事件的回调函数在不同的EntryPlugin中注册,比如singleEntryPlugin this.applyPluginsParallel("make", compilation, err => { if(err) return callback(err); compilation.finish(); compilation.seal(err => { if(err) return callback(err); this.applyPluginsAsync("after-compile", compilation, err => { if(err) return callback(err); return callback(null, compilation); }); }); }); }); }
Am Beispiel der Einzeleintragseintragskonfiguration definiert das EntryOptionPlugin-Plugin, welches Plug-in zum Parsen von Einträgen mit unterschiedlichen Konfigurationen aufgerufen werden soll. Entsprechende Make-Event-Callback-Funktionen werden in Entry-Plug-Ins mit unterschiedlichen Konfigurationen registriert und nach dem Auslösen des Make-Events aufgerufen. lautet wie folgt:
Die Apply-Methode eines Plug-Ins ist hauptsächlich die Kernmethode eines Plug-Ins angerufen.Das EntryOptionPlugin-Plug-in wird in webpackOptionsApply aufgerufen, das intern definiert, welches Plug-in zum Parsen der Eintragsdatei verwendet wird. const SingleEntryPlugin = require("./SingleEntryPlugin");
const MultiEntryPlugin = require("./MultiEntryPlugin");
const DynamicEntryPlugin = require("./DynamicEntryPlugin");
module.exports = class EntryOptionPlugin {
apply(compiler) {
compiler.plugin("entry-option", (context, entry) => {
function itemToPlugin(item, name) {
if(Array.isArray(item)) {
return new MultiEntryPlugin(context, item, name);
} else {
return new SingleEntryPlugin(context, item, name);
}
}
// 判断entry字段的类型去调用不同的入口插件去处理
if(typeof entry === "string" || Array.isArray(entry)) {
compiler.apply(itemToPlugin(entry, "main"));
} else if(typeof entry === "object") {
Object.keys(entry).forEach(name => compiler.apply(itemToPlugin(entry[name], name)));
} else if(typeof entry === "function") {
compiler.apply(new DynamicEntryPlugin(context, entry));
}
return true;
});
}
};
Wenn das Eintragsoptionsereignis ausgelöst wird, führt das EntryOptionPlugin-Plugin Folgendes aus:
Bestimmen Sie die Art des Eintrags über das Eingabefeld, das dem Eingabefeld entspricht, das a ist String-Objektfunktion.
Jeder unterschiedliche Typ ruft ein anderes Plug-In auf, um die Konfiguration des Eingangs zu verwalten. Die allgemeine Verarbeitungslogik lautet wie folgt:
数组类型的entry调用multiEntryPlugin插件去处理,对应了多入口的场景
function的entry调用了DynamicEntryPlugin插件去处理,对应了异步chunk的场景
string类型的entry或者object类型的entry,调用SingleEntryPlugin去处理,对应了单入口的场景
【问题】entry-option 事件是在什么时机被触发的呢?
如下代码所示,是在WebpackOptionsApply.js中,先调用处理入口的EntryOptionPlugin插件,然后触发 entry-option 事件,去调用不同类型的入口处理插件。
注意:调用插件的过程也就是一个注册事件以及回调函数的过程。
WebpackOptionApply.js
// 调用处理入口entry的插件 compiler.apply(new EntryOptionPlugin()); compiler.applyPluginsBailResult("entry-option", options.context, options.entry);
前面说到,make事件触发时,对应的回调逻辑都在不同配置入口的插件中注册的。下面以SingleEntryPlugin为例,说明从 make 事件被触发,到编译结束的整个过程。
SingleEntryPlugin.js
class SingleEntryPlugin { constructor(context, entry, name) { this.context = context; this.entry = entry; this.name = name; } apply(compiler) { // compilation 事件在初始化Compilation对象的时候被触发 compiler.plugin("compilation", (compilation, params) => { const normalModuleFactory = params.normalModuleFactory; compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory); }); // make 事件在执行compile的时候被触发 compiler.plugin("make", (compilation, callback) => { const dep = SingleEntryPlugin.createDependency(this.entry, this.name); // 编译的关键,调用Compilation中的addEntry,添加入口,进入编译过程。 compilation.addEntry(this.context, dep, this.name, callback); }); } static createDependency(entry, name) { const dep = new SingleEntryDependency(entry); dep.loc = name; return dep; } } module.exports = SingleEntryPlugin;
Compilation中负责具体编译的细节,包括如何创建模块以及模块的依赖,根据模板生成js等。如:addEntry,buildModule, processModuleDependencies等。
Compilation.js
addEntry(context, entry, name, callback) { const slot = { name: name, module: null }; this.preparedChunks.push(slot); // 添加该chunk上的module依赖 this._addModuleChain(context, entry, (module) => { entry.module = module; this.entries.push(module); module.issuer = null; }, (err, module) => { if(err) { return callback(err); } if(module) { slot.module = module; } else { const idx = this.preparedChunks.indexOf(slot); this.preparedChunks.splice(idx, 1); } return callback(null, module); }); }
_addModuleChain(context, dependency, onModule, callback) { const start = this.profile && Date.now(); ... // 根据模块的类型获取对应的模块工厂并创建模块 const moduleFactory = this.dependencyFactories.get(dependency.constructor); ... // 创建模块,将创建好的模块module作为参数传递给回调函数 moduleFactory.create({ contextInfo: { issuer: "", compiler: this.compiler.name }, context: context, dependencies: [dependency] }, (err, module) => { if(err) { return errorAndCallback(new EntryModuleNotFoundError(err)); } let afterFactory; if(this.profile) { if(!module.profile) { module.profile = {}; } afterFactory = Date.now(); module.profile.factory = afterFactory - start; } const result = this.addModule(module); if(!result) { module = this.getModule(module); onModule(module); if(this.profile) { const afterBuilding = Date.now(); module.profile.building = afterBuilding - afterFactory; } return callback(null, module); } if(result instanceof Module) { if(this.profile) { result.profile = module.profile; } module = result; onModule(module); moduleReady.call(this); return; } onModule(module); // 构建模块,包括调用loader处理文件,使用acorn生成AST,遍历AST收集依赖 this.buildModule(module, false, null, null, (err) => { if(err) { return errorAndCallback(err); } if(this.profile) { const afterBuilding = Date.now(); module.profile.building = afterBuilding - afterFactory; } // 开始处理收集好的依赖 moduleReady.call(this); }); function moduleReady() { this.processModuleDependencies(module, err => { if(err) { return callback(err); } return callback(null, module); }); } }); }
_addModuleChain 主要做了以下几件事情:
调用对应的模块工厂类去创建module
buildModule,开始构建模块,收集依赖。构建过程中最耗时的一步,主要完成了调用loader处理模块以及模块之间的依赖,使用acorn生成AST的过程,遍历AST循环收集并构建依赖模块的过程。此处可以深入了解webpack使用loader处理模块的原理。
Compilation的 seal 函数在 make 事件的回调函数中进行了调用。
seal(callback) { const self = this; // 触发seal事件,提供其他插件中seal的执行时机 self.applyPlugins0("seal"); self.nextFreeModuleIndex = 0; self.nextFreeModuleIndex2 = 0; self.preparedChunks.forEach(preparedChunk => { const module = preparedChunk.module; // 将module保存在chunk的origins中,origins保存了module的信息 const chunk = self.addChunk(preparedChunk.name, module); // 创建一个entrypoint const entrypoint = self.entrypoints[chunk.name] = new Entrypoint(chunk.name); // 将chunk创建的chunk保存在entrypoint中,并将该entrypoint的实例保存在chunk的entrypoints中 entrypoint.unshiftChunk(chunk); // 将module保存在chunk的_modules数组中 chunk.addModule(module); // module实例上记录chunk的信息 module.addChunk(chunk); // 定义该chunk的entryModule属性 chunk.entryModule = module; self.assignIndex(module); self.assignDepth(module); self.processDependenciesBlockForChunk(module, chunk); }); self.sortModules(self.modules); self.applyPlugins0("optimize"); while(self.applyPluginsBailResult1("optimize-modules-basic", self.modules) || self.applyPluginsBailResult1("optimize-modules", self.modules) || self.applyPluginsBailResult1("optimize-modules-advanced", self.modules)) { /* empty */ } self.applyPlugins1("after-optimize-modules", self.modules); while(self.applyPluginsBailResult1("optimize-chunks-basic", self.chunks) || self.applyPluginsBailResult1("optimize-chunks", self.chunks) || self.applyPluginsBailResult1("optimize-chunks-advanced", self.chunks)) { /* empty */ } self.applyPlugins1("after-optimize-chunks", self.chunks); self.applyPluginsAsyncSeries("optimize-tree", self.chunks, self.modules, function sealPart2(err) { if(err) { return callback(err); } self.applyPlugins2("after-optimize-tree", self.chunks, self.modules); while(self.applyPluginsBailResult("optimize-chunk-modules-basic", self.chunks, self.modules) || self.applyPluginsBailResult("optimize-chunk-modules", self.chunks, self.modules) || self.applyPluginsBailResult("optimize-chunk-modules-advanced", self.chunks, self.modules)) { /* empty */ } self.applyPlugins2("after-optimize-chunk-modules", self.chunks, self.modules); const shouldRecord = self.applyPluginsBailResult("should-record") !== false; self.applyPlugins2("revive-modules", self.modules, self.records); self.applyPlugins1("optimize-module-order", self.modules); self.applyPlugins1("advanced-optimize-module-order", self.modules); self.applyPlugins1("before-module-ids", self.modules); self.applyPlugins1("module-ids", self.modules); self.applyModuleIds(); self.applyPlugins1("optimize-module-ids", self.modules); self.applyPlugins1("after-optimize-module-ids", self.modules); self.sortItemsWithModuleIds(); self.applyPlugins2("revive-chunks", self.chunks, self.records); self.applyPlugins1("optimize-chunk-order", self.chunks); self.applyPlugins1("before-chunk-ids", self.chunks); self.applyChunkIds(); self.applyPlugins1("optimize-chunk-ids", self.chunks); self.applyPlugins1("after-optimize-chunk-ids", self.chunks); self.sortItemsWithChunkIds(); if(shouldRecord) self.applyPlugins2("record-modules", self.modules, self.records); if(shouldRecord) self.applyPlugins2("record-chunks", self.chunks, self.records); self.applyPlugins0("before-hash"); // 创建hash self.createHash(); self.applyPlugins0("after-hash"); if(shouldRecord) self.applyPlugins1("record-hash", self.records); self.applyPlugins0("before-module-assets"); self.createModuleAssets(); if(self.applyPluginsBailResult("should-generate-chunk-assets") !== false) { self.applyPlugins0("before-chunk-assets"); // 使用template创建最后的js代码 self.createChunkAssets(); } self.applyPlugins1("additional-chunk-assets", self.chunks); self.summarizeDependencies(); if(shouldRecord) self.applyPlugins2("record", self, self.records); self.applyPluginsAsync("additional-assets", err => { if(err) { return callback(err); } self.applyPluginsAsync("optimize-chunk-assets", self.chunks, err => { if(err) { return callback(err); } self.applyPlugins1("after-optimize-chunk-assets", self.chunks); self.applyPluginsAsync("optimize-assets", self.assets, err => { if(err) { return callback(err); } self.applyPlugins1("after-optimize-assets", self.assets); if(self.applyPluginsBailResult("need-additional-seal")) { self.unseal(); return self.seal(callback); } return self.applyPluginsAsync("after-seal", callback); }); }); }); }); }
在 seal 中可以发现,调用了很多不同的插件,主要就是操作chunk和module的一些插件,生成最后的源代码。其中 createHash 用来生成hash,createChunkAssets 用来生成chunk的源码,createModuleAssets 用来生成Module的源码。在 createChunkAssets 中判断了是否是入口chunk,入口的chunk用mainTemplate生成,否则用chunkTemplate生成。
在compiler中的 run 方法中定义了compile的回调函数 onCompiled, 在编译结束后,会调用该回调函数。在该回调函数中调用了 emitAsset,触发了 emit 事件,将文件写入到文件系统中的指定位置。
webpack的源码通过采用Tapable控制其事件流,并通过plugin机制,在webpack构建过程中将一些事件钩子暴露给plugin,使得开发者可以通过编写相应的插件来自定义打包。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in die Erstellung eines Webpacks. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!