当前位置: 首页 > 后端技术 > Node.js

webpack源码的运行流程

时间:2023-04-03 20:50:56 Node.js

介绍通过前面几章的铺垫,我们开始分析webpack源码的核心流程,大致可以分为初始化、编译、输出三个阶段。下面开始分析这个阶段初始化的整体流程做了什么。开始构建,读取并合并配置参数,加载Plugin,实例化Compiler。详细分析//通过yargs获取shell中的参数yargs.parse(process.argv.slice(2),(err,argv,output)=>{//将webpack.config.js中的参数和shell参数整合到在选项对象上设置选项;options=require("./convert-argv")(argv);functionprocessOptions(options){constfirstOptions=[].concat(options)[0];constwebpack=require("webpack");letcompiler;//通过webpack方法创建compile对象,Compiler负责文件监听和启动编译。//Compiler实例包含一个完整的Webpack配置,全局只有一个Compiler实例compiler=webpack(options);if(firstOptions.watch||options.watch){compiler.watch(watchOptions,compilerCallback);//开始新的编译。}elsecompiler.run(compilerCallback);}processOptions(选项);});表示第一步初始化是从源码中抽取出来并简化的。运行webpack命令的时候,是运行webpack-cli下载webpack.js,它的内容是一个自执行函数,上面是执行的第一步,解析合并参数,创建编译实例,然后开始编译并运行run方法,其中关键步骤compiler=webpack(options);详细扩展如下constwebpack=(options,callback)=>{//参数校验constwebpackOptionsValidationErrors=validateSchema(webpackOptionsSchema,options);让编译器;if(Array.isArray(options)){compiler=newMultiCompiler(options.map(options=>webpack(options)));}elseif(typeofoptions==="object"){options=newWebpackOptionsDefaulter().process(options);//创建编译器对象compiler=newCompiler(options.context);compiler.options=选项;新的NodeEnvironmentPlugin().apply(编译器);//在配置文件中注册插件,依次调用插件的apply方法,让插件监听后续所有的事件节点。同时将编译器实例的引用传递给插件,使插件可以通过编译器调用Webpack提供的API。if(options.plugins&&Array.isArray(options.plugins)){for(constpluginofoptions.plugins){plugin.apply(compiler);}}//开始对编译器对象应用Node.js风格的文件系统,方便后续的文件查找和读取。编译器.hooks.environment.call();编译器.hooks.afterEnvironment.call();//注册内部插件compiler.options=newWebpackOptionsApply().process(options,compiler);}返回编译器;};这个过程没有展开。webpack内置的插件真的很多。整个过程在编译的这个阶段做了什么?从Entry开始,依次为每个Module调用对应的Loader翻译文件内容,然后找到该Module所依赖的Module,递归进行Compile处理。详细分析this.hooks.beforeRun.callAsync(this,err=>{if(err)returnfinalCallback(err);this.hooks.run.callAsync(this,err=>{if(err)returnfinalCallback(err);this.readRecords(err=>{if(err)returnfinalCallback(err);this.compile(onCompiled);});});});表示编译过程从执行run方法开始,run方法触发Before-run,运行两个事件,然后通过readRecords读取文件,通过compile打包。在这个方法中,一个编译类compile(callback){constparams=this.newCompilationParams();this.hooks.before编译。callAsync(params,err=>{if(err)returncallback(err);this.hooks.compile.call(params);//每次编译都会创建一个编译对象(比如watch文件改变了,就会执行),但是compile只会创建一个constcompilation=this.newCompilation(params);//makeevent触发事件,触发SingleEntryPlugin监听函数,调用compilation.addEntry方法this.hooks.make.callAsync(编译,报错=>{如果(错误)返回回调(错误);});});}表示打包时触发before-compile、compile、make等事件,同时创建一个非常重要的编译对象。里面声明了很多hook,初始化Templates等this.hooks={buildModule:newSyncHook(["module"]),seal:newSyncHook([]),optimize:newSyncHook([]),};//拼接最终生成代码的主模板将使用this.mainTemplate=newMainTemplate(this.outputOptions);//拼接最终生成代码的chunk模板将使用this.chunkTemplate=newChunkTemplate(this.outputOptions);//拼接最终生成代码的热更新模板会使用Gotothis.hotUpdateChunkTemplate=newHotUpdateChunkTemplate()//监听comple的makehooks事件,通过内部的SingleEntryPlugincompiler.hooks.make从入口文件开始编译。tapAsync("SingleEntryPlugin",(compilation,callback)=>{const{entry,name,context}=this;constdep=SingleEntryPlugin.createDependency(entry,name);compilation.addEntry(context,dep,name,callback);});表示监听compile的makehooks事件,通过内部SingleEntryPlugin从入口文件开始编译,调用compilation.addEntry方法,获取对应的模块工厂并根据模块类型创建模块,开始构建模块doBuild(options,compilation,resolver,fs,callback){constloaderContext=this.createLoaderContext(解析器、选项、编译、fs);//调用加载器处理模块runLoaders({resource:this.resource,loaders:this.loaders,context:loaderContext,readResource:fs.readFile.bind(fs)},(err,result)=>{constresourceBuffer=result.resourceBuffer;constsource=result.result[0];constsourceMap=result.result.length>=1?result.result[1]:null;constextraInfo=result.result.length>=2?result.result[2]:null;this._source=this.createSource(this.binary?asBuffer(source):asString(source),resourceBuffer,sourceMap);//loader处理后得到_source然后AST处理this._ast=typeofextraInfo==="object"&&extraInfo!==null&&extraInfo.webpackAST!==undefined?extraInfo.webpackAST:null;返回回调();});}说明内存插件SingleEntryPlugin的主要功能是从入口读取文件,根据文件类型和配置的Loader执行runLoaders,然后将loader处理的文件抽象成抽象语法通过橡子树AST,并遍历AST。在构建模块所有依赖的这个阶段,整个过程是做什么的?将编译好的Modules组合成Chunks,将Chunks转换成文件,输出到文件系统。详细分析//所有依赖构建完成,开始优化chunk(提取公共模块,添加hash等)compilation.seal(err=>{if(err)returncallback(err);this.hooks.afterCompile.callAsync(compilation,err=>{if(err)returncallback(err);returncallback(null,compilation);});});表示compilation.seal主要是优化chunk,生成编译后的源码,比较重要和详细展开如下//优化前的代码生成this.hooks.optimize.call();this.hooks.optimizeTree.callAsync(this.chunks,this.modules,err=>{this.hooks.beforeHash.call();this.createHash();this.hooks.afterHash.call();if(shouldRecord)this.hooks.recordHash.call(this.records);this.hooks.beforeModuleAssets.call();this.createModuleAssets();if(this.hooks.shouldGenerateChunkAssets.call()!==false){this.hooks.beforeChunkAssets.call();//生成最终打包输出chunk资源,根据模板文件,详细步骤如下this.createChunkAssets();}});----------------------------------//删除最后一个需要的文件templateconsttemplate=chunk.hasRuntime()?你s.mainTemplate:this.chunkTemplate;//通过模板,最终生成webpack_require格式的内容。这是内部包的拼接渲染逻辑,不需要ejs、handlebar等模板工具source=fileManifest.render();//生成的资源保存在compilation.assets中,即方便下一步emitAssets输出文件到硬盘this.assets[file]=source;//将处理后的资源输出到输出路径emitAssets(compilation,callback){letoutputPath;constemitFiles=err=>{如果(错误)返回回调(错误);asyncLib.forEach(compilation.assets,(source,file,callback)=>{constwriteOut=err=>{//输出后打包文件到配置中指定的目录this.outputFileSystem.writeFile(targetPath,content,回调);};writeOut();});};this.hooks.emit.callAsync(compilation,err=>{if(err)returncallback(err);outputPath=compilation.getPath(这个.outputPath);this.outputFileSystem.mkdirp(outputPath,emitFiles);});}总结如果单独看这篇文章,会比较难理解。我推荐一系列相关文章。以上就是我对webpack源码运行过程的总结总结,整个过程已经跑通了,但是还是有不少值得挖掘的地方。清明在家呆了3天,过得好快。公司明天将在奥森公园组织寻宝活动,期待ing。推荐webpack源码tapablewebpack源码插件机制webpack源码ast介绍webpack源码loader机制参考源码webpack:"4.4.1"webpack-cli:"2.0.13"