摘要:先上一張流程圖一般打包文件是通過(guò)調(diào)用這實(shí)際上等同于通過(guò)調(diào)用源碼如下將用戶本地的配置文件拼接上內(nèi)置的參數(shù)初始化對(duì)象編輯器對(duì)象,包含所有主環(huán)境相關(guān)內(nèi)容注冊(cè)插件和用戶配置的插件觸發(fā)和上注冊(cè)的事件注冊(cè)內(nèi)置插件源碼如下注冊(cè)觸發(fā)鉤子觸發(fā)鉤子觸發(fā)鉤子
先上一張流程圖
一般webpack打包文件是通過(guò)cli調(diào)用
webpack.js --config=webpack.build.js
這實(shí)際上等同于通過(guò)node調(diào)用
const Webpack = require("./node_modules/webpack"); const config = require("./config1.js"); const compiler = Webpack(config); compiler.run();
Webpack(config)源碼如下:
const webpack = (options, callback) => { //將用戶本地的配置文件拼接上webpack內(nèi)置的參數(shù) options = new WebpackOptionsDefaulter().process(options); //初始化compiler對(duì)象(webpack編輯器對(duì)象,包含所有webpack主環(huán)境相關(guān)內(nèi)容) compiler = new Compiler(options.context); compiler.options = options; //注冊(cè)NodeEnvironmentPlugin插件和用戶配置的插件 new NodeEnvironmentPlugin().apply(compiler); if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { plugin.apply(compiler); } } //觸發(fā)environment和afterEnvironment上注冊(cè)的事件 compiler.hooks.environment.call(); compiler.hooks.afterEnvironment.call(); //注冊(cè)webpack內(nèi)置插件,源碼如下 compiler.options = new WebpackOptionsApply().process(options, compiler); return compiler; }) class WebpackOptionsApply extends OptionsApply { process(options, compiler) { //注冊(cè)EntryOptionPlugin new EntryOptionPlugin().apply(compiler); //觸發(fā)entryOption鉤子 var a = compiler.hooks.entryOption.call(options.context, options.entry); //觸發(fā)afterPlugins鉤子 compiler.hooks.afterPlugins.call(compiler); //觸發(fā)afterResolvers鉤子 compiler.hooks.afterResolvers.call(compiler); } }
主要是初始化compiler對(duì)象和注冊(cè)插件,下面介紹下EntryOptionPlugin插件
EntryOptionPlugin.apply方法 apply(compiler) { //將回調(diào)函數(shù)注冊(cè)到hooks.entryOption上 //上文調(diào)用compiler.hooks.entryOption.call(options.context, options.entry)時(shí)觸發(fā) compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => { //取出entry文件入口配置,判斷是否數(shù)組,調(diào)用對(duì)應(yīng)的插件 for (const name of Object.keys(entry)) { itemToPlugin(context, entry[name], name).apply(compiler); } } } const itemToPlugin = (context, item, name) => { if (Array.isArray(item)) { return new MultiEntryPlugin(context, item, name); } return new SingleEntryPlugin(context, item, name); } //本文介紹entry[name]為字符串的情況,調(diào)用new SingleEntryPlugin().apply方法,源碼如下 apply(compiler) { //在compilation鉤子上注冊(cè)回調(diào),compilation.call時(shí)觸發(fā) compiler.hooks.compilation.tap( "SingleEntryPlugin", (compilation, { normalModuleFactory }) => { //設(shè)置SingleEntryDependency使用normalModuleFactory創(chuàng)建Module compilation.dependencyFactories.set( SingleEntryDependency, normalModuleFactory ); } ); compiler.hooks.make.tapAsync( "SingleEntryPlugin", (compilation, callback) => { const { entry, name, context } = this; const dep = SingleEntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, callback); } ); }
經(jīng)過(guò)上一步的分析可以對(duì)webpack的插件機(jī)制有一定的了解,插件主要是掛載一些回調(diào)函數(shù)在compiler的生命周期上,當(dāng)執(zhí)行到該階段時(shí)觸發(fā)(事件的發(fā)布訂閱,繼承自tapable)。
compiler的生命周期可參考:webpack hooks,下面再看下compiler.run()方法
run(callback) { this.compile(onCompiled); } compile(callback) { //初始化compilation,compilation對(duì)象代表了一次單一的版本構(gòu)建和生成資源過(guò)程 const compilation = this.newCompilation(params); // 觸發(fā)注冊(cè)在make上的事件函數(shù), this.hooks.make.callAsync(compilation, err => { //make上注冊(cè)的事件執(zhí)行完畢后觸發(fā)回調(diào),源碼后面給出 } } //觸發(fā)上文提到的SingleEntryPlugin注冊(cè)事件 compiler.hooks.make.tapAsync( "SingleEntryPlugin", (compilation, callback) => { const { entry, name, context } = this; // 入口文件的依賴對(duì)象, const dep = SingleEntryPlugin.createDependency(entry, name); compilation.addEntry(context, dep, name, callback); } ); addEntry(context, entry, name, callback) { this._addModuleChain(context, dep, ...) } _addModuleChain(context, dependency, onModule, callback) { //獲取dependency const Dep = /** @type {DepConstructor} */ (dependency.constructor); //獲取moduleFactory,根據(jù)上文的介紹此處是normalModuleFactory const moduleFactory = this.dependencyFactories.get(Dep); //獲取module moduleFactory.create((err, module) => { dependency.module = module; this.buildModule(module, false, null, null, err => { //初始化moudle后生成ast對(duì)象,計(jì)算依賴,后面介紹 }) ) } //獲取module的實(shí)現(xiàn) //normalModuleFactory.create create(data, callback) { // 獲取在constructor中注冊(cè)的factory方法 const factory = this.hooks.factory.call(null); factory(result, (err, module) => {}) } class NormalModuleFactory extends Tapable { constructor(context, resolverFactory, options) { this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => { //返回初始的module對(duì)象 callback(null, { context: context, request: loaders .map(loaderToIdent) .concat([resource]) .join("!"), dependencies: data.dependencies, ... }); } } }
buildModule回調(diào)
this.buildModule(module, false, null, null, err => { // 根據(jù)js代碼獲取ast語(yǔ)法樹(shù)對(duì)象 ast = acorn.parse(code, parserOptions); // 根據(jù)ast加載模塊的依賴 this.prewalkStatements(ast.body); this.walkStatements(ast.body);
make主要是以entry為入口,生成一個(gè)modoule對(duì)象,其中的關(guān)鍵是根據(jù)js代碼生成ast語(yǔ)法樹(shù)對(duì)象,同時(shí)分析語(yǔ)法樹(shù)加載需要使用到的依賴(dependency),如果存在import依賴,就會(huì)生成新的modoule,知道所有依賴加在完畢,下圖是部分dependency示例
make階段完成之后會(huì)進(jìn)入seal階段
this.hooks.make.callAsync(compilation, err => { compilation.seal(err => {}) }) seal() { for (const preparedEntrypoint of this._preparedEntrypoints) { const module = preparedEntrypoint.module; const name = preparedEntrypoint.name; const chunk = this.addChunk(name); chunk.entryModule = module; } this.createChunkAssets(); } createChunkAssets(){ const manifest = template.getRenderManifest({ chunk, hash: this.hash, fullHash: this.fullHash, outputOptions, moduleTemplates: this.moduleTemplates, dependencyTemplates: this.dependencyTemplates }); // [{ render(), filenameTemplate, pathOptions, identifier, hash }] for (const fileManifest of manifest) { source = fileManifest.render(); } }
compile結(jié)束后調(diào)用compiler.emitAssets
emitAssets() { const targetPath = this.outputFileSystem.join( outputPath, targetFile ); let content = source.source(); //this.writeFile = fs.writeFile.bind(fs); this.outputFileSystem.writeFile(targetPath, content, callback); }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/96681.html
摘要:引言通過(guò)前面幾張的鋪墊下面開(kāi)始分析源碼核心流程大體上可以分為初始化編譯輸出三個(gè)階段下面開(kāi)始分析初始化這個(gè)階段整體流程做了什么啟動(dòng)構(gòu)建,讀取與合并配置參數(shù),加載,實(shí)例化。推薦源碼之源碼之機(jī)制源碼之簡(jiǎn)介源碼之機(jī)制參考源碼 引言 通過(guò)前面幾張的鋪墊,下面開(kāi)始分析webpack源碼核心流程,大體上可以分為初始化,編譯,輸出三個(gè)階段,下面開(kāi)始分析 初始化 這個(gè)階段整體流程做了什么? 啟動(dòng)構(gòu)建,讀...
摘要:正所謂四兩撥千斤,找對(duì)要分析的對(duì)象以及它的關(guān)系網(wǎng),就找到了正確的分析源碼的方法下面的是我的公眾號(hào)二維碼圖片,歡迎關(guān)注。 1、如何調(diào)試閱讀源碼 如果想要了解 Webpack 的流程,只要閱讀 @七玨 細(xì)說(shuō) webpack 之流程篇 所述的內(nèi)容就夠了,講解地比較全面了;本文就不對(duì) Webpack 流程再做重復(fù)的描述,而是從另外一個(gè)角度補(bǔ)充分析 Webpack 源碼; Webpack 中最為...
摘要:不直接使用的原因很簡(jiǎn)單首先構(gòu)建一次實(shí)在太慢了,特別是有幾十個(gè)頁(yè)面存在的情況下,另一個(gè)原因是我只是想拿到資源依賴,我根本不想對(duì)整個(gè)前端進(jìn)行一次構(gòu)建,也不想生成任何。這就達(dá)到了本文題目中目的,用十分之一的構(gòu)建時(shí)間做一場(chǎng)頁(yè)面靜態(tài)資源依賴分析。原文鏈接 作者:梯田 前言: 所謂【靜態(tài)資源依賴分析】,指的是可以通過(guò)分析頁(yè)面資源后,可以以 json 數(shù)據(jù)或者圖表的方式拿到頁(yè)面資源間的依賴關(guān)系。 比如 c...
摘要:流程劃分縱觀整個(gè)打包過(guò)程,可以流程劃分為四塊。核心類關(guān)系圖功能實(shí)現(xiàn)模塊通過(guò)將源碼解析為樹(shù)并拆分,以及直至基礎(chǔ)模塊。通過(guò)的依賴和切割文件構(gòu)建出含有和包含關(guān)系的對(duì)象。通過(guò)模版完成主入口文件的寫入,模版完成切割文件的寫入。 前言 插件plugin,webpack重要的組成部分。它以事件流的方式讓用戶可以直接接觸到webpack的整個(gè)編譯過(guò)程。plugin在編譯的關(guān)鍵地方觸發(fā)對(duì)應(yīng)的事件,極大的...
閱讀 1130·2021-11-16 11:42
閱讀 2910·2021-10-12 10:18
閱讀 2868·2021-09-24 09:48
閱讀 3471·2019-08-30 15:56
閱讀 1536·2019-08-30 14:17
閱讀 3052·2019-08-29 12:14
閱讀 914·2019-08-27 10:51
閱讀 2032·2019-08-26 13:28