一、优化打包构建速度

1.oneOf

使用:在webpack.config.js的module的rules中将一些loader的配置放在oneOf的数组中即可
  module: {
    rules: [
      {//匹配js文件,后面的babel也会匹配js文件,所以将eslint放在外面
        test: /\.js$/,
        exclude: /node_modules/,//排除node_modules下的文件
        loader: 'eslint-loader',
        options: {
          fix: true//自动修复eslint的错误
        }
      },
      {
        oneOf: [
          {
            test: /\.less$/,
            use: [
              'style-loader', 'css-loader', 'less-loader'
            ],
          },
          {
            test: /\.css$/,
            use: [
              'style-loader', 'css-loader', 'postcss-loader',
            ],
          },

          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',//预设,只是babel做怎么样的兼容性处理
                  {
                    useBuiltIns: 'usage',// 按需加载
                    corejs: {//指定core-js的版本
                      version: 3
                    },
                    targets: {//指定兼容性到浏览器的哪个版本
                      chrome: '60',
                      firefox: '60',
                      ie: '9',
                      safari: '10',
                      edge: '17'
                    }
                  }
                ]
              ],
            }
          }
        ]
      }
    ]
  },
注意:oneOf中的loader一种文件类型只会匹配一个loader,如果有两个不同的loader配置则后面的loader会失效,需要把其中一个放在oneOf的外面


2.babel缓存

设置babel里loader的配置:cacheDirectory:true
{
  test: /\.js$/,
  exclude: /node_modules/,
  loader: 'babel-loader',
  options: {
      presets: [
        [
          '@babel/preset-env',//预设,只是babel做怎么样的兼容性处理
          {
            useBuiltIns: 'usage',// 按需加载
            corejs: { version: 3 },//指定core-js的版本
            targets: {//指定兼容性到浏览器的哪个版本
                chrome: '60',
              firefox: '60',
              ie: '9',
              safari: '10',
              edge: '17'
            }
          }
         ]
      ],
      cacheDirectory: true//设置babel缓存,第二次构建时会读取之前的缓存
  }
}


3.多进程打包

使用的loader:thread-loader
加上thread-loader,进程启动大概需要600ms,进程通信也需要开销,只有工作消耗时间长,才需要多进程打包,因此一般用于babel中
{
  test: /\.js$/,
  use: [
      {
        loader: 'thread-loader',//开启多进程打包,一定要注意顺序,在babel-loader后执行
        options: {
          workers: 2
        }
      },
      {
        loader: 'babel-loader',
        options: {
          presets: [[
            '@babel/preset-env', {
              useBuiltIns: 'usage',
              corejs: { version: '3' },
              targets: {
                chrome: '60',
                firefox: '60'
               }
             }
          ]]
          }
      }
   ]
}


4.externals

拒绝一些库参与打包,但在页面使用cdn链接的方式引入(不然没有效果),比如jquery
externals:{
  jquery:'jQuery'//拒绝jquery参与打包,从而使构建速度更快
}

5.dll

类似externals指示哪些库是不参与打包的,但是dll会对某些库单独进行打包,将多个库打包为一个chunk
比如将node_modules中的库拆分打包为不同的文件/chunk,更加利于性能优化

(1)新建一个webpack.dll.js文件进行配置(以打包jquery为例)

配置写好后使用webpack --config webpack.dll.js命名生成打包的库以及manifest.json文件到dll目录下
let { resolve } = require('path')
const webpack = require('webpack')

module.exports = {
  entry: {
    jquery: ['jquery']//最终打包生成的名字为jquery,要打包的库是数组中的
  },
  output: {
    filename: '[name].js',//打包输出的名字取的是entry的key值
    path: resolve(__dirname, 'dll'),//打包输出的路径
    library: '[name]_[hash]'//打包暴露出去的库的名字
  },
  plugins: [
    new webpack.DefinePlugin({//打包生成一个mainfest.json文件,提供映射关系,告诉webpack下次打包的时候不要再打包其中的内容
      name: '[name]_[hash]',//映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/manifest.json')//输出文件路径
    })
  ],
  mode: 'production',
}

(2)在webpack.config.js中引入并配置插件

const AddAssetHtmlWebpackPlugin = require('add-asset-html-Webpack-plugin')

new webpack.DllReferencePlugin({//告诉webpack哪些库不参与打包
    manifest:resolve(__dirname,'dll/manifest.json')
})
new AddAssetHtmlWebpackPlugin({//将某个文件打包输出,并在html中自动引入
    filepath:resolve(__dirname,'dll/jquery.js')
})

二、优化代码运行的性能

1.缓存(hash-chunkhash-contenthash)

(1)资源缓存出现的问题:

    在缓存有效期间,如果代码有更新,重新打包构建刷新的话,浏览器的页面不会有任何变化

(2)解决方法:对打包文件的名字做一些操作

    1)hash
    每次webpack打包的时候都会生成一个hash值,取打包生成的hash值拼接在输出文件名后就会更新资源,html就会引入这个新的资源,浏览器就会去服务器重新请求这个新的资源
 output: {
   filename: 'js/built.[hash:10].js',//打包输出后会输出到js目录下,取hash的前十位
     path: resolve(__dirname, 'build')
 },
    出现的问题:css和js同时使用一个hash值,如果只改动一个会重新打包,导致所有的缓存失效

    2)chunkhash
    根据chunk生成hash,如果资源来自于同一个chunk就使用同一个hash
 output: {
   filename: 'js/built.[chunkhash:10].js',//打包输出后会输出到js目录下,取chunkhash的前十位  
 path: resolve(__dirname, 'build')
 },
    出现的问题:js和css还是属于同一个chunk,因为css是引入到js中的

    3)contenthash
    根据文件的内容生成hash,不同的文件hash值一定不同
 output: {
   filename: 'js/built.[contenthash:10].js',//打包输出后会输出到js目录下,取contenthash的前十位
   path: resolve(__dirname, 'build')
 },


2.tree shaking

(1)作用

    去除在程序中没有使用的代码,让代码体积变得更小

(2)前提

    必须使用ES6模块化
    开启production环境

(3)注意的问题

不同的版本可能会有一些差异,将css文件以及@babel/polyfill当做未使用的代码忽略
测试:在package.json中设置“sideEffects”:false,表示所有代码都可以进行tree shaking,这时候重新打包构建输出的资源就没有css文件了
解决:
"sideEffects":["*.css","*.less"]//指明哪些文件不用tree shaking


3.code split

(1)多入口代码分割

    有几个入口,最终输出就有几个bundle
  entry: {
    index: './src/index.js',
    a: './src/js/a.js'
  },
  output: {
    filename: 'js/[name].[contenthash:10].js',//打包输出后会输出到js目录下
    path: resolve(__dirname, 'build')
  },
    代码结果:

(2)optimization

  1)作用:
    可以将node_modules中的文件单独打包为一个chunk输出
    如果是多入口,会自动分析多入口文件,有多个文件中使用同一份公共的资源的话,会将公共的资源打包为单独的chunk
   2)使用:
    在webpack.confiig,js中配置optimization
optimization: {
  splitChunks: {
      chunks: 'all'
  }
},

(3)通过js代码让某个文件打包为一个单独的chunk

在入口js文件中将某个文件使用import单独打包
import('./js/a.js').then(() => {
  console.log('文件加载成功')
}).catch(() => {
  console.log('文件加载失败')
})
单独打包后的文件,默认使用id命名,如果想要对打包后的文件重命名的话,使用注释的方式在文件路径前面加一个webpackChunkName:
import(/* webpackChunkName:'test' */'./js/a.js').then(() => {
  console.log('文件加载成功')
}).catch(() => {
  console.log('文件加载失败')
})


4.懒加载/预加载

   正常的加载:并行加载,同一时间加载多个文件,所有的文件会全部先加载完
(1)懒加载:lazy loading
    场景:比如在点击按钮的时候才想去加载某个文件
    使用:在js文件中使用import(类似code split的第三种方式)
import(/* webpackChunkName:'test' */'./js/a.js').then(() => {
  console.log('文件加载成功')
}).catch(() => {
  console.log('文件加载失败')
})
(2)预加载:prefetch
在使用之前加载js文件,等其他资源加载完毕,浏览器空闲的时候加载webpackPrefetch的资源
import(/* webpackChunkName:'test',webpackPrefetch:true */'./js/a.js').then(() => {
  console.log('文件加载成功')
}).catch(() => {
  console.log('文件加载失败')
})


5.PWA

渐进式网络开发应用程序:由service worker+catch完成的,使网页像APP应用程序一样,离线也可以访问,性能会更好
使用的插件:workbox-webpack-plugin

(1)下载和引入插件

npm i workbox-webpack-plugin
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
new WorkboxWebpackPlugin({//最终会生成一个service-worker.js文件
    clientsClaim:true,//帮助serviceWorker快速启动
    skipWaiting:true//删除旧的serviceWorker
})

(2)注册serviceWorker

一般在入口js文件中注册:
if('serviceWorker' in navigator){
    window.addEventListener('load',function(){
        navigator.serviceWorker.register('service-worker.js').then(()=>{
                    console.log('注册成功');
               }).catch(()=>{
                    console.log('注册失败')
               })
    })
}

(3)serviceWorker代码必须运行在服务器上,本地测试可以先安装serve,再通过serve指令启动

npm i serve -g//全局安装serve
serve -s build//将build下的资源作为静态资源暴露出去,启动服务器

(4)测试:浏览器设置为下图的offline后,刷新网页,仍然存在刷新前的网页