webpack 面试题

121 阅读5分钟

问题 1: 请简要描述 Webpack 的核心概念

Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具

它能将各种类型的资源(如 JavaScript、CSS、图片、字体等)视为模块,通过分析模块间的依赖关系,将其打包成适合在浏览器中运行的静态资源。

主要作用包括:

  • 模块打包
    • 把分散的模块整合为一个或多个 bundle 文件;
  • 资源转换
    • 如使用 babel-loader 将 ES6 + 代码转换为 ES5、
    • css-loader 将 SCSS 转换为 CSS 等;
  • 代码优化
    • 如 Tree Shaking 去除未使用代码、压缩代码减小文件体积;
  • 代码拆分
    • 将代码按需求拆分成多个块,实现按需加载,提升性能。

主要包括:

  • 入口(entry):从入口开始寻找依赖构建依赖图
    • 单入口:
    • 多入口:多用于多页面 或者 分离第三方库
  • 输出(output)
    • path: 指定输出的路径(绝对路径)
    • filename:指定输出的文件名,可以使用[name] [hash]占位符
      • 可以用hash来确保文件名的唯一性
        • 缓存优化:浏览器会对已经访问过的文件进行缓存
        • hash可以作为文件版本的一种标识
        • 避免文件命名冲突导致的覆盖
  • 加载器(loader):预处理,将不同文件类型转换为 Webpack 能处理的模块
    • 多个:从右到左执行
  • 插件(plugin):执行更广泛的任务,如打包优化、资源管理、注入环境变量等
    • HtmlWebpackPlugin:自动生成 HTML 文件,并将打包后的 JavaScript 和 CSS 文件引入其中,方便部署
    • MiniCssExtractPlugin:将 CSS 从 JavaScript 中提取出来,生成单独的 CSS 文件
    • CleanWebpackPlugin:在每次构建前清理输出目录,确保输出目录只包含最新的文件

问题 2: Webpack 的热模块更新(HMR)是什么?它是如何工作的?

工作原理:

  • Webpack 在开发服务器中内置了 HMR 服务器。
  • 文件被修改,Webpack 会重新编译该模块
  • 编译后的模块会被发送到浏览器端。
  • 浏览器端的 HMR 运行时会接收更新的模块并替换旧模块,同时保持应用的状态。

问题3:什么是 Tree Shaking?Webpack 如何实现 Tree Shaking?

去除死代码

开启方式:

  • webpack4 以上mode为production时会自动开启
  • 也可通过optimization.minimize手动配置TerserPlugin等优化工具

Webpack 实现 Tree Shaking 需满足以下条件:

  • 使用 ES6 模块语法,因为 ES6 模块是静态可分析的,Webpack 能够确定哪些模块和代码被实际使用。
  • mode设置为'production'时,Webpack 默认使用TerserPlugin进行压缩,它会自动进行 Tree Shaking。

问题4: 如何在 Webpack 中实现代码拆分?

  • 多入口方式:在entry中配置多个入口,每个入口对应一个独立的输出文件,适用于多页面应用。

  • 使用splitChunks插件:通过optimization.splitChunks配置,可将公共代码提取到单独的 chunk 中。

  • 动态导入:使用 ES2020 的动态导入语法import(),Webpack 会自动将动态导入的模块拆分成单独的 chunk。例如:const module = await import('./module.js');

entry: {
    page1: './src/page1.js',
    page2: './src/page2.js' 
}

optimization: {
    splitChunks: {
    // 这会将所有入口 chunk 中的公共模块拆分出来。
        chunks: 'all'
    }
}
// 方式2:还可通过`cacheGroups`进行更细粒度的控制,如提取第三方库到`vendor` chunk 。
optimization: {
    splitChunks: {
        cacheGroups: {
            vendors: {
              name: 'chunk-vendors',
              minChunks: 1000,
              test: /[\\/]node_modules[\\/]/,
              priority: -10,
              chunks: 'initial'
            }
         }
     }
 }

问题5:如何在 Webpack 中配置不同环境(开发、生产)的差异化配置?

  • 使用mode配置:Webpack 会根据不同的mode启用默认的优化和插件。
    • 例如,production模式下会启用压缩和 Tree Shaking 等。
    • 同时,还可在configureWebpackchainWebpack中根据process.env.NODE_ENV进行差异化配置,
  • 使用多个配置文件:创建webpack.common.js存放公共配置,webpack.dev.js存放开发环境配置,webpack.prod.js存放生产环境配置

问题6:Webpack 中的 resolve.alias 有什么作用?如何使用?

  • 用于配置路径别名,通过为常用的目录或模块设置别名,可简化模块导入路径,提高代码的可维护性。

问题7:在 Webpack 中如何优化图片资源的加载?

  • 使用url-loaderfile-loader
    • url-loader可将小图片转换为 Data URL 嵌入到代码中,减少请求数量。
    • file-loader用于处理较大图片,将其输出到指定目录并返回路径
  • 图片压缩:使用image-webpack-loader插件,在构建过程中对图片进行压缩
  • 响应式图片:在 HTML 中使用<picture>标签或srcset属性,根据不同的设备屏幕大小加载合适尺寸的图片,Webpack 可配合html - loader等进行处理

问题8: 常用的一些webpack loader都有哪些?

  • babel-loader
  • css-loader:处理@import 和url()语句,将css代码解析成模块,可以和css和
  • style-loader:将css样式插入到DOM中
  • less-loader
  • sass-loader
  • file-laoder:将文件移动到输出目录,并返回文件的相对路径
  • url-loader: 可以将一些小文件转换为dataurl,直接嵌入到打包后的文件中,减少http请求数量
  • postcss-loader
  • ts-loader
  • eslint-loader

问题9: 常用的一些webpack plugin都有哪些?

  • HtmlWebpackPlugin:自动生成html文件
  • CleanWebpackPlugin
  • MiniCssExtractPlugin
  • UglifyJsPlugin
  • OptimizeCssAssetsPlugin

webpack中常见的三种哈希类型

  • 全量hash:整个项目内容没变,就不会
  • chunk哈希:具体代码块变化了,就改变
  • 内容hash:文件内容本身生成哈希值,文件不变,哈希就不变

vue-cli-service 底层是基于 Webpack 进行构建的,它将 Vue 项目的开发和构建需求转化为相应的 Webpack 配置,并使用 Webpack 进行文件的编译、打包和优化。

配置

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {  
        main: './src/main.js',
        vendor: './src/vendor.js'
  },
  devServer: {
    hot: true
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/[name].[contenthash].js',
    publicPath: '/',
  },
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js',
      '@': path.resolve(__dirname, 'src'),
    },
    extensions: ['.js', '.vue', '.json'],
  },
  module: {
    rules: [
      {
        test: /.vue$/,
        use: 'vue-loader',
      },
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: 'babel-loader',
      },
      {
        test: /.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
        ],
      },
      {
        test: /.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
        ],
      },
      {
        test: /.(png|jpe?g|gif|svg)(?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'img/[name].[hash:7].[ext]',
            },
          },
        ],
      },
      {
        test: /.(woff2?|eot|ttf|otf)(?.*)?$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000,
              name: 'fonts/[name].[hash:7].[ext]',
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new VueLoaderPlugin(),
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './index.html',
      inject: 'body',
    }),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css',
    }),
    new webpack.HotModuleReplacementPlugin(),
  ],
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },
      }),
      new OptimizeCSSAssetsPlugin({}),
    ],
    splitChunks: {
      chunks: 'all',
    },
  },
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 8080,
    hot: true,
    historyApiFallback: true,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        pathRewrite: {
          '^/api': '',
        },
      },
    },
  },
};