webpack高级优化

158 阅读5分钟

提高开发体验:sourceMap

它会生成一个xxx.map文件,里面包含源代码和构建后的代码每一行和每一列映射的关系。当构建后的代码报错了,它可以通过这个map文件,找到对应的源文件的位置,然后让浏览器显示出来。开发和生产模式下,都默认显示错误的行号,不暴露源代码文件。

1.开发模式配置devtool为'eval-source-map'的好处:

如果不配置,代码报错后显示的文件是打包后的文件,行号会和源代码不一致,不利于寻找错误的原因。配置后则点击错误信息会显示源文件,行号也对应上。

1.png

2.如何设置:

配置webpack.config.js文件

module.exports = {
  // 显示正确的行号,暴露源代码
  devtool:'eval-source-map',
},

生产模式的参数:

  • 'source-map':显示正确的行号,暴露源代码
  • 'nosources-source-map':显示正确的行号,不暴露源代码,折中的方法

3.注意:

出于安全性考虑,发布项目时一般不设置devtool或设置为'nosources-source-map',防止源代码泄露。

更多详情查看:Devtool | webpack 中文文档 | webpack 中文文档 | webpack 中文网 (webpackjs.com)

提升打包速度

HMR

在webpack4及以下的版本中,当我们修改了某个模块的代码,会默认将所有模块重新编译打包,速度很慢。另外,在vue,react等脚手架,它们所有模块都配置好了HMR。

1.是什么?

全称:HotModuleReplacement,又叫热模块替换。设置HMR后,更改代码,只会更新某个模块,而无需加载整个页面。

2.怎么用?

CSS样式经过style-loader处理后,已经开启了HMR,而js还没有,需要手动配置。

在入口文件的尾部添加如下代码

if (module.hot) {
  module.hot.accept("./js/count");
  module.hot.accept("./js/sum");
}

当修改count文件后

1 1.png

OneOf

打包时,每个文件都会经过所有loader,这比较耗时。

1.是什么?

顾名思义,就是每个文件只匹配一个loader,剩下的就不匹配了

2.怎么用?

rules: [
      {
        oneOf: [
          {
            test: /\.css$/,
            use: ["style-loader", "css-loader"],
          },
        ],
      },
    ],

Include-Exclude

开发时,我们经常会用到第三方库和插件,相关的文件被放到node_modules中,这些文件都是编译好的,无需再处理。所以,我们可以跳过这些文件。

1.是什么?

Include:选择处理哪些文件

Exclude:不处理哪些文件

2.怎么用?

{
  test: /\.js$/,
  // node_modules中的js文件不做处理
  // exclude: /(node_modules)/,
  // 只处理src目录下的js文件
  include: path.join(__dirname, "../src"),
  // loader只能写一个loader,而use数组可以写多个loader
  loader: "babel-loader",
},

Eslint和Bable缓存

每次打包js文件,都会经过Eslint检查和Babel编译,速度比较慢。

1.是什么?

缓存第一次Eslint检查和Babel编译的结果,修改js文件只局部更新,提高之后的打包速度

2.怎么用?

开启Babel缓存

{
  test: /\.js$/,
  // node_modules中的js文件不做处理
  // exclude: /(node_modules)/,
  // 只处理src目录下的js文件
  include: path.join(__dirname, "../src"),
  // loader只能写一个loader,而use数组可以写多个loader
  loader: "babel-loader",
  options: {
    // 开启babel缓存
    cacheDirectory: true,
    // 关闭缓存文件压缩
    cacheCompression:false
  }
},

开启Eslint缓存

plugins: [
  new ESLintPlugin({
    // 指定要检查的文件目录
    context: path.join(__dirname, "../src"),
    cache: true,
    cacheLocation:path.join(__dirname,'../node_modules/.cache/eslintCache')
  }),
],

多进程打包

因为一般的项目js代码是最多的,所以我们可以针对Eslint(语法检查),Babel(代码编译)和terser(代码压缩)入手

1.是什么?

开启电脑的多个进程干同一件事,只有项目较大时,打包速度才有明显的提升;项目较小不推荐使用,因为进程的启动需要时间,可能导致打包速度变得更慢。

2.怎么用

下载依赖:

npm i thread-loader -D

获取cpu核数:

const os = require('os');
const threads = os.cpus().length

配置:

Eslint

plugins: [
    new ESLintPlugin({
      // 指定要检查的文件目录
      context: path.join(__dirname, "../src"),
      cache: true,
      cacheLocation: path.join(__dirname, "../node_modules/.cache/eslintCache"),
      // 开启多进程和设置进程数量
      threads,
    }),
  ],

Babel

{
  test: /\.js$/,
  // node_modules中的文件不做处理
  exclude: /(node_modules)/,
  // loader只能写一个loader,而use数组可以写多个loader
  use: [
    {
      // 开启多进程
      loader: "thread-loader",
      options: {
        // 进程数量
        works: threads,
      },
    },
    {
      loader: "babel-loader",
      options: {
        // 开启babel缓存
        cacheDirectory: true,
        // 关闭缓存文件压缩
        cacheCompression: false,
      },
    },
  ],
},

terser

const terserWebpackPlugin = require("terser-webpack-plugin");

// 优化
optimization: {
  minimizer: [
    new terserWebpackPlugin({
      // 开启多进程和设置进程数量
      parallel: threads,
    }),
  ],
},

减少代码体积

TreeShaking

1.作用

用于移除javascript中没有引用的代码

2.怎么用

webpack的生产模式已经默认开启了该功能

Babel runtime

Babel在每个文件都添加了辅助代码,使代码体积过大,可以引入 Babel runtime 作为一个独立模块。

1.作用

避免重复引入,减少代码体积

2.怎么用

下载依赖:

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

配置:

rules: [
  // 'transform-runtime' 插件告诉 Babel
  // 要引用 runtime 来代替注入。
  {
    test: /\.js$/,
    exclude: /(node_modules)/,
    use: {
      loader: 'babel-loader',
      options: {
        presets: ['@babel/preset-env'],
        plugins: ['@babel/plugin-transform-runtime']
      }
    }
  }
]

压缩图片

优化代码运行性能

打包后所有js代码都放在同一个文件中,体积太大了。如果我们打开首页,就应该只加载首页相关的js代码,而不是加载所有js代码。

code split

作用:

  • 将打包后的js文件分割成多个js文件
  • 抽取公共js代码到一个文件中
  • 按需加载,需要哪个js文件就加载哪个js文件

配置:

const path = require("path");
const htmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // 多入口
  entry: {
    app:"./src/app.js",
    index:"./src/index.js",
  },
  output: {
    path: path.join(__dirname, "dist"),
    // [name]对应入口文件名
    filename:"[name].js"
  },
  plugins: [
    new htmlWebpackPlugin({
      template:path.join(__dirname,'public/index.html')
    })
  ],
	optimization: {
	  splitChunks: {
	    chunks: 'all',
	    cacheGroups: {
	      default: {
	        minSize: 0,
	        minChunks: 2,
	        priority: -20,
	        reuseExistingChunk: true,
	      },
	    },
	  },
	},
  mode:"production",
};

按需加载:

document.getElementById("btn").addEventListener("click", function () {
  // 按需加载,动态引入add.js文件,只有点击按钮后才会加载该文件
  import("./add").then(({ add}) => {
    console.log(add(10,10));
  });
});

Preload/Prefetch(预加载)

按需加载后,如果要加载的文件太大,则容易卡顿,给用户不好的体验。我们可以用Preload/Prefetch来解决这个问题。

1.是什么

  • Preload:让浏览器立即加载资源
  • Prefetch:让浏览器空闲时加载资源

2.缺点:兼容性差

3.如何使用

下载依赖

npm install --save-dev @vue/preload-webpack-plugin

// 引入
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');

plugins: [
  new PreloadWebpackPlugin({
    rel: 'prefetch'
  })
]

Core-js

1.作用:彻底解决兼容性问题

2.如何使用

下载依赖:npm i core-js

配置:

*const* PreloadWebpackPlugin = *require*("@vue/preload-webpack-plugin");

plugins: [
    new PreloadWebpackPlugin({
      rel: "prefetch",
    }),
  ],

PWA

1.作用:实现离线访问项目

2.如何使用

下载依赖:npm install workbox-webpack-plugin --save-dev

配置:

const WorkboxPlugin = require('workbox-webpack-plugin');

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

在index中注册service work

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);
     });
   });
 }

下载http-server,通过http-server dist 启动dist目录中的项目