webpack5(下)

405 阅读10分钟

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

5.2 生产环境性能优化

5.2.1 优化打包构建速度

5.2.1.1 oneOf

oneOf:匹配到 loader 后就不再向后进行匹配,优化生产环境的打包构建速度

代码:

module: {
  rules: [
    {
      // js 语法检查
      test: /.js$/,
      exclude: /node_modules/,
      // 优先执行
      enforce: 'pre',
      loader: 'eslint-loader',
      options: {
        fix: true
      }
    },
    {
      // oneOf 优化生产环境的打包构建速度
      // 以下loader只会匹配一个(匹配到了后就不会再往下匹配了)
      // 注意:不能有两个配置处理同一种类型文件(所以把eslint-loader提取出去放外面)
      oneOf: [
        {
          test: /.css$/,
          use: [...commonCssLoader]
        },
        {
          test: /.less$/,
          use: [...commonCssLoader, 'less-loader']
        },
        {
          // js 兼容性处理
          test: /.js$/,
          exclude: /node_modules/,
          loader: 'babel-loader',
          options: {
            presets: [
              [
                '@babel/preset-env',
                {
                  useBuiltIns: 'usage',
                  corejs: {version: 3},
                  targets: {
                    chrome: '60',
                    firefox: '50'
                  }
                }
              ]
            ]
          }
        },
        {
          test: /.(jpg|png|gif)/,
          loader: 'url-loader',
          options: {
            limit: 8 * 1024,
            name: '[hash:10].[ext]',
            outputPath: 'imgs',
            esModule: false
          }
        },
        {
          test: /.html$/,
          loader: 'html-loader'
        },
        {
          exclude: /.(js|css|less|html|jpg|png|gif)/,
          loader: 'file-loader',
          options: {
            outputPath: 'media'
          }
        }
      ]
    }
  ]
},

5.2.1.2 babel 缓存

babel 缓存:类似 HMR,将 babel 处理后的资源缓存起来(哪里的 js 改变就更新哪里,其他 js 还是用之前缓存的资源),让第二次打包构建速度更快

代码:

{
  test: /.js$/,
  exclude: /node_modules/,
  loader: 'babel-loader',
  options: {
    presets: [
      [
        '@babel/preset-env',
        {
          useBuiltIns: 'usage',
          corejs: { version: 3 },
          targets: {
            chrome: '60',
            firefox: '50'
          }
        }
      ]
    ],
    // 开启babel缓存
    // 第二次构建时,会读取之前的缓存
    cacheDirectory: true
  }
},

文件资源缓存

文件名不变,就不会重新请求,而是再次用之前缓存的资源

1.hash: 每次 wepack 打包时会生成一个唯一的 hash 值。

​ 问题:重新打包,所有文件的 hsah 值都改变,会导致所有缓存失效。(可能只改动了一个文件)

2.chunkhash:根据 chunk 生成的 hash 值。来源于同一个 chunk的 hash 值一样

​ 问题:js 和 css 来自同一个chunk,hash 值是一样的(因为 css-loader 会将 css 文件加载到 js 中,所以同属于一个chunk)

3.contenthash: 根据文件的内容生成 hash 值。不同文件 hash 值一定不一样(文件内容修改,文件名里的 hash 才会改变)

修改 css 文件内容,打包后的 css 文件名 hash 值就改变,而 js 文件没有改变 hash 值就不变,这样 css 和 js 缓存就会分开判断要不要重新请求资源 --> 让代码上线运行缓存更好使用

output:{
   filename:'[name:contenthash:8].[ext]'
}

5.2.1.3 多进程打包

多进程打包:某个任务消耗时间较长会卡顿,多进程可以同一时间干多件事,效率更高。

优点是提升打包速度,缺点是每个进程的开启和交流都会有开销(babel-loader消耗时间最久,所以使用thread-loader针对其进行优化)

{
  test: /.js$/,
  exclude: /node_modules/,
  use: [
    /* 
      thread-loader会对其后面的loader(这里是babel-loader)开启多进程打包。 
      进程启动大概为600ms,进程通信也有开销。(启动的开销比较昂贵,不要滥用)
      只有工作消耗时间比较长,才需要多进程打包
    */
    {
      loader: 'thread-loader',
      options: {
        workers: 2 // 进程2个
      }
    },
    {
      loader: 'babel-loader',
      options: {
        presets: [
          [
            '@babel/preset-env',
            {
              useBuiltIns: 'usage',
              corejs: { version: 3 },
              targets: {
                chrome: '60',
                firefox: '50'
              }
            }
          ]
        ],
        // 开启babel缓存
        // 第二次构建时,会读取之前的缓存
        cacheDirectory: true
      }
    }
  ]
},

5.2.1.4 externals

externals:让某些库不打包,通过 cdn 引入

webpack.config.js 中配置:

externals: {
  // 拒绝jQuery被打包进来(通过cdn引入,速度会快一些)
  // 忽略的库名 -- npm包名
  jquery: 'jQuery'
}

需要在 index.html 中通过 cdn 引入:

<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>

5.2.1.5 dll

dll:让某些库单独打包,后直接引入到 build 中。可以在 code split 分割出 node_modules 后再用 dll 更细的分割,优化代码运行的性能。

在根目录添加webpack.dll.js 配置:(将 jquery 单独打包)

/*
  node_modules的库会打包到一起,但是很多库的时候打包输出的js文件就太大了
  使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包
  当运行webpack时,默认查找webpack.config.js配置文件
  需求:需要运行webpack.dll.js文件
    --> webpack --config webpack.dll.js(运行这个指令表示以这个配置文件打包)
*/
const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    // 最终打包生成的[name] --> jquery
    // ['jquery] --> 要打包的库是jquery
    vendor: ['jquery']
  },
  output: {
    // 输出出口指定
    filename: '[name].dll.js', // name就是jquery
    path: resolve(__dirname, 'dll'), // 打包到dll目录下
    library: '[name]_[hash]', // 打包的库里面向外暴露出去的内容叫什么名字
  },
  plugins: [
    // 打包生成一个manifest.json --> 提供jquery的映射关系(告诉webpack:jquery之后不需要再打包和暴露内容的名称)
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 映射库的暴露的内容名称
      path: resolve(__dirname, 'dll/[name]-manifest.json') // 输出文件路径
    })
  ],
  mode: 'production'
};

webpack.config.js 配置:(告诉 webpack 不需要再打包 jquery,并将之前打包好的 jquery 跟其他打包好的资源一同输出到 build 目录下)

// 引入插件
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');

// plugins中配置:
plugins: [
  new HtmlWebpackPlugin({
    template: './src/index.html'
  }),
  // 告诉webpack哪些库不参与打包,同时使用时的名称也得变
  new webpack.DllReferencePlugin({
    manifest: resolve(__dirname, 'dll/vendor-manifest.json')
  }),
  // 将某个文件打包输出到build目录下,并在html中自动引入该资源
  new AddAssetHtmlWebpackPlugin({
    filepath: resolve(__dirname, 'dll/jquery.js')
  })
],

然后在package.json文件中添加快捷命令(build:dll)

"scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
    "build:dll": "webpack --config webpack.dll.config.js"
  },

最后打包的时候首先执行npm run build:dll命令会在打包目录下生成 vendor-manifest.json 文件与 vendor.dll.js 文件。
打包dll的时候,Webpack会将所有包含的库做一个索引,写在一个manifest文件中,而引用dll的代码(dll user)在打包的时候,只需要读取这个manifest文件,就可以了。

5.2.2 优化代码运行的性能

5.2.2.1 缓存

5.2.2.2 tree shaking(树摇)

tree shaking:去除无用代码

前提:1. 必须使用 ES6 模块化 2. 开启 production 环境 (这样就自动会把无用代码去掉)

作用:减少代码体积

在 package.json 中配置:

"sideEffects": false 表示所有代码都没有副作用(都可以进行 tree shaking)

这样会导致的问题:可能会把 css / @babel/polyfill 文件干掉(副作用)

所以可以配置:"sideEffects": ["*.css", "*.less"] 不会对css/less文件tree shaking处理

5.2.2.3 code split(代码分割)

代码分割。将打包输出的一个大的 bundle.js 文件拆分成多个小文件,这样可以并行加载多个文件,比加载一个文件更快。

1.多入口拆分

entry: {
    // 多入口:有一个入口,最终输出就有一个bundle
    index: './src/js/index.js',
    test: './src/js/test.js'
  },
  output: {
    // [name]:取文件名
    filename: 'js/[name].[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },

2.optimization:

optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  • 将 node_modules 中的代码单独打包(大小超过30kb)
  • 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk(比如两个模块中都引入了jquery会被打包成单独的文件)(大小超过30kb)

3.import 动态导入语法:

/*
  通过js代码,让某个文件被单独打包成一个chunk
  import动态导入语法:能将某个文件单独打包(test文件不会和index打包在同一个文件而是单独打包)
  webpackChunkName:指定test单独打包后文件的名字
*/
import(/* webpackChunkName: 'test' */'./test')
  .then(({ mul, count }) => {
    // 文件加载成功~
    // eslint-disable-next-line
    console.log(mul(2, 5));
  })
  .catch(() => {
    // eslint-disable-next-line
    console.log('文件加载失败~');
  });

5.2.2.4 lazy loading(懒加载/预加载)

1.懒加载:当文件需要使用时才加载(需要代码分割)。但是如果资源较大,加载时间就会较长,有延迟。

2.正常加载:可以认为是并行加载(同一时间加载多个文件)没有先后顺序,先加载了不需要的资源就会浪费时间。

3.预加载 prefetch(兼容性很差):会在使用之前,提前加载。等其他资源加载完毕,浏览器空闲了,再偷偷加载这个资源。这样在使用时已经加载好了,速度很快。所以在懒加载的基础上加上预加载会更好。

代码:

document.getElementById('btn').onclick = function() {
  // 将import的内容放在异步回调函数中使用,点击按钮,test.js才会被加载(不会重复加载)
  // webpackPrefetch: true表示开启预加载
  import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
    console.log(mul(4, 5));
  });
  import('./test').then(({ mul }) => {
    console.log(mul(2, 5))
  })
};

5.2.2.5 pwa(离线可访问技术)

pwa:离线可访问技术(渐进式网络开发应用程序),使用 serviceworker 和 workbox 技术。优点是离线也能访问,缺点是兼容性差。

webpack.config.js 中配置:

const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); // 引入插件

// plugins中加入:
new WorkboxWebpackPlugin.GenerateSW({
  /*
    1. 帮助serviceworker快速启动
    2. 删除旧的 serviceworker

    生成一个 serviceworker 配置文件
  */
  clientsClaim: true,
  skipWaiting: true
})

index.js 中还需要写一段代码来激活它的使用:

/*
  1. eslint不认识 window、navigator全局变量
    解决:需要修改package.json中eslintConfig配置
    "env": {
      "browser": true // 支持浏览器端全局变量
    }
  2. sw代码必须运行在服务器上
    --> nodejs
    或-->
      npm i serve -g
      serve -s build 启动服务器,将打包输出的build目录下所有资源作为静态资源暴露出去
*/
if ('serviceWorker' in navigator) { // 处理兼容性问题
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js') // 注册serviceWorker
      .then(() => {
        console.log('sw注册成功了~');
      })
      .catch(() => {
        console.log('sw注册失败了~');
      });
  });
}

七、Webpack5 介绍和使用

此版本重点关注以下内容:

  • 通过持久缓存提高构建性能.
  • 使用更好的算法和默认值来改善长期缓存.
  • 通过更好的树摇和代码生成来改善捆绑包大小.
  • 清除处于怪异状态的内部结构,同时在 v4 中实现功能而不引入任何重大更改.
  • 通过引入重大更改来为将来的功能做准备,以使我们能够尽可能长时间地使用 v5.

下载

npm i webpack@next webpack-cli -D

自动删除 Node.js Polyfills

早期,webpack 的目标是允许在浏览器中运行大多数 node.js 模块,但是模块格局发生了变化,许多模块用途现在主要是为前端目的而编写的。webpack <= 4 附带了许多 node.js 核心模块的 polyfill,一旦模块使用任何核心模块(即 crypto 模块),这些模块就会自动应用。

尽管这使使用为 node.js 编写的模块变得容易,但它会将这些巨大的 polyfill 添加到包中。在许多情况下,这些 polyfill 是不必要的。

webpack 5 会自动停止填充这些核心模块,并专注于与前端兼容的模块。

迁移:

  • 尽可能尝试使用与前端兼容的模块。
  • 可以为 node.js 核心模块手动添加一个 polyfill。错误消息将提示如何实现该目标。

Chunk 和模块 ID

添加了用于长期缓存的新算法。在生产模式下默认情况下启用这些功能。

chunkIds: "deterministic", moduleIds: "deterministic"

Chunk ID

你可以不用使用 import(/* webpackChunkName: "name" */ "module") 在开发环境来为 chunk 命名,生产环境还是有必要的

webpack 内部有 chunk 命名规则,不再是以 id(0, 1, 2)命名了

Tree Shaking

  1. webpack 现在能够处理对嵌套模块的 tree shaking
// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from './inner';
export { inner };

// user.js
import * as module from './module';
console.log(module.inner.a);

在生产环境中, inner 模块暴露的 b 会被删除

  1. webpack 现在能够多个模块之前的关系
import { something } from './something';

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}

当设置了"sideEffects": false时,一旦发现test方法没有使用,不但删除test,还会删除"./something"

  1. webpack 现在能处理对 Commonjs 的 tree shaking

Output

webpack 4 默认只能输出 ES5 代码

webpack 5 开始新增一个属性 output.ecmaVersion, 可以生成 ES5 和 ES6 / ES2015 代码.

如:output.ecmaVersion: 2015

SplitChunk

// webpack4
minSize: 30000;
// webpack5
minSize: {
  javascript: 30000,
  style: 50000,
}

Caching

// 配置缓存
cache: {
  // 磁盘存储
  type: "filesystem",
  buildDependencies: {
    // 当配置修改时,缓存失效
    config: [__filename]
  }
}

缓存将存储到 node_modules/.cache/webpack

监视输出文件

之前 webpack 总是在第一次构建时输出全部文件,但是监视重新构建时会只更新修改的文件。

此次更新在第一次构建时会找到输出文件看是否有变化,从而决定要不要输出全部文件。

默认值

  • entry: "./src/index.js
  • output.path: path.resolve(__dirname, "dist")
  • output.filename: "[name].js"