目前,幾乎所有業務的開發建置都會用到 webpack 。所以下面這篇文章主要給大家介紹了關於webpack構建的詳細流程的相關資料,文中透過範例程式碼介紹的非常詳細,對大家的學習或工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
作為模組加載和打包神器,只需配置幾個文件,加載各種 loader 就可以享受無痛流程化開發。但對於 webpack 這樣一個複雜度較高的插件集合,它的整體流程及想法對我們來說還是很透明的。
本文旨在搞清楚從命令列下敲下webpack 命令,或者配置npm script 後執行package.json 中的命令,到工程目錄下出現打包的後的bundle 文件的過程中,webpack都替我們做了哪些工作。
測試用webpack版本為webpack@3.4.1
webpack.config.js中定義好相關配置,包括entry、output、module、plugins等,指令列執行webpack 指令,webpack便會根據設定檔中的配置進行打包處理文件,並產生最後打包後的文件。
第一步:執行 webpack 指令時,發生了什麼事? (bin/webpack.js)
命令列執行 webpack 時,如果全域命令列中找不到webpack命令的話,執行本地的node-modules/bin/webpack.js 檔案。
在bin/webpack.js中使用 yargs函式庫 解析了命令列的參數,處理了 webpack 的設定物件 options,呼叫 processOptions()
函式。
// 处理编译相关,核心函数 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); }
第二步: 呼叫webpack,傳回compiler 物件的過程(lib/webpack.js)
如下圖所示,lib/webpack.js 中的關鍵函數為webpack,其中定義了編譯相關的一些操作。
"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 = {}, {...});
接下來看在webpack函數中主要定義了哪些操作
// 核心方法,调用该方法,返回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; }
#webpack函數中主要做了以下兩個操作,
實例化Compiler 類別。這類繼承自 Tapable 類,Tapable 是一個基於發布訂閱的插件架構。 webpack 就是基於Tapable的發布訂閱模式實現的整個流程。 Tapable 中透過 plugins 註冊插件名,以及對應的回呼函數,透過 apply,applyPlugins,applyPluginsWater,applyPluginsAsync等函數以不同的方式呼叫註冊在某一插件下的回調。
透過WebpackOptionsApply 處理webpack compiler對象,透過
compiler.apply
的方式呼叫了一些必備插件,在這些插件中,註冊了一些plugins,在後面的編譯過程中,透過呼叫一些插件的方式,去處理一些流程。
第三步:呼叫compiler的run的過程(Compiler.js)
run()呼叫
run函數中主要觸發了before-run事件,在before-run事件的回呼函數中觸發了run事件,run事件中呼叫了readRecord函數讀取文件,並呼叫compile()
函數進行編譯。
compile()呼叫
##compile函數中定義了編譯的相關流程,主要有以下流程:- 創建編譯參數
- 觸發before-compile 事件,
- #觸發compile 事件,開始編譯
#建立compilation對象,負責整個編譯過程中具體細節的對象
觸發make 事件,開始建立模組和分析其依賴
根據入口配置的類型,決定是呼叫哪個plugin中的make 事件的回呼。如單入口的 entry,呼叫的是SingleEntryPlugin.js下 make 事件註冊的回呼函數,其他多入口同理。
呼叫 compilation 物件的 addEntry 函數,建立模組以及依賴關係。
make 事件的回呼函數中,透過seal 封裝建構的結果
###run 方法中定義的onCompiled回呼函數被調用,完成emit過程,將結果寫入目標檔案############compile函數的定義###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); }); }); }); }); }######【問題】make 事件觸發後,有哪些外掛程式中註冊了make事件並得到了運作的機會呢? #########以單入口entry配置為例,在EntryOptionPlugin插件中定義了,不同配置的入口應該調用何種插件進行解析。在不同配置的入口插件中註冊了對應的 make 事件回呼函數,在make事件觸發後被呼叫。 ######如下所示:#########一個插件的apply方法是一個插件的核心方法,當說一個插件被呼叫時主要是其apply方法被呼叫。 #########EntryOptionPlugin 插件在webpackOptionsApply中被調用,其內部定義了使用何種插件來解析入口檔案。 ###
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; }); } };###entry-option 事件被觸發時,EntryOptionPlugin 外掛做了這幾個事情:######判斷入口的類型,透過entry 欄位來判斷,對應了entry 欄位為string object function的三種情況######每種不同的類型呼叫不同的插件去處理入口的配置。大致處理邏輯如下:###
数组类型的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处理模块的原理。
第四步:模块build完成后,使用seal进行module和chunk的一些处理,包括合并、拆分等。
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生成。
第五步:通过 emitAssets 将生成的代码输入到output的指定位置
在compiler中的 run 方法中定义了compile的回调函数 onCompiled, 在编译结束后,会调用该回调函数。在该回调函数中调用了 emitAsset,触发了 emit 事件,将文件写入到文件系统中的指定位置。
总结
webpack的源码通过采用Tapable控制其事件流,并通过plugin机制,在webpack构建过程中将一些事件钩子暴露给plugin,使得开发者可以通过编写相应的插件来自定义打包。
上面是我整理给大家的,希望今后会对大家有帮助。
相关文章:
以上是建構webpack的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3漢化版
中文版,非常好用

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

Atom編輯器mac版下載
最受歡迎的的開源編輯器

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!