当前位置: 首页 > 科技观察

Webpack原理与实践Webpack运行机制及核心工作原理

时间:2023-03-17 00:30:04 科技观察

写在前端Webpack在整个打包过程中:通过loader处理特殊类型资源的加载,比如加载样式、图片等,实现各种自动化构建通过插件完成任务,比如自动压缩,自动发布,那么webpack的工作流程和原理是如何实现的呢?webpack的工作过程首先加载入口文件js,通过分析代码中的import、require等来解析依赖,然后通过dependencies形成一个依赖树,webpack会遍历依赖树加载依赖的资源模块。webpack会通过Loader配置加载模块,通过插件实现自动化构建。对于依赖模块中无法用js代码表示的资源模块,比如图片或者字体文件,一般Loader会把它们作为资源文件单独复制到输出目录,然后使用资源文件对应的访问路径作为导出该模块的成员暴露于外界。webpack在每个打包环节都预留了hooks,我们可以通过plugins来配置它所依赖的插件。具体:Webpackcli开始打包过程,加载Webpack核心模块,创建Compiler对象,使用CreateCompiler对象开始编译整个项目,从入口文件开始,解析模块依赖,形成依赖树,遍历递归依赖树,将每个模块交给对应的模块。loader进程合并loader处理后的结果,将打包结果输出到dist目录。webpackcli的作用是将cli参数和webpack配置文件中的配置进行整合,得到一个完整的配置对象。webpackcli会通过yargs模块解析cli参数,运行webpack命令时通过命令行传入的参数。constconfig={options:{},path:newWeakMap()};//判断是否指定了配置文件if(options.config&&options.config.length>0){constloadedConfigs=awaitPromise.all(options.config.map((configPath)=>loadConfigByPath(path.resolve(configPath),options.argv),);config.options=[];loadedConfigs.forEach((loadedConfig)=>{constisArray=Array.isArray(loadedConfig.options);//TODOweshouldrunwebpackmultipletimeswhenthe`--config`optionshavemultiplevalueswith`--merge`,needtosolveforthenextmajorreleaseif(config.options.length===0){config.options=loadedConfig.options;}else{if(!Array.isArray(config.options)){config.options=[config.options];}if(isArray){loadedConfig.options.forEach((item)=>{config.options.push(item);});}else{config.options.push(loadedConfig.options);}}if(isArray){loadedConfig.options.forEach((options)=>{config.path.set(options,loadedConfig.path);});}else{config.path.set(loadedConfig.options,loadedConfig.path);}});config.options=config.options.length===1?config.options[0]:config.options;}else{//根据配置文件规则查找并加载配置文件//Orderdefinesthepriority,decreasingorderconstdefaultConfigFiles=["webpack.config",".webpack/webpack.config",".webpack/webpackfile",.map((filename)=>//Since.cjs不可用oninterpretsideadditmanuallytodefaultconfigextensionlist[...Object.keys(interpret.extensions),".cjs"].map((ext)=>({path:path.resolve(文件名+ext),ext:ext,module:interpret.extensions[ext],})),).reduce((accumulator,currentValue)=>accumulator.concat(currentValue),[]);letfoundDefaultConfigFile;for(constdefaultConfigFileofdefaultConfigFiles){if(!fs.existsSync(defaultConfigFile.path)){continue;}foundDefaultConfigFile=defaultConfigFile;break;}if(foundDefaultConfigFile){constloadedConfig=awaitloadConfigByPath(foundDefaultConfigFile.path,options.argv);config.options=loadedConfig.options;if(Array.isArray(config.options)){config.options.forEach((item)=>{config.path.set(item,loadedConfig.path);});}else{config.path.set(loadedConfig.options,loadedConfig.path);}}}开始加载webpack核心模块,传入配置选项,创建Compiler对象//创建Compiler对象的函数asynccreateCompiler(options,callback){if(typeofoptions.nodeEnv==="string"){process.env.NODE_ENV=options.nodeEnv;}letconfig=awaitthis.loadConfig(options);config=awaitthis.buildConfig(config,options);letcompiler;try{//开始调用webpack核心模块compiler=this.webpack(config.options,callback?(error,stats)=>{if(error&&this.isValidationError(error)){这个.logger.error(error.message);process.exit(2);}callback(error,stats);}:callback,);}catch(error){if(this.isValidationError(error)){this.logger.error(error.message);}else{this.logger.error(error);}process.exit(2);}//TODOwebpack@4returnWatchingandMultiWatchinginsteadCompilerandMultiCompiler,removethisafterdropwebpack@4if(compiler&&compiler.compiler){compiler=compiler.compiler;}returncompiler;}make阶段make阶段主体的目标是:根据入口配置找到入口模块,开始依次递归所有的依赖,形成依赖树,然后将每个递归的模块交给不同的加载程序进行处理。//多路封装if(Array.isArray(options)){awaitPromise.all(options.map(async(_,i)=>{if(typeofoptions[i].then==="function"){options[i]=awaitoptions[i];}//`Promise`可能返回`Function`if(typeofoptions[i]==="function"){//当配置为函数时,将envfromargs传递给configfunctionoptions[i]=awaitoptions[i](argv.env,argv);}}));}else{//单行打包if(typeofoptions.then==="function"){options=awaitoptions;}//`Promise`可能返回`Function`if(typeofoptions==="function"){//whenconfigisafunction,passtheenvfromargstotheconfigfunctionoptions=awaitoptions(argv.env,argv);}}默认是使用单入口的打包方式,所以这里会执行SingleEntryPlugin。在SingleEntryPlugin中调用Compilation对象的addEntry方法开始解析条目。在addEntry方法中调用_addModuleChain方法将入口模块添加到模块依赖列表中。然后使用Compilation对象的buildModule方法构建模块。在buildModule方法中,执行具体的Loader处理特殊资源的加载。构建完成后,使用acorn库生成模块代码的AST语法树,根据语法树分析模块是否有依赖模块。如果有,则继续循环构建各个依赖。解析所有依赖项。构建阶段完成后,将需要输出的bundle.js合并写入目录。参考文章《webpack原理与实践》《webpack中文文档》写在最后。本文主要讲解webpack的工作原理和工作原理,并分析了部分源码,源码相当于牛津词典,不能单独设定专门的时间阅读,而是要查找自己需要的,有目的的学习。