青训营X豆包MarsCode 技术训练营第四课 | 豆包MarsCode AI 刷题

55 阅读10分钟

第四篇伴学笔记~ 今天的内容是webpack工具的使用以及性能优化,属于方向三

webpack构建流程:

webpack构建从开始到结束是一个串行的过程,主要经历以下几个步骤,

1、初始化参数,读取webpack.config.js配置文件以及从shell语句中获取到一些必要的参数,融合配置,最终会得到一个初始化参数

2、将得到的初始化参数去初始化compiler这个对象,然后将所需要的webpack插件进行实例化,最后执行我们对象的run方法准备开始编译

3、开始编译,从配置文件中找到entry入口

4、从entry入口出发进行一个依赖收集,使用loaders进行模块文件的编译,并将模块id保存在dependencies的数组里,将模块导入导出语法替换成_webpack_require,保存转换后的模块代码,找到该模块所依赖的模块递归这个过程,直到所有入口依赖的文件都被编译收集到,最终生成一个AST抽象语法树

5、在第四步完成之后chunk会生成一个模块列表,列表包含模块之内的依赖关系,包含模块id(就是文件名),和模块转换的代码,也就是后面立即执行函数中参数module的key与value

6、根据输出路径和文件名把每个chunk转换成单一的文件bundle,(bundle实际上就是一个立即执行函数),加载到我们的输出列表中,写入文件系统

7、生成Source map提供构建报告

8、runtime监视文件变化,实现热模块(HMR)替换

webpack构建工具打包代码的性能优化:

1、SourceMap(源代码映射)

使用原因:因为开发时写错浏览器提示错误是编译后的代码中的错误,有时很难发现编译前哪里出错

它会生成一个xxx.map文件,里面包含了源代码和编译后代码之间的映射关系

使用:

(1)开发模式:cheap-module-source-map(只提示行问题)

(2)生产模式:source-map(行列问题都提示)

2、开发环境中提高打包构建速度

HotModuleReplacement(热模块替换HMR)

开发时修改某一个模块的代码,webpack就会默认将所有模块都进行打包编译,所以需要做到修改某一个模块代码就只有这个模块需要打包编译,其他不变,加快打包速度

 devServer: {
     hot: true  //HMR,css会热更新,但是js还是强制更新
   }

css可以,但是js还不行原因:是因为原理是style-loader带有热模块替换功能

js要实现热模块替换功能需要在main.js中写如下代码进行处理

 `main.js`
 if(module.hot){  //判断是否支持热模块替换功能
     module.hot.accept("./js/sum")   //将需要热模块替换的文件进行处理
 }

但是vue-loader以及react-hot-loader也可以实现热模块替换

HMR需要搭配相应的插件使用,常用的是webpack.HotModuleReplacementPlugin

 plugins: [
     new webpack.HotModuleReplacementPlugin(),
     // ...其他插件
   ],
3、OneOf

一个文件在匹配loader时,就算前面匹配成功也会继续往下看去,但是一个文件只需要一个loader匹配,所以需要oneOf只让一个文件匹配一个loader来提高速度很有必要

 `webpack.config.js`
 modules:{
     rules:[
         {
             //让每一个文件只经历一个loader的处理
             oneOf:[
                 {
                     test:/.css$/,
                     use:[]
                 },
                 {
                     test://,
                     use:[]
                 }
             ]
         }
     ]
 }
4、exclude/include

因为外界的文件,比如node_module里的文件都是已经编译处理过的,所以不需要自己再编译处理,使用exclude将这些文件排除在外会提高编译速度(在eslint和loader规则中都需要配置)

  • include //只处理xxx文件
  • exclude //除了xxx文件其他都处理

主要针对js文件,css不处理(因为1、一般不引入外部css 2、就算引入也想和自己写的css打包在一起)

include和exclude只能出现其中一个配置项,否则报错

5、cache缓存

每次打包js文件都要经过eslint检查和babel编译,速度比较慢,我们可以缓存之前的eslint检查和babel编译结果,后面打包只需要对修改的文件进行检查和编译,可以让后面打包速度变快

 babel缓存——在loader匹配中配置
 {
     test: /.js$/,   //正则匹配js文件
     exclude: /node_modules/,   //排除node_modules中的js文件
     loader: "babel-loader",
     options: {
       cacheDirectory: true,//开启babel缓存
       cacheCompression:false  //关闭缓存文件的压缩(不压缩只是会占自己电脑内存,上线的时候不影响,打包速度快,所以选择不压缩)
     }
 }
 ​
 eslint缓存——在插件中配置
 new eslintplugin({
   context: path.resolve(__dirname, "src"),
   exclude: /node_modules/,
   cache: true,
 cacheLocation:path.resolve(__dirname,"../node_modules/.cache/eslintcache")//配置缓存文件路径
     })
6、多进程打包

js文件处理时使用eslint,babel,Terser(虽然没有配置过,但是一般js的自动压缩就是自动开启了这个工具)三个工具完成的,之前是一个个完成是单进程所以慢,现在使用多进程打包

但是注意:多个进程一般是要在特别耗时间的地方使用,因为每个进程启动就有大约600ms左右的开销

如何使用

1、获取CPU核数,因为启动进程的最大数量就是CPU的核数

 const os = require("os")//nodejs核心模块
 //cpu核数
 const threads = os.cpus().length

2、配置thread-loader,因为要处理babel-loader所以放在babel-loader前面

 //处理babel-loader
 {
     //js处理:eslint(使用插件)=>babel(使用loader)
     test: /.js$/,   //正则匹配js文件
     exclude: /node_modules/,   
     use: [
       {
         loader: "thread-loader",
         options: {
           works:threads//进程数量
         }
       },
       {
         loader: "babel-loader",
         options: {
           cacheDirectory: true,
           cacheCompression:false  
         }
       }
     ]
 }
 ​
 //处理eslint插件
 new eslintplugin({
       //检测哪些文件,但是在vscode中使用eslint插件是检测所有文件的,可以在eslintignore文件里进行忽略配置
       context: path.resolve(__dirname, "src"),
       exclude: /node_modules/,
       cache: true,
       cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslintcache"),//配置缓存文件路径
       threads  //开启多进程
 })
 ​
 //处理terser(js压缩的内置插件)
 const TerserWebpackPlugin = require("terser-webpack-plugin")
 new TerserWebpackPlugin({
       parallel:threads  //开启多进程
 })
7、减少代码体积TreeShaking

tree-shaking:是webpack中的一种代码优化策略,它会在运行过程中静态分析模块之间的导入和导出,确定esmodule中哪些导出值没有被其他模块使用,将死代码剔除从而提升应用程序的性能,treeshaking是在rollup中率先实现,后来才在webpack2中引入

原理步骤:(1)做静态分析,会对webpack所有模块进行静态分析,构建出一个依赖图(2)标记未使用代码(3)webpack会根据标记结果,生成输出文件

commonjs不支持treeshaking,因为可以动态导入

 optimization: {
     usedExports: true, // 启用Tree Shaking
 }
8、对于babel

babel默认只转换新的 JavaScript 语法,比如箭头函数、扩展运算(spread),不转换新的 API:promise,async等,如果要转换需要polyfill

这里有三种polyfill:(1)babel-polyfill:解决了Babel不转换新API的问题,但是直接在代码中插入帮助函数,会导致污染了全局环境,并且不同的代码文件中包含重复的代码,导致编译后的代码体积变大(2)babel-runtime,提供了单独的包babel-runtime用以提供编译模块的工具函数,但是得手动引入(3)babel-plugin-transform-runtime插件,自动识别并替换代码中的新特性,解决了第二点手动引入的问题

普通babel不用polyfill也会加入辅助代码解析新语法,所以要使用这个插件,禁用了babel自动对每个文件的runtime注入而是引入,避免了辅助代码的重复引用,减小代码体积

怎么用(插件+corejs这个polyfill):

1、下载包

 npm i @babel/plugin-transform-runtime -D

2、配置(因为babel开发模式和生产模式都要所以这个也是)

 {
     loader: "babel-loader",
     options: {
       cacheDirectory: true,//开启babel缓存
       cacheCompression: false,  //关闭缓存文件的压缩(不压缩只是会占自己电脑内存,上线的时候不影响,打包速度快,所以选择不压缩)
       plugins:["@babel/plugin-transform-runtime"]  //减少代码体积
     }
 }

3、使用core-js这个polyfill

 npm下载core-js
 方式一、全部引入core-js
 import "core-js"
 方式二、手动按需加载,手动引入需要的语法
 比如引入promise语法
 import "core-js/es/promise"
 方式三、配置babel智能预设,自动按需加载
 `babel配置文件中`
 module.exports = {
   presets: [
     ["@babel/preset-env", {
       useBuiltIns: "usage",//自动按需引入
        corejs:3,
        targets:"> 0.25%, not dead" // 浏览器支持范围
     }]
   ]
 }
9、压缩

css压缩,js压缩,图片压缩ImageMinimizerWebpackPlugin

图片打包失败的话有一些错误可以看看这个视频,可能是有两个文件没下载成功41-高级-压缩图片哔哩哔哩bilibili

10、代码分割Code Split

chunk:打包进来的一个入口文件及所有它依赖为一个模块,这个模块就是chunk

bundle:webpack输出代码叫做bundle

(1)原因:我们想要在进入首页时只渲染首页的代码,但是此时所有的js代码都被打包到一个文件里,无法做到,此时就需要代码分割优化代码运行性能

img

(2)是什么:

1、分割文件:将打包生成的文件进行分割,生成多个js文件,将公共部分提取出来,将第三方库也提取出来

2、按需加载,动态导入:需要哪个文件就加载文件

(3)怎么做:

1、多页面应用要弄成多入口文件(但问题是:有可能重复引入相同代码)

提取公共部分:如果不同文件引入同一段逻辑代码,会重复加载,所以解决方法是将这同一段逻辑代码拆出来作为一个独立文件,其他的文件将这个文件引入即可,但webpack并不会默认加载一次,而是每次引入时都加载一次,要有配置,但是这时将所有引入多次的文件的代码独立出一个文件,第三方库要变成另外独立的文件要有其他配置

按需加载:使用imoprt动态导入语法,打包时会将动态导入的文件代码分割(拆分成单独模块),在需要使用的时候自动加载

 `main.js`
 document.getElementById("btn").onclick = function(){
     import("./count")
         .then((res)=>{
         log("模块加载成功")
         })
         .catch((err)=>{
         log("模块加载失败")
     })
 }

2、单页面应用——单入口文件

动态导入解决资源大的问题,如果是单入口文件,打包后会将所有的资源全在一个文件,那么在看这个页面的时候一下要加载所有的资源会太大了,加载速度慢,所以用到代码分割,将有些后面用到的且很大的资源使用异步处理后面使用,但是也会增加http请求

代码分割配置:

 optimization:{
     splitChunks:{
         chunks:"all"
     }
 }//只需要配置这个,其他都用默认值即可
 做的事1、node_module打包成一个文件 2、将公共重复代码抽离出来 3、将动态导入的文件抽离出来
 ​
 不过eslint不识别动态导入语法,需要使用插件
 eslint配置文件中要使用动态导入插件
 plugins:["import"]

但是如果项目中有一些特别大且不会去改动的第三方库可以使用dllplugin提前打包减少代码的多次编译并且实现代码复用,但是特别注意!dll提前打包的文件不会自动在html引入需要手动引入

dll优化步骤:

(1)配置webpack.dll.config.js文件

 const webpack = require('webpack')
 module.exports = {
   mode: 'production',
   entry: {
     vendor: [  //写用到的第三方库
       "axios",
       "lodash"
     ]
   },
   output: {
     path: __dirname + "/dist/dll",
     filename: "[name].dill.js",//记得要在html中手动引入
     library: '[name]_library'  //用于通知,定位哪些被打包
   },
   plugins: [
     new webpack.DllPlugin({//webpack内置插件
       path: __dirname + "/[name]-manifest.json",  //通过这个插件会生成json,由这个json通知打包
       name: '[name]_library',//必须和library同名
       context: __dirname
     })
   ]
 }

(2)在正式打包前提前使用命令打包

 {
   "scripts": {
     "build:dll": "webpack --config webpack.dll.config.js"
   }
 }

(3)让主配置知道哪些文件被dll打包过

 `webpack.config.js`
 plugins:[
     new webpack.DllReferencePlugin({
       manifest: require(__dirname + '/vendor-manifest.json')
     }),
 ]

3、给动态打包的文件命名

 import(/*webpackChunkName:"math"*/'./js/math').then(res => {
     console.log(res.default)//这个default是a文件中暴露的值
   })
 ​
 //输出那里加个chunkFileName(打包输出其他文件命名),告诉webpack要用上这个名字
 output:{
     ...
     chunkFilename:"static/js/[name].js"
 }
 ​
 ​