学习 前端工程化阶段性总结 - Webpack 5 篇

198 阅读4分钟

前言

近期学完 哲玄(egg.js开发者) 的大前端课程 Webpack 的配置后,写下此篇。

Webpack 5

webpack 提供很多配置,一般脚手架工具会配置好大部分,不需要开发者再进行配置,但是呢,可以不做但不能不会。

/**
 * webpack 配置 示例文件
 */
// 引入 path
const path = require('path');
// 引入 vue-loader
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
// 配置
module.exports = {
  // 基础目录,绝对路径,用于从配置中解析入口点(entry point)和 加载器(loader)。
  // 一般不配置,默认使用 Node.js 进程的当前工作目录
  context: path.resolve(process.cwd(), 'app'),
  // 模式 development / production / none , 默认 production
  mode: 'development',
  // 单入口配置
  entry: path.resolve(__dirname, './src/index.js'),
  // 单入口配置 等同于上面的配置
  entry: {
    index: path.resolve(__dirname, './src/index.js'),
  },
  // 多入口配置
  entry: {
    index: path.resolve(__dirname, './src/index.js'),
    other: path.resolve(__dirname, './src/other.js'),
  },
  // 出口配置示例
  output: {
    // 输出路径
    path: path.join(process.cwd(), 'app/public/dist'),
    // 输出文件名 
    // js/ 表示 打包到 path (配置的输出路径) 下的 js 目录
    // [name] 表示文件名
    // [chunkhash:8] 表示 hash 值 
    filename: 'js/[name]_[chunkhash:8].bundle.js',
    // 是否携带凭据启用跨域加载 anonymous 不携带凭据 use-credentials 携带凭据
    crossOriginLoading: 'anonymous',
    // 该选项的值是以 runtime(运行时) 或 loader(载入时) 所创建的每个 URL 的前缀。
    // 因此,在多数情况下,此选项的值都会以 / 结束。
    publicPath: '/dist',
  },
  // 模块配置
  module: {
    // 规则
    rules: [
      {
        test: /\.vue$/, // 匹配规则
        use: 'vue-loader', // 匹配到的文件使用的 loader
      },
      {
        test: /\.js$/,
        use: 'babel-loader',
        // 只对 src 目录下的文件进行处理
        include: path.resolve(process.cwd(), 'src'),
        // 不处理 node_modules 目录下的文件
        exclude: /node_modules/,
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: 'asset', // 代替 file-loader 和 url-loader
        parser: {
          dataUrlCondition: {
            // 小于 10kb 转 base64,使用此配置会减少 http 请求开销,但是会增加包体积
            // 所以配置的时候需要根据项目实际情况平衡
            maxSize: 10 * 1024, 
          },
        },
      },
      {
        test: /\.(eot|ttf|woff|woff2)$/,
        type: 'asset',
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.scss$/,
        // 执行顺序 从右到左,即先执行 sass-loader 再执行 css-loader 最后执行 style-loader
        use: ['style-loader', 'css-loader', 'scss-loader'],
      }
    ],
  },
  // 解析配置
  resolve: {
    // 尝试按顺序解析这些后缀名。
    // 如果有多个文件有相同的名字,但后缀名不同,
    // webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。
    extensions: ['.js', '.vue', 'scss', 'css'],
    alias: {
      // 使用 @ 代替 app 目录
      // 例如 import xxx from '../../pages/index.vue' 
      // 转换成 import xxx from '@/pages/index.vue'
      '@': path.resolve(process.cwd(), 'src'),
    },
  },
  // 插件
  plugins: [
    // 将你定义过的其他规则复制并应用到 .vue 文件里
    // 例如 /\.js$/ 应用到 .vue 文件里的 <script> 块
    // 例如 /\.css$/ 应用到 .vue 文件里的 <style> 块
    new VueLoaderPlugin(),
    // 暴露第三方库到 window context 上
    new webpack.ProvidePlugin({ Vue: 'vue' }),
    new webpack.DefinePlugin({
      // 启用 Vue 2 的选项式 API
      __VUE_OPTIONS_API__: true, 
      // 禁用 Vue 2 的生产环境工具
      __VUE_PROD_DEVTOOLS__: false, 
      // 禁用 Vue 2 的生产环境 hydration 不匹配的详细信息
      __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false, 
    }),
    new HtmlWebpackPlugin({
      // 设置网页标题 如果使用了 template 则 title 会被忽略
      title: 'webpack 示例',
      // 生成的 HTML 文件的名称
      // 这里的根目录是项目根目录 不是 output 配置中的 path 
      // 如果不配置则默认在 output 配置中的 path 下
      filename: path.resolve(process.cwd(), 'dist/', 'index.html'),
      // 使用的模板文件
      template: path.resolve(process.cwd(), 'src/index.html'),
      // 引入的 js 文件 即将哪些 js 文件引入到 html 中
      chunks: ['index']
    }),
    // 多页面配置 多页面可配置多个 HtmlWebpackPlugin
    new HtmlWebpackPlugin({
      filename: path.resolve(process.cwd(), 'dist/', 'other.html'),
      template: path.resolve(process.cwd(), 'src/other.html'),
      chunks: ['other']
    })
  ],
  // 优化配置
  optimization: {
    // 代码分割
    splitChunks: {
      // 自动生成的文件名的分隔符
      automaticNameDelimiter: '-',
      // 选择哪些 chunks 进行优化
      chunks: 'all',
      // 按需加载时的最大并行请求数量
      maxAsyncRequests: 5,
      // 入口点的最大并行请求数量
      maxInitialRequests: 3,
      // 缓存组
      cacheGroups: {
        // 提取 第三方 公共代码
        // 例如:a 和 b 都引入了 lodash 那么不做此优化时,a 和 b 都会引入 lodash
        // 做此优化时 lodash 会被提取到 vendor.bundle.js 中,a 和 b 会引入 vendor.bundle.js
        vendor: {
          test: /[\\/]node_modules[\\/]/, // 匹配规则
          chunks: 'all', // 匹配的 chunk 类型
          name: 'vendor', // 提取的文件名
          priority: 10, // 权重 值越大 优先级越高
          // 强制提取 忽略上层配置的影响 
          // 忽略 minSize minChunks maxAsyncRequests maxInitialRequests 这些配置
          enforce: true, 
          reuseExistingChunk: true, // 复用已存在的模块
        },
        // 提取 业务 公共代码
        // 例如:a 和 b 都引入了 tools.js 那么不做此优化时,a 和 b 都会引入 tools.js
        // 做此优化时 tools.js 会被提取到 common.bundle.js 中,a 和 b 会引入 common.bundle.js
        common: {
          name: 'common',
          chunks: 'all',
          minSize: 20000, // 最小尺寸 单位 byte
          minChunks: 2, // 最小引用次数
          priority: 0,
        }
      }
    },
  },
  // 其他配置
  externals: {
    // 提取第三方库 不打包到 bundle.js 中
    // 例如:在html中使用 script 标签引入 jQuery CDN
    // 然后配置 externals 引入 jQuery
    // 当你在代码中使用 import $ from 'jQuery' 时,
    // Webpack 会导出全局作用域下的 jQuery 变量,这个变量在 script 标签引入的 jQuery CDN 中。
    // 而不需要将 jQuery 打包到 bundle.js 中。
    jquery: 'jQuery',
  },
  // 开发工具 映射代码生成 .map 文件用于错误提示,可选映射到具体行,列。也可关闭,影响打包速度
  devtool: 'cheap-module-source-map',
}

优化

前端逃不掉的就是兼容,优化。在 Webapck 中我们需要关注两个主要优化点:打包后的性能,打包速度。 Webpack 提供了很多插件可以用来优化以及其他优化手段,例如 :

  • css 压缩(CssMinimizerPlugin)
  • 提取 css 并按需引入(MiniCssExtractPlugin)
  • 合并 chunk 限制打包数量,减少 http 请求开销(MinChunkSizePlugin,LimitChunkCountPlugin)
  • 压缩图片资源(ImageMinimizerWebpackPlugin)
  • 多开 worker 进行多线程打包(thread-loader)
  • 在module.rules中配置loader时,可配置处理的目录(include)与不处理的目录(exclude)
  • 减少生成 chunk 的 hash 长度,这样可以减少引入文件的代码长度。
  • 甚至减少使用 loader 与 plugin 都可以达到优化打包速度的目的,毕竟这些都有启动时间。