webpack面试题

一、webpack热更新原理

 

1.使用 webpack-dev-server (后面简称 WDS)托管静态资源、提供websocket服务

2.webpack打包时会将HMR Runtime和源代码一起编译成 bundle 文件。HMR Runtime主要 负责接收服务发送的websocket消息,处理模块更新。

3.当浏览器加载页面后,会与 WDS 建立 WebSocket 连接

4.后续源文件发生变动时,webpack会发生增量构建,生成2个文件,并通过 WebSocket 发送 hash 事件。

此阶段会生成两个文件

  • manifest 文件:JSON 格式文件,包含所有发生变更的模块列表,命名为 [hash].hot-update.json
  • 模块变更文件:js 格式,包含编译后的模块代码,命名为 [hash].hot-update.js

5.浏览器接收到 hash 事件后,发送ajax请求获取 manifest 资源文件,确认增量变更范围。

注意,在 Webpack 4 及之前,热更新文件以模块为单位,即所有发生变化的模块都会生成对应的热更新文件; Webpack 5 之后热更新文件以 chunk 为单位,如上例中,main chunk 下任意文件的变化都只会生成 main.[hash].hot-update.js 更新文件。

6.浏览器加载发生变更的增量模块,获取到更新内容后HMR触发变更模块的 module.hot.accept 回调,执行代码变更逻辑

文章来源

二、webpack打包优化思路有哪些?

1. 不常变动的库,只构建一次

DllPluginDllReferencePlugin 插件,它们的主要作用是通过提前编译和缓存某些不经常改变的库。

  • DllPlugin:用于创建一个“动态链接库(DLL)”的文件,该文件包含了第三方库(比如 ReactLodash 等)或者其他不常改变的代码。
  • DllReferencePlugin:在主应用的构建中引用已经用 DllPlugin 构建的 DLL 文件,从而避免重复打包这些不常变化的第三方库。

首先,需要使用 DllPlugin 将不常变化的第三方库打包成一个 DLL 文件。这通常是在一个单独的构建配置中进行。

webpack.dll.config.js

const path = require('path');

module.exports = {
  entry: {
    vendor: ['react', 'react-dom', 'lodash'], // 这里是第三方库
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].dll.js',
    library: '[name]_[hash]',  // 为 DLL 文件指定一个全局变量名
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]_[hash]', // 必须和 library 保持一致
      path: path.join(__dirname, 'dist', '[name]-manifest.json'), // 生成的 manifest 文件
    }),
  ],
};

在这个配置中,第三方库 reactreact-domlodash 被打包成一个 DLL 文件,这样做的目的是将它们从主项目的打包中分离出来,避免重复打包。

使用时:
在主应用的 Webpack 配置中,使用 DllReferencePlugin 来引用之前生成的 DLL 文件。 

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: './src/index.js',  // 入口文件
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  plugins: [
    // 引用之前生成的 DLL 文件
    new webpack.DllReferencePlugin({
      context: __dirname,
      manifest: path.resolve(__dirname, 'dist', 'vendor-manifest.json'), // 指定对应的 manifest 文件
    }),
  ],
};

2. 使用构建缓存: webpack自带的缓存,以及babel缓存

Webpack 5 引入了持久化缓存功能,能够将构建的中间结果缓存起来,避免每次都重新编译,从而提高打包速度。

module.exports = {
  cache: {
    type: 'filesystem',  // 使用文件系统缓存
  },
};

babel-loader缓存

module: {
  rules: [
    {
      test: /\.js$/,
      use: {
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,  // 启用缓存
        },
      },
    },
  ],
}

3. 使用并行编译

Webpack 可以通过并行化构建过程来加速编译。启用多线程编译可以有效提升性能。

使用 thread-loader 来并行化构建过程:

const ThreadLoader = require('thread-loader');

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['thread-loader', 'babel-loader'],
      },
    ],
  },
};

也可以通过 HappyPack 来并行化构建任务

4. 明确模块搜索范围

webpack 的 resolve.modules 用于配置 Webpack 去哪些目录下寻找第三方模块。

resolve.modules 的默认值是 [‘node_modules’],含义是先去当前目录下的 ./node_modules 目录下去找想找的模块,如果没找到就去上一级目录 ../node_modules 中找,再没有就去 ../../node_modules 中找,以此类推,这和 Node.js 的模块寻找机制很相似。

当安装的第三方模块都放在项目根目录下的 ./node_modules 目录下时,没有必要按照默认的方式去一层层的寻找,可以指明存放第三方模块的绝对路径.

5. 缩小文件后缀的数量

在导入语句没带文件后缀时,Webpack 会自动带上后缀后去尝试询问文件是否存在。 Webpack 配置中的 resolve.extensions 用于配置在尝试过程中用到的后缀列表,默认是:

extensions: ['.js', '.json']

也就是说当遇到 require(‘./data’) 这样的导入语句时,Webpack 会先去寻找 ./data.js 文件,如果该文件不存在就去寻找 ./data.json 文件,如果还是找不到就报错。

如果这个列表越长,或者正确的后缀在越后面,就会造成尝试的次数越多,所以 resolve.extensions 的配置也会影响到构建的性能。

6.使用include或exclude排除掉不需要处理的目录

node_modules 排除在 babel-loaderts-loader 等构建流程之外,避免不必要的转换。

{
  test: /\.js$/,
  exclude: /node_modules/,  // 排除 node_modules
  use: 'babel-loader',
}

7. 分割代码(Code Splitting)

通过分割代码,可以避免一次性加载过多的代码,减少打包时间,并提高运行时性能。

动态导入(Lazy loading):

import(/* webpackChunkName: "moduleA" */ './moduleA').then(module => {
  // 动态加载 moduleA
});

SplitChunks 插件: Webpack 会自动分析并分割代码。例如,分割 node_modules 中的第三方库:

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',  // 抽取所有的第三方代码
    },
  },
};

 

 

 

 

posted @ 2022-04-26 11:54  我是格鲁特  阅读(23)  评论(0编辑  收藏  举报