webpack系列之优化策略

1、打包速度优化

1.1、速度监控插件—speedMeasureWebpackPlugin

speed-measure-webpack-plugin是一款能检测Loader和Plugin耗时的插件,具体的用法就不再阐述了,可以去https://www.npmjs.com/package/speed-measure-webpack-plugin这看看

1.2、减小webpack搜索文件范围

1.2.1、Loader中的配置

我们通常使用Loader去加载webpack不能识别的文件,例如.css .vue文件等,但是这个加载过程是一个挺耗时的过程。
因此,在loader的配置中我们可以通过设置:

  • include:指定Loader去解析某个文件
  {
     
                    test: /.js$/,    //ES6的babel
                    include:path.resolve('src'),   //只加载src下的.js文件
                    use: 'babel-loader',
                   
                },
  • exclude:不去加载某些文件,例如node_modules下的文件等
  {
     
                    test: /.js$/,    //ES6的babel
                    include:path.resolve('src'),   //只加载src下的.js文件
                    use: 'babel-loader',
                    exclude:/node_modules/   //解析时忽略node——modules
                },

1.2.2、resolve中的配置

webpack的寻找模块的文件机制一般是由内置的JavaScript解析器决定的

  1. 当引入一个绝对路径:则直接去这个路径下去解析该模块
import "D:\vueProject\webpack\commons"
  1. 当引入一个相对路径时:根据当前文件所在的路径,拼接成绝对路径去解析
import "./src/commons"
  1. 当直接引入一个模块时(这种模块一般叫做第三方模块):webpack会去当前目录下的node_modules下去寻找,如果找不到则再往上去查找,层层向上。这跟Node的找模块机制很像,
import "vue"

(具体的模块解析请看这里https://webpack.docschina.org/concepts/module-resolution)
你可以通过resolve去修改默认的查找规则,我们可以优化resolve的配置,达到减小webpack搜索文件范围的目的。
具体策略为:

  • alias—指定某个模块的搜索路径,是一个对象:“模块名”:“对应模块的路径”,对于整体性非常强的模块,我们可以这么去设置
 resolve:{
         //利用resolve模块减少文件查找
            alias:{
     
                'vue':path.resolve(__dirname,'./node_modules/vue/dist/vue.min.js'),  //对于像常用的模块,我们可以定义在alias中,直接去该路径下查找该模块,减少查找模块的时间
            },
         
        }
  • modules—规定webpack寻找模块应该寻找的文件,路径有两种,绝对和相对,建议用相对路径,因为绝对路径只会在规定的文件查找,当查找不到就会失败。我们一般传入一个数组有多个路径,modules[0]要优先于modules[1],先去modules[0]查找,查找不到再去modules[1]查找。
    webpack系列之优化策略_第1张图片
 resolve:{
         //利用resolve模块减少文件查找
         modules:["path.resolve(__dirname, 'src')","node_modules"]
        }
  • mainFields----在pachage中查找对应字段,根据对应字段,直接读取此模块的代码。第三方模块针对,环境的不同,入口文件也不同的,webpack默认的resolve.mainFields : [’browser’, ’main ’ ]

使用本方法优化时,需要考虑到所有运行时依赖的第三方模块的入口文件的描述字段, 就算只有一个模块出错,也可能会造成构建出的代码无法正常运行。-----《深入浅出Webpack》

resolve:{
         //利用resolve模块减少文件查找
           
                
            mainFields:['main']   //查找package.json中的main字段   根据main字段就可以直接找到入口了,因为大多数第三方模块都是main为入口文件
        }
  • extensions----webpack将去优先解析对应后缀的文件,默认值为: [ .js,.json ],将确定的后缀写出,最常用的后缀放前面,减少搜索时间,对于确定的模块,直接写出对应后缀
   resolve:{
         //利用resolve模块减少文件查找
           
            extensions:['.js','.json'],   //先查找.js后缀的文件,再去查找.json后缀的文件

        }

假设有一个模块 import “./data” —当我们设置了上面的extensions规则之后,先去对应文件夹查找data.js,找不到再去查找data.json。

假设我们已经直到data就是一个js文件,那么,我们该直接补全后缀。

import "./data.js"

1.2.2、module.noParse配置

module.noParse可以帮我忽略掉那些没有采用模块化的模块,因为像一些工具库,Jquery等没有采用模块化,让webpack去解析这个模块,很是浪费时间

module:{
     
	   noParse:[/jquery\.min\.js$/],   //不去解析jquery  没用且浪费时间
}

1.3、提升第二次打包的时间

1.3.1、利用缓存

在webpack系列项目中,如果开启了缓存,那么就会在node_modules下生成一个.cache文件夹

  • 对于第一次打包没有效果
  • 一旦开启了缓存,除了第一次打包之后的速度会特别快

1.3.1.1、babel-loader

babel-loader常用于解析.js文件,将高版本的JS语法,例如ES6转换成低版本的语法。我们可以针对babel-loader去进行缓存的操作,使得打包速度加快

  {
         //之前提到的针对Loader的匹配规则去优化打包速度
                    test: /.js$/,    //ES6的babel
                    include:path.resolve('src'),   //只加载src下的.js文件
                    use: [{
     
                        loader:'babel-loader',
                        options:{
     
                            cacheDirectory:true    //开启缓存,将在node_modules/.cache生成对应的缓存文件
                        }
                    }],
                    exclude:/node_modules/   //解析时忽略node——modules
                },

1.3.1.2、terser-webpack-plugin

webpack4中推荐使用ter-webpack-plugin进行缓存,在webpack4中有一个配置optimization,它会根据mode不同去进行一个代码的压缩优化,我们可以通过传入一个new TerSerPlugin(…)去覆盖webpack默认的压缩工具

     optimization: {
     
        minimizer: [
          new TerserPlugin({
     
            cache:true,   //开启缓存
          }),
        ],

1.3.1.3、cache-loader

cached-loader可以帮助性能开销较大的Loader,将结果缓存到磁盘中,请将cache-loader,放在别的Loader之后执行
但是如果是性能开销小的Loader去使用的话,那么就会变得更慢了

请注意,保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。–webpack官网

 {
     
                    test: /.js$/,    //ES6的babel
                    include:path.resolve('src'),   //只加载src下的.js文件
                      //loader执行顺序从右到左
                    use: ['cache-loader','babel-loader']
                    }],
                    exclude:/node_modules/   //解析时忽略node——modules
                },

1.3.1.4、资源分包—DLL

这是一个经常用到的缓存策略,单独配置一个webpack配置,将公共资源打包出来,等我们正式打包发布的时候,直接引用就好了,如此一来经过我们预编译打包的模块就不需要经过webpack打包

  1. 新建一个webpack.dll.js作为我们进行资源分包的webpack配置,以打包vue模块为例:
const path=require('path');
const webpack=require('webpack')
module.exports={
     
    entry:{
     
        library:[   //要分离的包
            'vue'   //这里我只打包了vue 其实也可以将我们业务中常用到的工具包打包
        ]
    },
    output:{
     
        filename:'[name]_[hash:6].dll.js',   //文件指纹  方便检测文件有没有被修改过
        path:path.join(__dirname,"commons/library"),   //生成的文件路径
        library:'[name]'   //分离包暴露模块的名字
    },
    plugins:[
        new webpack.DllPlugin({
         //分包
            name:'[name]',   //文件名
            path:path.join(__dirname,"commons/library/[name].json"),   //生成的json  供DllReferencePlugin使用
        })
    ]
}

经过上面的打包,在comms/library/下生成一个library_[hash:6].dll.js和library.json文件
下一步我们将根据这个library.json文件,直接找到我们预编译好的包

  1. 正式打包发布的webpack中的配置:webpack.pro.js
module.exports={
     
  plugins:[
  		  new webpack.DllReferencePlugin({
     
            manifest:require('./commons/library/library_[has:6].json')   //引入经由DLLPlugin生成的json文件  同样的带上了文件指纹
        }),
  ]
}

1.4、HappyPack

webpack时运行在Node环境中的,JS是单线程的,任务是一个接着一个的处理,有什么办法能让webpack在同个时间段,执行多个任务呢?答案就是使用HappyPack。

  1. 对于Loader这些需要大量读写操作的,在需要解析的loader前面添加一个happypack/loader?id=xxx,id为此loader的唯一标识
  2. 在new HappyPack({…})传入配置,去配置一个happyloader,id为loader中的id,代表的是一个实例化的happyPack-loader
modules.rules={
     
                    test: /.js$/,  
                    include:path.resolve('src'),   //只加载src下的.js文件
                     use:[’happypack/loader?id=babel ’],  
                    exclude:/node_modules/   //解析时忽略node——modules
                },
plugins:[
    new HappyPack ( {
      //用唯一的标识符 id,来代表当前的 HappyPack 是用来处理一类特定的文件的 
    	id: ’ babel ’, // 如何处理. j s 文件,用法和 Loader 配置中的一样 
    	loaders: [ ’ babel-loader?cacheDirectory’] , //. .. 其他配置项 }) ,
]

原理:因为loader是对文件的读写操作,特别耗时,HappyPack的原理就是将这些操作分解到多个进程去并行处理,缩短总的构建时间

2、打包体积优化

打包体积关系到我们一个项目性能的好坏,首屏优化,资源压缩等。

2.1、打包体积分析插件

常用的打包体积分析插件就是:webpack-bundle-analyzer,具体用法就不说了,可以去这看:https://www.npmjs.com/package/webpack-bundle-analyzer

2.2、文件压缩

将代码压缩,资源压缩,项目的体积更小,速度更快。
我们都知道,在webpack中,使用Loader去解析除了 .js 和 .json 之外的文件,其实这些文件也提供了很多的压缩选项,也有很多的plugin能帮我们压缩各种资源。

2.2.1、CSS压缩

使用optimize-css-assets-webpack-plugin结合cssnano压缩css代码

  • optimize-css-assets-webpack-plugin是一款压缩css文件的插件,npm地址:https://www.npmjs.com/package/optimize-css-assets-webpack-plugin
  • cssnano—一款压缩css的工具
plugins:[
    new OptimizeCSSAssetsPlugin({
     
            assetNameRegExp:/\.css$/g,  //要解析的css文件匹配规则,尽可能的精准 避免webpack不必要的工作
            cssProcessor:require('cssnano')   //使用cssnano工具进行css压缩
        }),
]

加粗样式

2.2.2、js压缩

如果是webpack4.x以上的版本的话,那么mode="production"是会自动开启uglifyPlugin的
使用uglifyPlugin进行JS文件的压缩,

2.2.4、图像压缩

图像压缩的plugin和loader很多,这里只提一个常用的url-loader:https://www.webpackjs.com/loaders/url-loader

  • options.limit-----小于这个配置的文件将转换成base64嵌入html页面中
 {
     
                    test:/\.(png|jpg|jpeg)$/,
                    use:[
                        {
     
                            loader: 'url-loader',
                            options:{
     
                               name:'[name]_[hash:6].[ext]',
                               limit:10*1024,   //小于10*1024字节即10kb时,就将图片转换成base64嵌入html中
                            },

                        },
}

3、关闭source-map

mode="production"时,默认关闭source-map

4、代码分割

4.1、提取公共模块—splitChunksPlugin

有这么一个需求,当A页面引用了C模块,B页面也引用了C模块,那么对于这个C模块,我们就可以叫做公共模块,webpack会重复打包C模块,那么我们就可以使用SplitChunksPlugin去将C模块打包出来,避免重复打包的情况
相关配置:https://webpack.docschina.org/plugins/split-chunks-plugin/#src/components/Sidebar/Sidebar.jsx

 splitChunks: {
     
 		chunks:"all",   //打包模块的类型:同步模块(initial),异步模块(async)  all为所有模块
 		//同步模块---一般就是直接执行文件,例如入口文件等
 		//异步模块---import("vue")等
            minSize: 0,  //引用的块的大小,直接大于这个就直接打包,设置为0为怎么样都打包
            cacheGroups: {
        //打包的模块
                commons: {
       //打包的commons模块
                    name: 'commons',   //commons模块的name                  	
                    minChunks: 2   //引用次数为2 采取实现打包  例如A模块引用C模块,B模块也引用C模块,那么对于C模块来说就是引用了两次
                }
            }
        },

要注意,如果是webpack4.0以下的版本,提取公共代码用CommonsChunkPlugin

4.2、优化引入模块

4.2.1、CDN引入:html-webpack-externals-plugins+html-webpack-plugin

在打包生产环境时,我们可以将一些比较大的模块,例如Vue、React等采取CDN引入的方式,如此一来就能缩小我们的项目大小了。
html-webpack-externals-plugins—将某个模块,替换成CDN资源:https://www.npmjs.com/package/html-webpack-externals-plugin
webpack.pro.js

  new HtmlWebpackExternalsPlugin({
     
            externals:[ 
                {
     
                    module:'Vue',   //打包模块名称
                    entry:'//cdn.bootcss.com/vue/2.6.11/vue.common.dev.js',  //输出CDN资源
                    global:'Vue'   //全局名称
                }
            ]
        })

index.html-----使用EJS语法引入

<!DOCTYPE html>
<html lang="en">
<head>

    <title>webpack</title>
     <script type="text/script" src='https//cdn.bootcss.com/vue/2.6.11/vue.common.dev.js'></script> 
 
</head>
<body>
    <div id='search'>
        <p>search页</p>
    </div>
</body>
</html>

4.2.2、页面公用的资源

比如我们正在写一个多页面应用,此时我们index.html页面需要一大串的meta,search.html也需要同样的meta,那么我们就可以使用html-webpack-plugin中的EJS语法+raw-loader,直接引入一个meta.html。
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    
    <%= require('raw-loader!./meat.html') %>
    <title>webpack</title>
  
 
</head>
<body>
    <div id='search'>
        <p>search页</p>
    </div>
</body>
</html>

meta.html


    <meta charset="UTF-8">
    <meta name="viewport" content="viewport-fit=cover,width=device-width,initial-scale=1,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <meta name="keywords" content="now,now直播,直播,腾讯直播,QQ直播,美女直播,附近直播,才艺直播,小视频,个人直播,美女视频,在线直播,手机直播">
    <meta name="name" itemprop="name" content="NOW直播—腾讯旗下全民视频社交直播平台">
    <meta name="description" itemprop="description"
        content="NOW直播,腾讯旗下全民高清视频直播平台,汇集中外大咖,最in网红,草根偶像,明星艺人,校花,小鲜肉,逗逼段子手,各类美食、音乐、旅游、时尚、健身达人与你24小时不间断互动直播,各种奇葩刺激的直播玩法,让你跃跃欲试,你会发现,原来人人都可以当主播赚钱!">
    <meta name="image" itemprop="image"
        content="https://pub.idqqimg.com/pc/misc/files/20170831/60b60446e34b40b98fa26afcc62a5f74.jpg">
    <meta name="baidu-site-verification" content="G4ovcyX25V">
    <meta name="apple-mobile-web-app-capable" content="no">
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">

5、文件内联—减少请求数,加快首屏时间

  1. CSS内联-----style-loader,将.css文件内联到html中减少css请求
  2. 图片内联-----url-loader,通过设定options.limit来规定大小为多少的图片将被采用Base64编码内嵌到html页面中
  3. JS内联----使用raw-loader+html-webpack-plugin的EJS语法

5、摇树优化—Tree shaking

5.1、定义

将用到的方法,模块打包,不用到的方法或者模块就不打包

5.2、原理

根据DCE的规则去执行,当满足DCE的代码或者模块就不去打包。
在ES6的模块机制下才有效,因为ES6的模块机制是静态的不是动态的,DCE匹配的代码会在uglify阶段被擦除,不会被打包。
DCE规则
4. 代码不被执行,不会被到达的
5. 代码执行结果不会被用到的
6. 代码只写不读的

5.3、用法

JS–Tree shaking

在webpack4.x以上的版本中,当mode==“pdoduction”,默认开启,即生产模式自动开启,若想在开发环境开启,那么就需要.babelrc里设置modules:false即可

CSS–Tree shaking

  1. Purgecss: 原理—遍历CSS代码、识别已经用到的CSS class,搭配mini-css-extract-plugin(此plugin能将css文件打包成单独的.css文件)使用
    2.uncss 原理–通过jsdom加载html, 样式通过PostCSS解析,通过document.querySelector识别用不到的CSS器

6、scope hoisting

6.1、定义

webpack打包会生成各种闭包,导致内存泄漏,运行开销增大,体积增大,所以我们要使用scope hoisting 来重新构建webpack。

6.2、原理

将模块代码按照引用顺序放在一个函数作用域中,然后适当命名解决冲突

6.3、使用

  • webpack4.0 在mode="production"默认开启
  • new ModuleConcatenationPlugin()也可开启
module.exports = {
      
	resolve: {
      //针对 Npm 中的第二方模块优先采用 j snext :main 中指向的 ES6 模块化语法的文件 
	mainFields: [’ j snext :main ’,’browser’,’main ’] 
plugins: [  
	new ModuleConcatenationPlugin( ), ]//开启 Scope Hoisting
}

6.4、作用

  • 减少函数声明
  • 减小内存开销

7、提取公共代码

7.1、为什么需要提取公共代码

大型网站,不单单只是单页面,其实每一个页面都是一个单页面,如果这些单页面之间采用了相同的技术栈和相同的代码,相同的样式,那么代码重复率就特别高,这会产生以下问题

  • 相同代码重复被执行,影响效率,浪费用户流量和服务器成本

  • 页面加载资源大,首屏时间慢
    那么将页面之间共有的代码抽离成公共代码就尤为重要,这么做的好处有:

  • 加快访问时间,例如当刚进入页面就开始加载公共代码,设置缓存,那么再去访问网站的另一个页面,就不需要再去请求公共代码,直接从缓存中获取

  • 减少服务器压力,减少用户流量和带宽,因为第一次进入时,已经缓存了公共代码,那么,访问该网站别的页面时,就不需要去请求公共代码

当确定一个网站需要的技术栈之后,我们可以将公共代码分成:

7.2、公共库

这个库是存放一些业务模块的代码,例如utils模块的代码等,这个库主要的特点是,内容经常更新,commos.js

7.3、基础库

这个库是存放一些项目必须的依赖模块,例如:当一个网站是以vue框架开发的,一个大型网站不单单只是一个单页面应用,很可能有多个入口,但是这些应用的特点就是,共同使用vue开发,那么vue就是这个项目的基础库,这个库的特点就是内容不经常更新,是多页页面共同用到的模块。base.js

7.4、为文件添加文件指纹

文件指纹

为每一个经由webpack打包生成的文件的文件名,添加一个hash值,标识当前唯一的文件,一般而言是:[name]_[hash:8].js

作用

结合我们需要提取公共代码,其实就是为了让我们知道,该文件是否被更新过,如果被更新过,那么我们就需要去替换掉缓存中的公共代码,若没有,那么我们就不需要去更新缓存中的公共代码。

7.5、使用splitChunksPlugin进行分包

https://webpack.docschina.org/plugins/split-chunks-plugin/#src/components/Sidebar/Sidebar.jsx具体的配置参照官网,这里只介绍一下基本的参数:

  • chunks:需要打包块的类型,有all—打包全部,async—打包异步模块,initial—打包同步模块
  • minSize—需要打包模块的大小
  • cacheGroups----打包公共文件,webpack根据这里面的配置,决定要打包成几个.js文件。根据我们之前的讨论,应该打包成一个基础库base.js,还应该打包一个公共库,commons.js,那么这里就应该有两个对象,一个是base,一个是commons。
  • cacheGroups.test—打包该公共库的匹配规则,按照我们之前讨论的,base为基础库,打包的是node_modules下的模块,commons为公共库模块,打包的应该是src下的模块
module.exports = {
     
    optimization: {
     
        splitChunks: {
     
            cacheGroups: {
     
                base: {
        //打包基础库base.js
                    name: "base",
                    test: /[\\/]node_modules[\\/]/,  //打包node_modules下的模块
                    chunks: "all",
             
                },
                common: {
        //打包公共模块
                    name: "common",
                    test: /[\\/]src[\\/]/,
                    minSize: 1024,
                    chunks: "all",
               
                }
            }
        }
    },
};

你可能感兴趣的:(webpack)