摘要:先上一張流程圖一般打包文件是通過調用這實際上等同于通過調用源碼如下將用戶本地的配置文件拼接上內置的參數初始化對象編輯器對象,包含所有主環境相關內容注冊插件和用戶配置的插件觸發和上注冊的事件注冊內置插件源碼如下注冊觸發鉤子觸發鉤子觸發鉤子
先上一張流程圖
一般webpack打包文件是通過cli調用
webpack.js --config=webpack.build.js
這實際上等同于通過node調用
const Webpack = require("./node_modules/webpack"); const config = require("./config1.js"); const compiler = Webpack(config); compiler.run();
Webpack(config)源碼如下:
const webpack = (options, callback) => { //將用戶本地的配置文件拼接上webpack內置的參數 options = new WebpackOptionsDefaulter().process(options); //初始化compiler對象(webpack編輯器對象,包含所有webpack主環境相關內容) compiler = new Compiler(options.context); compiler.options = options; //注冊NodeEnvironmentPlugin插件和用戶配置的插件 new NodeEnvironmentPlugin().apply(compiler); if (options.plugins && Array.isArray(options.plugins)) { for (const plugin of options.plugins) { plugin.apply(compiler); } } //觸發environment和afterEnvironment上注冊的事件 compiler.hooks.environment.call(); compiler.hooks.afterEnvironment.call(); //注冊webpack內置插件,源碼如下 compiler.options = new WebpackOptionsApply().process(options, compiler); return compiler; }) class WebpackOptionsApply extends OptionsApply { process(options, compiler) { //注冊EntryOptionPlugin new EntryOptionPlugin().apply(compiler); //觸發entryOption鉤子 var a = compiler.hooks.entryOption.call(options.context, options.entry); //觸發afterPlugins鉤子 compiler.hooks.afterPlugins.call(compiler); //觸發afterResolvers鉤子 compiler.hooks.afterResolvers.call(compiler); } }
主要是初始化compiler對象和注冊插件,下面介紹下EntryOptionPlugin插件
EntryOptionPlugin.apply方法 apply(compiler) { //將回調函數注冊到hooks.entryOption上 //上文調用compiler.hooks.entryOption.call(options.context, options.entry)時觸發 compiler.hooks.entryOption.tap("EntryOptionPlugin", (context, entry) => { //取出entry文件入口配置,判斷是否數組,調用對應的插件 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]為字符串的情況,調用new SingleEntryPlugin().apply方法,源碼如下 apply(compiler) { //在compilation鉤子上注冊回調,compilation.call時觸發 compiler.hooks.compilation.tap( "SingleEntryPlugin", (compilation, { normalModuleFactory }) => { //設置SingleEntryDependency使用normalModuleFactory創建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); } ); }
經過上一步的分析可以對webpack的插件機制有一定的了解,插件主要是掛載一些回調函數在compiler的生命周期上,當執行到該階段時觸發(事件的發布訂閱,繼承自tapable)。
compiler的生命周期可參考:webpack hooks,下面再看下compiler.run()方法
run(callback) { this.compile(onCompiled); } compile(callback) { //初始化compilation,compilation對象代表了一次單一的版本構建和生成資源過程 const compilation = this.newCompilation(params); // 觸發注冊在make上的事件函數, this.hooks.make.callAsync(compilation, err => { //make上注冊的事件執行完畢后觸發回調,源碼后面給出 } } //觸發上文提到的SingleEntryPlugin注冊事件 compiler.hooks.make.tapAsync( "SingleEntryPlugin", (compilation, callback) => { const { entry, name, context } = this; // 入口文件的依賴對象, 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,根據上文的介紹此處是normalModuleFactory const moduleFactory = this.dependencyFactories.get(Dep); //獲取module moduleFactory.create((err, module) => { dependency.module = module; this.buildModule(module, false, null, null, err => { //初始化moudle后生成ast對象,計算依賴,后面介紹 }) ) } //獲取module的實現 //normalModuleFactory.create create(data, callback) { // 獲取在constructor中注冊的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對象 callback(null, { context: context, request: loaders .map(loaderToIdent) .concat([resource]) .join("!"), dependencies: data.dependencies, ... }); } } }
buildModule回調
this.buildModule(module, false, null, null, err => { // 根據js代碼獲取ast語法樹對象 ast = acorn.parse(code, parserOptions); // 根據ast加載模塊的依賴 this.prewalkStatements(ast.body); this.walkStatements(ast.body);
make主要是以entry為入口,生成一個modoule對象,其中的關鍵是根據js代碼生成ast語法樹對象,同時分析語法樹加載需要使用到的依賴(dependency),如果存在import依賴,就會生成新的modoule,知道所有依賴加在完畢,下圖是部分dependency示例
make階段完成之后會進入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結束后調用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); }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/96681.html
摘要:引言通過前面幾張的鋪墊下面開始分析源碼核心流程大體上可以分為初始化編譯輸出三個階段下面開始分析初始化這個階段整體流程做了什么啟動構建,讀取與合并配置參數,加載,實例化。推薦源碼之源碼之機制源碼之簡介源碼之機制參考源碼 引言 通過前面幾張的鋪墊,下面開始分析webpack源碼核心流程,大體上可以分為初始化,編譯,輸出三個階段,下面開始分析 初始化 這個階段整體流程做了什么? 啟動構建,讀...
摘要:正所謂四兩撥千斤,找對要分析的對象以及它的關系網,就找到了正確的分析源碼的方法下面的是我的公眾號二維碼圖片,歡迎關注。 1、如何調試閱讀源碼 如果想要了解 Webpack 的流程,只要閱讀 @七玨 細說 webpack 之流程篇 所述的內容就夠了,講解地比較全面了;本文就不對 Webpack 流程再做重復的描述,而是從另外一個角度補充分析 Webpack 源碼; Webpack 中最為...
摘要:不直接使用的原因很簡單首先構建一次實在太慢了,特別是有幾十個頁面存在的情況下,另一個原因是我只是想拿到資源依賴,我根本不想對整個前端進行一次構建,也不想生成任何。這就達到了本文題目中目的,用十分之一的構建時間做一場頁面靜態資源依賴分析。原文鏈接 作者:梯田 前言: 所謂【靜態資源依賴分析】,指的是可以通過分析頁面資源后,可以以 json 數據或者圖表的方式拿到頁面資源間的依賴關系。 比如 c...
摘要:流程劃分縱觀整個打包過程,可以流程劃分為四塊。核心類關系圖功能實現模塊通過將源碼解析為樹并拆分,以及直至基礎模塊。通過的依賴和切割文件構建出含有和包含關系的對象。通過模版完成主入口文件的寫入,模版完成切割文件的寫入。 前言 插件plugin,webpack重要的組成部分。它以事件流的方式讓用戶可以直接接觸到webpack的整個編譯過程。plugin在編譯的關鍵地方觸發對應的事件,極大的...
閱讀 1115·2021-11-16 11:42
閱讀 2895·2021-10-12 10:18
閱讀 2854·2021-09-24 09:48
閱讀 3457·2019-08-30 15:56
閱讀 1523·2019-08-30 14:17
閱讀 3036·2019-08-29 12:14
閱讀 902·2019-08-27 10:51
閱讀 2020·2019-08-26 13:28