Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能

目录

一、Code Split(代码分割)

1.1 多入口

1.2 多入口提取公共模块

1.3 按需加载,动态导入

1.4 单入口

1.5 统一命名

二、Preload / Prefetch

2.1 简介

2.2 下载包

2.3 配置

三、Network Cache

3.1 文件名生成hash值

3.2 使用hash值命名出现的问题

3.3 原因

3.4 解决

四、解决js兼容性问题CoreJS

4.1 下载包

4.2 手动全部引入

4.3 手动按需引入

4.4 自动按需引入

五、PWA

5.1 下载包

5.2 配置


一、Code Split(代码分割)

代码分割(Code Split)主要做了两件事:

  • 分割文件:将打包生成的文件进行分割,生成多个 js 文件
  • 按需加载:需要哪个文件就加载哪个文件

1.1 多入口

新创建项目演示

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第1张图片

(1)准备

#初始化项目
npm init -y

#下载包
npm i webpack webpack-cli html-webpack-plugin -D

创建app.js 以及 main.js ,这两个js文件只是起到演示多入口演示作用。

(2)webpack.config.js配置文件

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // 单入口
  // entry: './src/main.js',
  // 多入口
  entry: {
    main: "./src/main.js",
    app: "./src/app.js",
  },
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "js/[name].js", // webpack命名方式,[name]以文件名自己命名
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
  mode: "production",
};

(3)打包

npx webpack

可以看到打包后app.js 和 main.js都成功输出

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第2张图片

1.2 多入口提取公共模块

(1)count.js

export default function count(x, y) {
  return x - y;
}

(2)app.js

import  count  from "./count.js";

console.log("app");
console.log(count(5,2));

(3)main.js

import  count  from "./count.js";

console.log("main");
console.log(count(1,1));

(4)webpack.config.js

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  // 单入口
  // entry: './src/main.js',
  // 多入口
  entry: {
    main: "./src/main.js",
    app: "./src/app.js",
  },
  output: {
    path: path.resolve(__dirname, "./dist"),
    filename: "js/[name].js", // webpack命名方式,[name]以文件名自己命名
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
  mode: "production",
  optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all", // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
      // cacheGroups: { // 组,哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重(越大越高)
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
      // 修改配置
      cacheGroups: {
        // 组,哪些模块要打包到一个组
        // defaultVendors: { // 组名
        //   test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
        //   priority: -10, // 权重(越大越高)
        //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
        // },
        default: {
          // 其他没有写的配置会使用上面的默认值
          minSize: 0, // 定义的文件体积太小了,所以要改打包的最小文件体积
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

(5)打包

npx webpack

可以看到公共模块也被单独打包出来

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第3张图片

1.3 按需加载,动态导入

index.html 设置一个按钮以及点击事件来触发

(1)index.html



  
    
    
    
    Webpack
  
  
    
  

(2)app.js

console.log("app");

(3)main.js

console.log("main");

document.getElementById("btn").onclick = function () {
  // 动态导入 --> 实现按需加载
  // 即使只被引用了一次,也会代码分割
  import("./count")
    .then((res) => {
      console.log("模块加载成功", res.default(2, 1));
    })
    .catch((err) => {
      console.log("模块加载失败", err);
    });
};

(4)打包

npx webpack

打包后打开dist下html文件,可以看到只有点击按钮时,才会加载计算模块

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第4张图片

1.4 单入口

webpack.prod.js

module.exports = {
// 省略... 
optimization: {
    // 代码分割配置
    splitChunks: {
      chunks: "all", // 对所有模块都进行分割
    },
  },
...
};

1.5 统一命名

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "../dist"),
    filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
    chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
    assetModuleFilename: "static/media/[name].[hash:10][ext]", // 图片、字体等资源命名方式(注意用hash)
    clean: true,
  },
// 省略....
  //   插件
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/[name].css",
      chunkFilename: "static/css/[name].chunk.css",
    }),
  ],
// 省略....
};

(1)index.html添加计算事件按钮

(2)main.js修改

import sum from "./js/sum";
// 引入css文件
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./stylus/index.styl";

document.getElementById("btn").onclick = function () {
  // webpackChunkName: "count":这是webpack动态导入模块命名的方式
  // "math"将来就会作为[name]的值显示。
  import(/* webpackChunkName: "count" */ "./js/count")
    .then((res) => {
      console.log("模块加载成功", res.default(2, 1));
    })
    .catch((err) => {
      console.log("模块加载失败", err);
    });
};
console.log(sum(1, 2, 3, 4, 5, 6));

(3)打包后可看到对应文件的命名,以及动态导入的js命名

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第5张图片


二、Preload / Prefetch

2.1 简介

在浏览器空闲时间,加载后续需要使用的资源。我需要用上 Preload 或 Prefetch 技术。

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

共同点:

  • 都只会加载资源,并不执行
  • 都有缓存

区别:

  • Preload加载优先级高,Prefetch加载优先级低
  • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源

因此,当前页面优先级高的资源用 Preload 加载;下一个页面需要使用的资源用 Prefetch 加载

存在的问题:兼容性较差,Preload 相对于 Prefetch 兼容性好一点

Preload兼容性:"Preload" | Can I use... Support tables for HTML5, CSS3, etc

Prefetch兼容性:"Prefetch" | Can I use... Support tables for HTML5, CSS3, etc

2.2 下载包

npm i @vue/preload-webpack-plugin -D

2.3 配置

webpack.prod.js配置文件

// 省略...
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");

// 省略...

module.exports = {
// 省略...
  //   插件
  plugins: [
    // plugins的配置
    // 省略...
    new PreloadWebpackPlugin({
      rel: "preload", // preload兼容性更好
      as: "script",
      // rel: 'prefetch' // prefetch兼容性更差
    }),
  ],
// 省略...
};

打包后,查看dist目录下index.html文件

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第6张图片


三、Network Cache

3.1 文件名生成hash值

在开发时,会对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了。

但由于前后输出的文件名都是一样的 —— 都叫main.js。这样的话,一旦发布新版本,由于文件名没有发生变化,这样浏览器会直接读取缓存,而不会加载新资源,从而导致项目无法更新。

因此需要确保更新前后文件名不一样,这样才能做缓存。

一般将文件名生成hash值

  • fullhash(webpack4 是 hash)

每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。

  • chunkhash

根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。

  • contenthash

根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。

以webpack.prod.js举例

// 省略...

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "../dist"),
    // [contenthash:8]使用contenthash,取8位长度
    filename: "static/js/[name].[contenthash:8].js", // 入口文件打包输出资源命名方式
    chunkFilename: "static/js/[name].[contenthash:8].chunk.js", // 动态导入输出资源命名方式
    assetModuleFilename: "static/media/[name].[hash:10][ext]", // 图片、字体等资源命名方式(注意用hash)
    clean: true,
  },
  module: {
    rules: [
      // loader的配置
      // 省略...
    ],
  },
  //   插件
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"),
    }),
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      // [contenthash:8]使用contenthash,取8位长度
      filename: "static/css/[name].[contenthash:8].css",
      chunkFilename: "static/css/[name].[contenthash:8].chunk.css",
    }),
    // new CssMinimizerPlugin(),
    new PreloadWebpackPlugin({
      rel: "preload",
      as: "script",
    }),
  ],
  optimization: {
    splitChunks: {
      chunks: "all",
    },
  },
  //   模式
  mode: "production",
  devtool: "source-map",
};

3.2 使用hash值命名出现的问题

当修改 count.js 文件再重新打包的时候,因为 contenthash 原因,count.js 文件 hash 值发生了变化。间接地导致main.js的hash值也发生了变化,导致缓存失效。

3.3 原因

  • 更新前:count.xxx.js, main.js 引用的 count.xxx.js
  • 更新后:count.yyy.js, main.js 引用的 count.yyy.js,文件名发生了变化,间接导致 main.js 也发生了变化

3.4 解决

将 hash 值单独保管在一个 runtime 文件中。

runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。

webpack.prod.js配置文件中

// 省略...

module.exports = {
  // 省略...
  optimization: {
    splitChunks: {
      chunks: "all",
    },
    // 提取runtime文件
    runtimeChunk: {
      name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则
    },
  },
  // 省略...
};

打包后,可以看到dist下生成了对应的文件,且当修改count.js文件后重新打包,只有runtime和count的文件命名中的hash值会发生变化

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第7张图片


四、解决js兼容性问题CoreJS

@babel/preset-env 智能预设处理兼容性问题,能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,是没办法处理的。

core-js 是专门用来做 ES6 以及以上 API 的 polyfill。  

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

在main.js中使用promise语法

import sum from "./js/sum";
// 引入css文件
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./stylus/index.styl";

document.getElementById("btn").onclick = function () {
  // webpackChunkName: "count":这是webpack动态导入模块命名的方式
  // "math"将来就会作为[name]的值显示。
  import(/* webpackChunkName: "count" */ "./js/count")
    .then((res) => {
      console.log("模块加载成功", res.default(2, 1));
    })
    .catch((err) => {
      console.log("模块加载失败", err);
    });
};
console.log(sum(1, 2, 3, 4, 5, 6));

// 添加promise代码
const promise = Promise.resolve();
promise.then(() => {
  console.log("hello promise");
});

4.1 下载包

npm i core-js

4.2 手动全部引入

  • main.js
import "core-js";

4.3 手动按需引入

  • main.js
import "core-js/es/promise";

4.4 自动按需引入

@babel/preset-env · Babel (babeljs.io)

  • babel.config.js
module.exports = {
  // 预设
  presets: [
    [
      "@babel/preset-env",
      // 按需加载core-js的polyfill
      {
        useBuiltIns: "usage", // 按需加载自动引入
        corejs: { version: "3", proposals: true },
      },
    ],
  ],
};

打包后,可以看到将corejs单独打包了出来

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第8张图片


五、PWA

渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。

其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。

内部通过 Service Workers 技术实现的。

5.1 下载包

渐进式网络应用程序 | webpack 中文文档 (docschina.org)

npm i workbox-webpack-plugin -D

5.2 配置

  • webpack.prod.js中添加代码
// 省略...
// 添加 workbox-webpack-plugin 插件
const WorkboxPlugin = require("workbox-webpack-plugin");

// 省略...

module.exports = {
 // 省略...
  //   插件
  plugins: [
    // plugins的配置
    // 省略...

    new WorkboxPlugin.GenerateSW({
      // 这些选项帮助快速启用 ServiceWorkers
      // 不允许遗留任何“旧的” ServiceWorkers
      clientsClaim: true,
      skipWaiting: true,
    }),
  ],
// 省略...
};
  • main.js 添加代码,注册 Service Worker
 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);
     });
   });
 }

打包后,可以看见生成了service-worker.js文件

Webpack5相关知识点(二)- 优化 (3)- 优化代码运行性能_第9张图片

你可能感兴趣的:(打包工具,webpack,javascript,前端)