Webpack相关知识点
webpack的优点
webpack从配置的入口出发,可以打包所有前端资源,同时可以配置多种loader来处理不同类型文件的转换,并且可以配置plugin来扩展模块打包流程,满足更多构建中特殊的需求,开发过程中还可以使用HMR提升本地开发效率和体验,生产环境中可以利用代码压缩和代码分割来提升前端加载性能,总之就是既可以提升开发效率,又可以提升应用性能。
webpack中loader和plugin的区别
webpack中loader主要用于处理非JavaScript模块的转换,在固定的阶段中使用,本身只是一个函数,返回转换后的结果,因为webpack只能处理js;而plugin是用于处理特殊的构建需求,比如将css代码单独输出为一个文件、定义环境中的变量等,利用了webpack的hooks介入构建过程中不同的阶段,可以定制项目的构建流程。
开始编译,读取webpack配置,创建NormalModuleFactory,创建NormalModule(使用resolveLoader解析loader路径),开始编译模块(loader-runner)
如何利用webpack提升应用性能
webpack优化性能大概有三种方式:
代码压缩、代码分割、去除无用代码,主要的方向是减少请求体积,代码分割有的情况下还可以利用浏览器缓存,减少请求次数
- 代码压缩(主要就减少请求体积)
我们可以对optimization下的minimize和minimizer配置项进行设置,配置TerserPlugin对js代码进行压缩处理,还可以使用插件如HTMLWebpackPlugin对html文件进行压缩。 - 代码分割(既减少一次的请求体积,使请求返回更快,有的情况还可以利用浏览器缓存,减少请求次数)
我了解的大概几种方式,第一种是设置optimization下的splitChunks配置项、第二种css代码利用MiniCssExtractPlugin插件单独生成文件、第三是利用webpack的dllPlugin插件将第三方库打包成独立的文件。
splitChunks可以将模块抽离为单独的文件,chunks可以设置为all/async/inital三个值的其中一个,all就是所有的模块都打包到一个chunk,async是把异步加载的模块打包到chunk中,如通过动态import加载的模块,initial是把同步加载的模块打包到chunk中。可以配合按需加载使用,按需加载使用ES动态加载语法import来加载模块,webpack会自动处理使用这种语法编写的模块,把模块单独分离成文件,可以减少大型应用初始化时需要加载的前端资源,提升用户体验。
css代码利用plugin插件生成单独的文件,可以在后续请求中,利用浏览器缓存,如果是多页面应用,优点更明显,不用多次加载css。
dllPlugin也是将部分模块抽离成单独的文件,但是它和splitChunks不同,在代码不变的情况下不用重复打包,可以单独写一个配置文件,运行打包,在后续项目的构建流程直接使用DLLReferencePlugin引用文件就可以,通常可以用来处理不太变动的第三方库,可以看出来这样不仅可以利用浏览器缓存,还能提高开发效率。 - 去除无用代码(减少请求体积)
可以利用tree-shaking、还有sideEffects,将未使用的代码移除。
要利用到tree-shaking就要使用分别导入、而不是整体导入,未引入和未使用的模块方法就不会被打包,sideEffects对没有副作用的模块也可以将模块中未使用到的代码不进行打包。
还有webpack的IgnorePlugin插件,可以在打包中忽略掉某些依赖包中体积大但又不太需要的文件,如react脚手架生成的项目中就默认将moment的locale文件夹整个忽略掉,这个语言包会非常大,但是通常实际中不需要这些多语言的配置,单独引入需要的语言就可以。
webpack的热更新原理
热更新HMR就是不用刷新页面而将新变更的模块替换掉旧的模块,避免了频繁手动刷新页面、应用状态丢失,也减少了页面刷新时的等待。它的核心是客户端去服务端拉取更新后的文件。在DevServer开启hot后,webpack会往应用代码中添加websocket相关的代码,用于和服务器保持连接,等待更新动作,本地代码变更时通知浏览器做相应的处理;webpack还会往应用代码中添加HMR运行时的代码,用于定义应用更新时的API。当有更新时,webpack-dev-server发送更新信号给HMR运行时,然后HMR再请求所需要的更新数据,服务端返回一个json包含所有要更新的模块的hash值,对应模块再次请求获取到最新的模块代码,没有问题的话就进行应用更新。
应用更新的API常见的有module.hot.accept、module.hot.decline、module.hot.dispose等,accept是在应用特定代码模块更新时执行相应的callback;decline是对于指定的代码模块,拒绝进行更新;dispose用于添加一个处理函数,在模块代码被替换时运行(可用于移除之前添加的持久化资源或者相关状态)。
如何优化webpack的构建速度
总的来说方向就是减少webpack的工作量。
- 首先在开发环境下使用配置mode为develpment,webpack本身就默认不配置一些压缩优化的插件,这样可以减少在优化操作上的时间消耗;
- 其次提前处理一些文件资源,如使用imagemin或者其他工具提前压缩好图片,可以减少在图片处理上的耗时,一些不频繁更新的第三方库使用dllPlugin打包,在项目中直接引用,不打包到应用代码中,也可以大大减少构建所用时间;
- 还可以通过配置resolve,设置适合的extensions、modules、mainFields、mainFiles的值,减少模块解析时路径的查询范围,配置module的rules使用loader处理不同的文件时,通过include和exclude限制处理范围,减少耗时;
- 另外还可以使用thread-loader利用多进程加速loader执行,利用tree-shaking减少webpack处理打包的代码量,利用缓存提升二次构建速度,像babel-loader、terser-webpack-plugin都可以开启缓存。
- 生产环境还可以配置devtool不输出sourcemap。
- 如果项目非常大,涉及代码模块过多,在合适的情况下也可以根据一定的粒度,把不同的业务代码拆分到不同的代码库去维护和管理,减少webpack处理的代码量。
如何开发一个webpack的plugin
plugin的实现可以是一个类或者函数,使用时传入相关配置来创建一个实例,plugin实例最重要的方法就是apply,在webpack compiler安装插件时会被调用,接收webpack的compiler对象实例的引用作为参数,在compiler对象实例上我们可以注册各种事件钩子函数hooks,在compiler的有些hooks中,还可以获取到compilation对象实例,在compilation对象实例上注册各种hooks,我们可以通过注册各种hooks来影响webpack的所有构建流程,以便完成更多其他构建任务。
hooks可以简单分为同步和异步两种,同步类型的hooks只能使用tap来注册事件,异步的还可以使用tapPromise和tapAsync来注册。
本地开发plugin,可以使自定义plugin对外暴露一个类,然后在webpack配置文件中引入,运行webpack构建查看结果就可以,可以使用node命令进行调试。
compiler一些hooks:entryOption、beforeRun、emit、compilation、thisCompilation、make(compilation完成编译后执行)、shouldEmit(控制是否输出对应的构建结果)、assetEmitted(在构建结果输出之后执行,可以获取输出内容的相关信息)、done、failed(构建失败时执行)
compilation一些hooks:buildModule、finishModules、chunkAsset(chunk 对应的一个输出资源添加到compilation时执行)、processAssets
如何开发一个webpack的loader
webpack loader本质就是一个实现转换功能的函数,接收content、map、meta三个参数。content就是要进行转化的资源内容,可以是字符串或者buffer,如图片、字体等文件,map是sourcemap对象。
通常webpack loader都是基于一个实现核心功能的类库来开发的,如果直接return一个值,这个值就是转换后的内容,如果要返回sourcemap对象或者其他数据,或是抛出一个异常,需要使用this.callback(err, content, map, meta)来传递这些数据;有些loader在执行过程中可能依赖外部I/O的结果,就需要使用异步的方式来处理,在loader执行时调用this.async()来标识该loader是异步处理的,this.async()会返回一个函数,然后我们可以调用这个函数用于返回loader的处理结果。
使用本地开发的loader有两种方式:一种是配置loader时使用本地的路径;另一种是在loader路径解析中加入本地开发loader的目录,具体是在loader所在目录下添加package.json文件并配置name字段,然后配置resolveLoader的modules将loader所在目录的路径添加进去,并在配置loader时使用package.json中name的值,这种比较适合多个loader的情况。
官方提供的一个工具库loader-utils可以帮助我们获取给loader传递的options,我们还可以使用官方提供的schema-utils对传入的options进行校验。
本文来自博客园,作者:beckyye,转载请注明原文链接:https://www.cnblogs.com/beckyyyy/p/16824573.html