【 webpack】面试题

lxf2023-07-14 11:20:01

开启编程成长之旅!这是我参与「编程 · 2 月更文挑战」的第 26 天,点击查看活动详情

原文来自我的个人博客

1. 什么是 Webpack?

1.1 回答

webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。它主要解决的问题是将多个模块(Module)打包成一个或多个文件,并且在这个过程中还支持一些特性,如代码分离、文件压缩等。

我们先将着重点落在 静态模块打包工具 上,为什么是 静态模块打包工具

是因为它可以将多个模块JavaScript 文件CSS 文件图片 等)打包成一个或多个静态资源文件。静态资源文件包含了应用程序中的所有依赖关系和逻辑,可以直接在浏览器中加载和运行。

1.2 webpack 打包示例

下面我们就来演示一个最简单的 webpack 打包示例:

  1. 首先我们创建一个目录,初始化 npm,然后 在本地安装 webpack,接着安装 webpack-cli(注意:此工具用于在命令行中运行 webpack):
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev
  1. 现在,我们将创建以下目录结构、文件和内容:

【 webpack】面试题

add.js

export function add(n1, n2) {
  return n1 + n2;
}

sub.js

export function sub(n1, n2) {
  return n1 - n2;
}

index.js

import { add } from "./add";
import { sub } from "./sub";

console.log(add(1, 2));
console.log(sub(1, 2));

可以看到,我们做的仅仅定义了两个工具文件 a.jsb.js,并在 index.js 中导入并且调用了两个函数。

  1. 接着,我们执行 npx webpack 命令进行打包,打包结果如下图所示:

【 webpack】面试题

【 webpack】面试题

可以这样说,执行 npx webpack,会将我们的脚本 src/index.js 作为 入口起点,也会生成 dist/main.js 作为 输出。Node 8.2/npm 5.2.0 以上版本提供的 npx 命令,可以运行在初次安装的 webpack package 中的 webpack 二进制文件(即 ./node_modules/.bin/webpack)。

1.3 模块

在上面的例子中,我们仅仅演示了 es6 的模块化导入导出,事实上除了 importexportwebpack 还能够很好地支持多种其他模块语法比如 CommonJSAMD

1.4 配置文件

webpack v4 中,可以无须任何配置,然而大多数项目会需要很复杂的设置,这时候就需要一个配置文件来拯救我们了,因为这比在 terminal(终端) 中手动输入大量命令要高效的多

  1. 创建 webpack-demo/webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
};
  1. 执行打包命令 npx webpack --config webpack.config.js

【 webpack】面试题

可以看到生成的新的文件名为 bundle.js 说明我们的配置文件已经生效。

事实上,如果 webpack.config.js 存在,则 webpack 命令将默认选择使用它,所以我们依然可以使用 npx webpack 进行打包。这里使用 --config 选项只是表明可以传递任何名称的配置文件,这对于需要拆分成多个文件的复杂配置是非常有用的。

1.5 npm scripts

考虑到用 CLI 这种方式来运行本地的 webpack 副本并不是特别方便,我们可以设置一个快捷方式。调整 package.json 文件,添加一个 npm script

 "build": "webpack"

现在,可以使用 npm run build 命令,来替代我们之前使用的 npx 命令。注意,使用 npm scripts,我们可以像使用 npx 那样通过模块名引用本地安装的 npm packages。这是大多数基于 npm 的项目遵循的标准,因为它允许所有贡献者使用同一组通用脚本。

1.6 总结

在这一题中,我们使用了一个最简单的案例来解释了 webpack 是什么东西,它的本质就是一个模块化的打包工具,我想大家应该对它的概念都能有一个基本的了解了。

2. Webpack 的核心概念有哪些?

Webpack 的核心概念主要包括以下几个:

  1. 入口 Entry
  2. 输出 Output
  3. 加载器 Loader
  4. 插件 Plugin
  5. 模式 Mode
  6. 浏览器兼容性 Browser compatibility
  7. 环境 Environment
  8. 代码块 Chunk
  9. 模块 Module

2.1 入口 Entry

入口起点(entry point) 定义 webpack 从哪个文件开始构建依赖关系图,进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

它的默认值是 ./src/index.js,但是我们可以通过在配置文件中配置 entry属性来指定一个(或多个)不同的入口起点。例如:

webpack.config.js

module.exports = {
  entry: './path/to/my/entry/file.js',
};

2.2 输出 Output

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

你可以通过在配置中指定一个 output 字段,来配置这些处理过程:

const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js',
  },
};

在上面的示例中,我们通过 output.filenameoutput.path 属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。

2.3 加载器 Loader

webpack 只能理解 JavaScriptJSON 文件。loader 的作用就是让让 webpack 能够去处理其他类型的文件,并将它们转换为 JavaScript 文件。

webpack 的配置中,loader 有两个属性:

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader

webpack.config.js

const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
};

以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:testuse。这告诉 webpack 编译器(compiler) 如下信息:

“嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。”

2.4 插件 Plugin

webpack 中 loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化资源管理注入环境变量。插件目的在于解决 loader 无法实现的其他事

想要使用一个插件,只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 用于访问内置插件

module.exports = {
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

在上面的示例中,html-webpack-plugin 为应用程序生成一个 HTML 文件,并自动将生成的所有 bundle 注入到此文件中。

2.5 模式 Mode

Webpack 有三种模式:developmentproductionnone。通过设置不同的模式,可以启用不同的内置优化。

2.6 浏览器兼容性 Browser compatibility

Webpack 支持所有符合 ES5 标准 的浏览器(不支持 IE8 及以下版本)。

2.7 环境 Environment

Webpack 5 运行于 Node.js v10.13.0+ 的版本。

2.8 代码块 Chunk

chunk 可以理解为 webpack 构建输出的一个文件,通常包含了在构建过程中生成的一些代码块。webpack 将所有相关的模块打包在一起,以此生成一个或多个chunk 。这些 chunk 可以是 JavaScript文件、CSS文件、图片等,也可以是异步加载的代码块。

webpack 将代码分割成多个 chunk 的主要目的是优化应用程序的性能。通过将代码拆分成多个小块,webpack 可以减少初始加载时间,并提高应用程序的性能。

2.9 模块 Module

Webpack 会将所有的文件都看作一个个模块,每个模块都有自己的依赖关系,Webpack 会根据这些依赖关系构建出一个依赖关系图。

3. 请解释一下 Webpack 的打包原理

Webpack 的打包原理可以简单地概括为以下几个步骤:

  1. 解析入口文件Webpack 通过指定的入口文件开始打包,从该文件开始分析和解析整个项目的依赖关系。

  2. 依赖分析Webpack 根据模块之间的依赖关系,递归地分析和收集所有需要打包的模块,包括 JavaScriptCSS、图片等资源文件。

  3. 模块转换Webpack 在解析模块的过程中,会根据不同的模块类型,将它们转换成 JavaScript 代码,以便在浏览器中执行。

  4. 生成 ChunkWebpack 会将所有模块打包成一个或多个 Chunk,每个 Chunk 包含了一组模块,以及这些模块之间的依赖关系。

  5. 生成 Bundle:最后,Webpack 会将所有的 Chunk 生成对应的静态资源文件,例如 JavaScript、CSS、图片等,供浏览器加载和执行。

在整个打包过程中,Webpack 提供了很多插件和配置选项,可以帮助我们自定义打包过程,例如代码压缩、分离 CSS 文件、处理图片 等。同时,Webpack 还支持使用各种 Loader 来处理不同类型的模块,例如使用 Babel Loader 来处理 ES6 语法的模块。

3.1 chunk 和 bundle 的区别是什么?

chunkchunkWebpack 打包生成的一个或多个 JavaScript 文件,它包含了一组相关的模块的代码,可以看做是模块的集合。在实际开发中,chunk 可以用于实现代码分割、按需加载、懒加载等功能,从而优化代码的加载和执行,提高应用的性能和用户体验。

bundlebundleWebpack 打包生成的一个或多个静态资源文件,它包含了所有模块的代码,可以看做是代码的最终打包结果。在实际开发中,bundle 可以用于发布到生产环境,供浏览器加载和执行。

简单来说,chunkWebpack 在打包过程中产生的中间文件,bundle 是最终生成的静态资源文件。在代码分割和按需加载的场景下,Webpack 会生成多个 chunk 文件,每个文件包含一部分模块的代码,然后将这些 chunk 文件合并成一个或多个 bundle 文件,以便在浏览器中加载和执行。

4. 如何使用 Webpack 进行代码分割

代码分离(Code Spliting)webpack 一个非常重要的特性,它主要的目的是将代码剥离到不同的 bundle 中,之后我们可以按需加载,或者并行加载这些文件

webpack 常用的代码分离方式有三种

  1. 入口起点:使用 entry 配置手动分离代码;
  2. 防止重复:使用 EntryDependencies 或者 SplitChunksPlugin 去重和分离代码:
  3. 动态导入:通过模块的内联函数用来分离代码

4.1 方式一:多入口起点

这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们将会解决这些问题。

先来看看如何从 main bundle 中分离 another module(另一个模块)

4.1.1 没有代码分离时

创建一个小的 demo

  1. 首先我们创建一个目录,初始化 npm,然后在本地安装 webpackwebpack-cliloadsh
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli lodash --save-dev
  1. 创建 src/index.js
import _ from "lodash";

console.log(_);
  1. 创建 src/another-module.js
import _ from 'lodash';

console.log(_);
  1. 创建 webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
};
  1. package.json 中添加命令:
"scripts": {
  "build": "webpack"
},
  1. 执行命令进行打包:
npm run build
  1. 生成如下构建结果:

【 webpack】面试题

可以看到此时生成了一个 554KBmain.js 文件

4.1.2 有代码分离时

接下来我们从 main bundle 中分离出 another module(另一个模块)

  1. 修改 webpack.config.js
const path = require("path");
module.exports = {
  mode: "development",
-  entry: './src/index',
+  entry: {
+    index: './src/index',
+    another: './src/another-module.js'
+  },
  output: {
    path: path.resolve(__dirname, "dist"),
-    filename: "main.js",
+    filename: "[name].main.js",
  },
};
  1. 打包,生成如下构建结果:

【 webpack】面试题

我们发现此时已经成功打包出 another.bundle.jsindex.bundle.js 两个文件了,但是文件的大小似乎有些问题,怎么两个都是 554KB

正如前面提到的,这种方式存在一些隐患:

  1. 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。
  2. 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。

以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。在下一小节我们将移除重复的模块。

4.1.3 优化:移除重复的模块

在通过多入口分离代码的方式中,我们可以通过配置 dependOn 这个选项来解决重复模块的问题,它的原理就是从两个文件中抽出一个共享的模块,然后再让这两个模块依赖这个共享模块。

  1. 修改 webpack.config.js 配置文件:
 const path = require('path');

 module.exports = {
   mode: 'development',
   entry: {
-   index: './src/index.js',
-    another: './src/another-module.js',
+    index: {
+     import: './src/index.js',
+     dependOn: 'shared',
+   },
+   another: {
+     import: './src/another-module.js',
+     dependOn: 'shared',
+   },
+   shared: ['lodash'],
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };
  1. 打包,生成如下构建结果:

【 webpack】面试题

可以看到 index.mian.jsanother.mian.js 中重复引用的部分被抽离成了 shared.main.js 文件,且 index.mian.jsanother.mian.js 文件大小也变小了。

4.2 方式二:splitChunks 模式

另外一种分包的模式是 splitChunks,它底层是使用 SplitChunksPlugin 来实现的:

SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk

因为该插件 webpack 已经默认安装和集成,所以我们并 不需要单独安装和直接使用该插件;只需要提供 SplitChunksPlugin 相关的配置信息即可

webpack 提供了 SplitChunksPlugin 默认的配置,我们也可以手动来修改它的配置:

  • 比如默认配置中,chunks 仅仅针对于异步(async)请求,我们可以设置为 initial 或者 all

4.2.1 splitChunk 的配置

  1. 1.1.2 的基础上修改 webpack.cofig.js
  const path = require('path');

  module.exports = {
    mode: 'development',
    entry: {
      index: './src/index.js',
      another: './src/another-module.js',
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
+   optimization: {
+     splitChunks: {
+       chunks: 'all',
+     },
+   },
  };
  1. 打包,生成如下构建结果:

【 webpack】面试题

使用 optimization.splitChunks 配置选项之后,现在应该可以看出,index.bundle.jsanother.bundle.js 中已经移除了重复的依赖模块。需要注意的是,插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。

除了 webpack 默认继承的 SplitChunksPlugin 插件,社区中也有提供一些对于代码分离很有帮助的 pluginloader,比如:

  • mini-css-extract-plugin: 用于将 CSS 从主应用程序中分离。

4.2.2 SplitChunks 自定义配置解析

关于 optimization.splitChunks 文档上有很详细的记载,我这里讲你叫几个常用的:

1. Chunks:

  • 默认值是 async
  • 另一个值是 initial,表示对通过的代码进行处理
  • all 表示对同步和异步代码都进行处理

2. minSize

  • 拆分包的大小, 至少为 `minSize;
  • 如果一个包拆分出来达不到 minSize ,那么这个包就不会拆分;

3. maxSize

  • 将大于maxSize的包,拆分为不小于minSize的包;

4. cacheGroups:

  • 用于对拆分的包就行分组,比如一个 lodash 在拆分之后,并不会立即打包,而是会等到有没有其他符合规则的包一起来打包;
  • test 属性:匹配符合规则的包;
  • name 属性:拆分包的 name 属性;
  • filename 属性:拆分包的名称,可以自己使用 placeholder 属性;
  1. 修改 webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  entry: {
    index: "./src/index.js",
    another: "./src/another-module.js",
  },
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
  optimization: {
    splitChunks: {
      chunks: "all",
      // 拆分包的最小体积
      // 如果一个包拆分出来达不到 minSize,那么这个包就不会拆分(会被合并到其他包中)
      minSize: 100,
      // 将大于 maxSize 的包,拆分成不小于 minSize 的包
      maxSize: 10000,

      // 自己对需要拆包的内容进行分组
      cacheGroups: {
        自定义模块的name: {
          test: /node_modules/,
          filename: "[name]_vendors.js",
        },
      },
    },
  },
};
  1. 打包,生成如下构建结果:

【 webpack】面试题

4.3 方式三:动态导入(dynamic import)

另外一个代码拆分的方式是动态导入时,webpack 提供了两种实现动态导入的方式:

  • 第一种,使用 ECMAScript 中的 import() 语法来完成,也是目前推荐的方式;
  • 第二种,使用 webpack 遗留的 require.ensure,目前已经不推荐使用;

动态 import 使用最多的一个场景是懒加载(比如路由懒加载)

4.3.1 import 方式

接着从 1.1.2 小节代码的基础上修改:

  1. 修改 webpack.confg.js
const path = require("path");

module.exports = {
  entry: "./src/index.js",
  mode: "development",
  entry: {
    index: "./src/index.js",
  },
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};
  1. 删除 src/another-module.js 文件

  2. 修改 src/index.js,不再使用 statically import (静态导入) lodash,而是通过 dynamic import(动态导入) 来分离出一个 chunk

const logLodash = function () {
  import("lodash").then(({ default: _ }) => {
    console.log(_);
  });
};

logLodash();

之所以需要 default,是因为 webpack 4 在导入 CommonJS 模块时,将不再解析为 module.exports 的值,而是为 CommonJS 模块创建一个 artificial namespace 对象。

  1. 打包,生成如下构建结果:

【 webpack】面试题

由于 import() 会返回一个 promise,因此它可以和 async 函数一起使用。下面是如何通过 async 函数简化代码:

const logLodash = async function () {
  const { default: _ } = await import("lodash");

  console.log(_);
};

logLodash();

4.3.2 动态导入的文件命名

因为动态导入通常是一定会打包成独立的文件的,所以并不会再 cacheGroups 中进行配置;

它的命名我们通常会在 output 中,通过 chunkFilename 属性来命名:

  1. 修改 webpack.config.js
const path = require("path");

module.exports = {
  entry: "./src/index.js",
  mode: "development",
  entry: {
    index: "./src/index.js",
  },
  output: {
    filename: "[name].bundle.js",
    path: path.resolve(__dirname, "dist"),
+    chunkFilename: "chunk_[name].js""
  },
};

  1. 打包构建:

【 webpack】面试题

如果对打包后的 [name] 不满意,还可以通过 magic comments(魔法注释)来修改:

1, 修改 src/index.js

const logLodash = async function () {
  const { default: _ } = await import(/*webpackChunkName: 'lodash'*/ "lodash");

  console.log(_);
};

logLodash();
  1. 打包构建

【 webpack】面试题

5. 什么是 Tree Shaking?如何使用 Webpack 实现?

什么是 Tree Shaking

  • Tree Shaking 是一个术语,在计算机中表示消除死代码(dead_code);
  • 最早的想法起源于 LISP,用于消除未调用的代码(纯函数无副作用,可以放心的消除,这也是为什么要求我们在进行函数式编程时,尽量使用纯函数的原因之一);
  • 后来 Tree Shaking 也被应用于其他的语言,比如 JavaScriptDart

JavaScriptTree Shaking

  • JavaScript 进行 Tree Shaking 是源自打包工具 rollup
  • 这是因为 Tree Shaking 依赖于 ES Module 的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系);
  • webpack2 正式内置支持了 ES2015 模块,和检测未使用模块的能力;
  • webpack4 正式扩展了这个能力,并且通过 package.jsonsideEffects 属性作为标记,告知 webpack 在编译时,哪里文件可以安全的删除掉;
  • webpack5 中,也提供了对部分 CommonJStree shaking 的支持; ✓ github.com/webpack/cha…

5.1 webpack 实现 Tree Shaking

webpack 实现 Tree Shaking 采用了两种不同的方案:

  1. usedExports:通过标记某些函数是否被使用,之后通过 Terser 来进行优化的;
  2. sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用;

usedExportssideEffects 这两个东西的优化是不同的事情。

引用官方文档的话: The sideEffects and usedExports(more konwn as tree shaking)optimizations are two different things

下面我们分别来演示一下这两个属性的使用

5.1.1 usedExports

  1. 新建一个 webpack-demo
mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli lodash --save-dev
  1. 创建 src/math.js 文件:
export const add = (num1, num2) => num1 + num2;
export const sub = (num1, num2) => num1 - num2;

在这个问价中仅是导出了两个函数方法

  1. 创建 src/index.js 文件:、
import { add, sub } from "./math";

console.log(add(1, 2));

index.js 中 导入了刚刚创建的两个函数,但是只使用了 add

  1. 配置 webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  devtool: false,
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.js",
  },
  optimization: {
    usedExports: true,
  },
};

为了可以看到 usedExports 带来的效果,我们需要设置为 development 模式。因为在 production 模式下,webpack 默认的一些优化会带来很大的影响。

  1. 设置 usedExportstruefalse 对比打包后的代码:

【 webpack】面试题

【 webpack】面试题

仔细观察上面两张图可以发现当设置 usedExports: true 时,sub 函数没有导出了,另外会多出一段注释:unused harmony export mul;这段注释的意义是会告知 Terser 在优化时,可以删除掉这段代码。

这个时候,我们将 minimize 设置 true

【 webpack】面试题

【 webpack】面试题

  • usedExports 设置为 false 时,sub 函数没有被移除掉;
  • usedExports 设置为 true 时,sub 函数有被移除掉;

所以,usedExports 实现 Tree Shaking 是结合 Terser 来完成的。

5.1.2 sideEffects

在一个纯粹的 ESM 模块世界中,很容易识别出哪些文件有副作用。然而,我们的项目无法达到这种纯度,所以,此时有必要提示 webpack compiler 哪些代码是“纯粹部分”。

通过 package.json"sideEffects" 属性,来实现这种方式。

{
  "name": "your-project",
  "sideEffects": false
}

如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack 它可以安全地删除未用到的 export

"side effect(副作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export

如果你的代码确实有一些副作用,可以改为提供一个数组:

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

注意,所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loaderimport 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:

{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}

5.2 CSS 实现 Tree Shaking

上面将的都是关于 JavaScriptTree Shaking ,对于 CSS 同样有对应的 Tree Shaking 操作。

  • 在早期的时候,我们会使用 PurifyCss 插件来完成 CSStree shaking,但是目前该库已经不再维护了(最新更新也是在 4 年前了);

  • 目前我们可以使用另外一个库来完成 CSSTree ShakingPurgeCSS,也是一个帮助我们删除未使用的 CSS 的工具;

  1. 安装 PurgeCsswebpack 插件:
npm install purgecss-webpack-plugin -D
  1. webpack.config.js 中配置 PurgeCss
new PurgeCSSPlugin({
  paths: glob.sync(`${path.resolve(__dirname, '../src')}/**/*`, { nodir: true }),
  only: ['bundle', 'vendor']
})
  • paths:表示要检测哪些目录下的内容需要被分析,这里我们可以使用 glob
  • 默认情况下,Purgecss 会将我们的 html 标签的样式移除掉,如果我们希望保留,可以添加一个 safelist 的属性;

purgecss 也可以对 lesssass文件进行处理(它是对打包后的 css 进行 tree shaking 操作);

6. Webpack 如何实现 Hot Module Replacement(HMR)

Webpack 实现 热更新 Hot Module Replacement(HMR)的主要原理是通过在运行时替换被修改的模块,而不需要重新加载整个页面或应用。

具体来说,Webpack HMR 的实现流程如下:

  1. 首先,在入口文件中添加对 HMR 的支持,例如使用 module.hot.accept 方法或 webpack-dev-server 等工具提供的 HMR API

  2. 当某个模块发生变化时,Webpack 会构建新的模块代码,并将其传递给 HMR runtime

  3. HMR runtime 会将新的模块代码与当前运行的模块进行比较,找出发生变化的部分,然后将其应用到当前运行的模块上。

  4. 如果当前模块依赖其他模块,HMR runtime 会检查这些依赖模块是否也发生了变化,如果有,会递归执行上述操作,直到所有相关的模块都被更新为止。

  5. 最后,HMR runtime 会通知应用程序,告诉它哪些模块已经被更新,并提供一些回调函数,让应用程序可以根据需要进行一些额外的操作。

总之,Webpack HMR 通过在运行时替换被修改的模块,使应用程序可以保持运行状态,同时也提高了开发效率和用户体验。要实现 HMR,我们需要在入口文件中添加对 HMR 的支持,并使用 webpack-dev-server 或其他工具来提供 HMR runtime 的支持。

7. 请列举几个常用的 Webpack 插件

  1. HtmlWebpackPlugin:该插件可以根据指定的模板生成 HTML 文件,并自动将生成的 JSCSS 文件引入到 HTML 中。

  2. CleanWebpackPlugin:该插件可以在每次构建之前清空输出目录,避免旧文件对新构建结果的影响。

  3. MiniCssExtractPlugin:该插件可以将 CSS 文件提取出来并单独打包成一个或多个文件,以便在浏览器中异步加载。

  4. Webpack.DefinePlugin:该插件可以定义全局变量,可以用来区分开发环境和生产环境等场景。

  5. TerserWebpackPlugin:该插件可以压缩 JavaScript 代码,减小文件大小,加快加载速度。

  6. CopyWebpackPlugin:该插件可以将某些文件或文件夹复制到输出目录中,例如静态资源文件等。

8. webpack 的优化策略有哪些?

webpack 的优化策略包括:

  1. Tree Shaking:通过静态代码分析,找出未被引用的代码并在打包时剔除掉,以减少打包后的代码体积。
  2. **Scope Hoisting**:将模块中的所有函数作用域合并到一个函数作用域中,以减少代码体积和函数声明语句执行时间。
  3. Code Splitting:将代码按照路由或者功能进行拆分,实现按需加载,减少首屏加载时间。
  4. 懒加载:将某些资源的加载延迟到真正需要时再进行加载,以减少首屏加载时间。
  5. 长缓存:利用浏览器缓存机制,将打包后的文件按照版本号进行区分,提高页面访问速度。
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!