webpack进阶优化配置学习(二)

108 阅读8分钟

1. 提升开发体验

1.1 SourceMap

1.1-1 为什么

运行后,所有的CSS和JS都合并成了一个文件,并且解决了其他代码,此时如果代码运行出错,那么提示代码错误的位置我们是看不懂的,那么我们及很难得去发现错误在哪里。

所以我们需要更加准确的错误提示,来帮助我们更好的开发代码。

1.1-2 是什么

SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的方案。他会生成XXX.map文件,里面包含源代码和构建后代码每一行,每一列得映射关系。当构建后代码出错了,会通过XXX.map文件,从构建后的代码出错位置找到映射后源代码出错的位置,从而让浏览器提示源代码文件出错的位置,帮助开发者能够更快的找到错误的根源。

1.1-3 怎么用

虽然SourceMap的值有很多种情况,但是实际情况开发的时候,我们一般关注两种即可:

开发模式: cheap-module-source-map

优点:打包编译速度快,只包含行映射
缺点:没有列映射
module.exports = {
    // 其他省略号
    mode: "development",
    devtool: "cheap-module-source-map",
}

生产模式: source-map

优点:包含行/列映射(生产环境代码会被压缩到只有一行)
缺点:打包编译速度更慢
module.exports = {
    // 其他省略号
    mode: "production",
    devtool: "source-map",
}

提高打包构建速度

2.1 Hot Module Replacement

2.1-1 为什么

开发时我们修改了其中一个模块代码,webpack默认会将所有模块全部重新打包编译,速度很慢,所以我们需要做到修改某个模块代码,就只有这个模块代码需要打包编译,其他模块不变,提高打包速度。

2.1-2 是什么

Hot Module Replacement(HMR/热模块替换)在程序运行中 替换 添加或删除模块 而无需重新加载整个页面。

2.1-3 怎么用

//webpack.config.js配置
devServer: {
   // 其他省略
   hot:false,//关闭HMR
 },
 //js 文件修改需要在main.js里去每一个js都做判断
 if(module.hot) {
    // 判断是否支持热模块替换功能
    module.hot.accept("./js/count")//接收这个文件
}
//以上这么些在文件较多的时候会比较麻烦 所以需要借助loader

2.2 oneOf

2.2-1 为什么

打包时每个文件都会经过所有loader处理,虽然因为test正则原因实际没有处理上,但是都要过一遍速度比较慢。

2.2-2 是什么

oneOf会提高loader匹配效率,匹配到一个loader后,后面的就不会再继续匹配了。

2.2-3 怎么用

module:{
  rules:[
    {
      test:/\.js$/,
      exclude:/node_modules/,
      // //优先执行
      enforce:'pre',
      loader:'eslint-loader',
      options:{
        fix:true
      }
    },
    {
      //下面的loader只会匹配一个,处理性能更好
      //注意:不能有两个配置处理同一种类型文件
      oneOf:[
        {
          test:/\.css$/,
          use:[...commenCssLoader]
        },
        {
          test:/\.less$/,
          use:[...commenCssLoader,'less-loader']
        },
      ]
    }
  ]
},

2.3 include/exclude

2.3-1 为什么

webpack配置时,为了提高解析速度,需要指定需要处理的文件。

有三种配置可以指定需要处理的文件:

. test . include . exclude

2.3-2 是什么

include 包含 只处理xxx文件 exclude 排除 除了xxx文件以外其他文件都处理 优先级最高

2.3-3 怎么用

module:{
        rules: [
                {
                  test: /\.js$/,
                  // 只能二者存在其一
                  // include:path.resolve(__dirname,"../src"),//只处理src下的文件,其他文件不处理
                  exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
                  loader: "babel-loader",
                },
          ],
    },

2.4 cache

2.3-1 为什么

每次打包时js文件都要经过ESLint检查和Babel编译,速度比较慢,我们可以缓存之前得EsLint检查和babel编译结果,这样二次打包速度就会更快了(提升第一次以后得打包速度)。

2.3-2 是什么

对ESLint检查和babel编译结果进行缓存

2.3-3 怎么用

{
     test: /\.js$/,
     // 只能二者存在其一
     // include:path.resolve(__dirname,"../src"),//只处理src下的文件,其他文件不处理
     exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
     loader: "babel-loader",
     options:{
        cacheDirectory:true,//开启babel缓存
        cacheCompression:false,//关闭缓存文件压缩
     }
},

2.5 Thead

2.5-1 为什么

开启多个线程同时来处理js(eslint babel Terser这三个工具从而提升他们的运行速度)

2.5-2 是什么

多进程打包:开启电脑得多个进程同时干一件事 速度更快 需要主意:请仅在特别耗时得操作中使用,因为每个进程启动大约有600ms左右开销

2.5-3 怎么用

1. 获取cpu核数
const os=require("os");
const threads=os.cpus().length;
2. 下载依赖
npm i thread-loader -D
{
   test: /\.js$/,
   {
      loader:"thread-loader",//放在babel-loader之前
      options:{
         works:threads,//开启多进程对babel进行处理
      }
   },
   {
      loader: "babel-loader",
      options:{
        cacheDirectory:true,//开启babel缓存
        cacheCompression:false,//关闭缓存文件压缩
      }
   }
 },
3. js压缩
const TerserWebpackPlugin = require("terser-webpack-plugin");
new TerserWebpackPlugin({
  parallel: threads, // 开启多进程和设置进程数量
})
或者放在
optimization: {
    // 压缩的操作
    minimizer: [
      new TerserWebpackPlugin({
        parallel: threads, // 开启多进程和设置进程数量
      }),
    ],
},

3.减少代码体积

3.1 Babel

3.1-1 为什么

babel为编译的每个文件插入了辅助代码,使得代码体积过大 Babel对一些共共方法使用了非常小的辅助代码比如_extend,默认情况下会被添加到每一个需要他的文件中 你可以将这些辅助代码作为一个独立模块来避免重复引入

3.1-2 是什么

@babel/plugin-transform-runtime:禁用了babel自动对每个文件的runtime注入,而是引入 @babel/plugin-transform-runtime 并且所有辅助代码从这里引入

3.1-3 怎么用

//下载依赖
npm i @babel/plugin-transform-runtime -D
//配置文件
{
   loader: "babel-loader",
   options:{
     plugins:["@babel/plugin-transform-runtime"]//减少代码体积
   }
}

3.2 Tree Shaking

3.2-1 为什么

函数库或者第三方工具库或组件库不做处理就会把所有的打包,我们用到什么就打包什么

3.2-2 是什么

Tree Shaking是一个术语 通常用于描述移除JavaScript肿的没有使用上的代码 注意:他依赖ES moudle

3.2-3 怎么用

默认开启了

3.3 Image Minimizer

3.3-1 为什么

开发项目引用了较多的图片,那么图片体积会比较大,将来请求需求比较慢,可以对图片进行压缩,减少图片体积(在线链接图片就不需要,只有本地静态图片才需要压缩)

3.3-2 是什么

Image Minimizer-webpack-plugin:用来压缩图片的插件

3.3-3 怎么用

npm install image-minimizer-webpack-plugin --save-dev

**推荐用于无损优化的 imagemin 插件**
npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo --save-dev

**推荐用于有损优化的 imagemin 插件**
npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo --save-dev
对于`imagemin-svgo`v9.0.0+ 需要使用 svgo[配置](https://github.com/svg/svgo#configuration)


const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const { extendDefaultPlugins } = require("svgo");

module.exports = {
  module: {
    rules: [
      {
        test: /.(jpe?g|png|gif|svg)$/i,
        type: "asset",
      },
    ],
  },
  plugins: [
    new ImageMinimizerPlugin({
      minimizerOptions: {
        // Lossless optimization with custom option
        // Feel free to experiment with options for better result for you
        plugins: [
          ["gifsicle", { interlaced: true }],
          ["jpegtran", { progressive: true }],
          ["optipng", { optimizationLevel: 5 }],
          // Svgo configuration here https://github.com/svg/svgo#configuration
          [
            "svgo",
            {
              plugins: extendDefaultPlugins([
                {
                  name: "removeViewBox",
                  active: false,
                },
                {
                  name: "addAttributesToSVGElement",
                  params: {
                    attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],
                  },
                },
              ]),
            },
          ],
        ],
      },
    }),
  ],
};

4.优化代码运行性能

4.1 Code Split

4.1-1 为什么

打包代码时会将所有js文件打包到一个文件中去,体积太大了,我们如果只要渲染首页,就应该只加载首页的js文件,其他文件不应该加载。 所以我们需要将打包生成的文件进行代码分割,生成多个js文件,渲染哪个页面就只加载某个js文件,这样加载的资源就少,速度更快。

4.1-2 是什么

代码分割主要做了两件事:

1、分割文件:将打包生成的文件进行分割,生成多个js文件

2、按需加载:需要哪个文件加载哪个文件

4.1-3 怎么用

代码分割实现方式有不同的方式

1、多入口

image.png

module.exports = {
  entry: {
    // 多入口
    index: './index.js',
    home: './home.js',
   },
   output: {
     filename: '[name].[contenthash:4].js'
   }
}

方式二 防止重复

作用

  1. 将node_modules中的代码单独打包一个chunk最终输出
  2. 自动分析多入口chunk中,如果有公共的问题件,会打包成单独一个chunk
module.exports = {
  optimization: {
    splitChunks: {
        chunks: 'all'
    }
  }
}

方式三 按需加载,动态导入

import动态引入语法,能将文件单独打包

import(/* webpackChunkName: 'test' */ './test.js').then(()=>{
    // 加载成功
}).catch(()=>{
    // 加载失败
})

4.2 Preload/Prefetch

4.2-1 为什么

想在浏览器空闲时,加载后续使用的资源,我们就需要用上preload或prefetch技术

4.2-2 是什么

Preload:告诉浏览器立即加载资源

Prefetch:告诉浏览器在空闲时才开始加载资源

image.png

4.2-3 怎么用

1、懒加载 npm install --save-dev preload-webpack-plugin

当文件需要时才加载 new PreloadWebpackPlugin({ rel:'preload', as:'script', })

2、预加载

prefetch 会在使用前,提前加载js文件

正常加载可以认为是并行加载(同一时间加载多个文件) 预加载 prefetch:等其他资源加载完毕,浏览器空闲了,再偷偷加载资源

NetWork Cache

filename: "[name].[contenthash:10].css", 可以看到,我们的main.js还是之前的包,所以这样一来,我们再更新版本的时候,没有修改到的文件缓存仍然存在 contenthash顾名思义,就是取的文件内容生成的hash值,内容变了hash就变了;而hash是每次webpack打包生成的hash值,所以,我们在提取分类的时候,所有提取出去的文件名都应该用contenthash

4.3 Core-js

4.3-1 为什么

能将es6得一些语法进行编译转换,比如箭头函数 ...运算符等,对于async函数 promise对象 数组的一些方法(includes)他没办法处理,所以对于js代码(Babel处理过后)仍然存在兼容性,一旦遇到低版本浏览器会直接报错,我们就需要将js得姜戎i选哪个问题彻底解决

4.3-2 是什么

core-js是专门用来做es6以及以上得api得polyfill

polyfill翻译过来叫做垫片/补丁,就是用社区上提供的一段代码,让我们在不兼容某些特性的浏览器上使用该特性

4.3-3 怎么用

npm i core-js
//手动全部引入 会将所有将兼容代码全部引入 体积太大 
import 'core-js';
//手动按需引入
import 'core-js/es/promise';
//自动按需引入在babel配置文件里配置
module.exports = {
  // 智能预设:能够编译ES6语法
  presets: [
    ["@babel/preset-env",
    {
      useBuiltIns:'usage',//按需加载自动引入
      corejs:3,
    }
  ]
  ],
};

4.4 PWA

4.4-1 为什么

开发web app项目 项目一旦处于网路离线情况就没法访问 我们希望给项目提供离线体验

4.4-2 是什么

渐进式网络应用程序(progressive web application - PWA)是一种可以提供类似于native app(原生应用程序)体验的web app的技术 其中最重要的是 在离线时应用程序能够继续运行功能 内部通过Service Workers技术实现的

4.4-3 怎么用

npm install workbox-webpack-plugin --save-dev

const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const WorkboxPlugin = require('workbox-webpack-plugin');

  module.exports = {
    plugins: [
     new WorkboxPlugin.GenerateSW({
       // 这些选项帮助快速启用 ServiceWorkers
       // 不允许遗留任何“旧的” ServiceWorkers
       clientsClaim: true,
       skipWaiting: true,
     }),
    ],
  };
  
index.js

if ('serviceWorker' in navigator) {
   window.addEventListener('load', () => {
     navigator.serviceWorker.register('/service-worker.js').then(registration => {
       console.log('SW registered: ', registration);
     }).catch(registrationError => {
       console.log('SW registration failed: ', registrationError);
     });
   });
 }

image.png