【原创】Webpack 高级(一)——提升打包速度

SourceMap

SourceMap 是一个用来生成源代码与构建后代码一一映射的文件的方案。
通俗的来说, Source Map 就是一个信息文件,里面存储了代码打包转换后的位置信息,实质是一个 json 描述文件,维护了打包前后的代码映射关系
我们线上的代码一般都是经过打包的,如果线上代码报错了,想要调试起来,那真是很费劲了,比如下面这个例子:
使用打包工具 Webpack ,编译这一段代码

开发模式

cheap-module-source-map

module.exports = {
  // 其他省略
  mode: "development",
  devtool: "cheap-module-source-map",
};
  • 优点:打包编译速度快,只包含行映射
  • 缺点:没有列映射

生产模式

source-map

module.exports = {
  // 其他省略
  mode: "production",
  devtool: "source-map",
};
  • 优点:包含行/列映射
  • 缺点:打包编译速度更慢

参考:https://blog.csdn.net/u010358168/article/details/124902877

提升打包构建速度

在开发时,我们修改其中一个模块代码,Webpack 默认会将所有模块重新打包编译,速度很慢。
我们需要做到修改某个模块代码,就只有这个模块代码重新打包编译,其他模块不变,这样速度就会有很大的提升。
HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

使用

在config/webpack.dev.js文件中配置

//开发服务器
devServer: {
  host: '127.0.0.1', // 域名
  port: '3000', // 端口
  open: true, // 是否自动打开浏览器
  hot: true,// 开启HMR功能(只能用于开发环境,生产环境不需要)
},

此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了, 但是 js 还是不支持的。
在 main.js 中开启 HMR:

// 判断是否支持HMR功能
if (module.hot) {
  module.hot.accept('./js/sub.js')
  module.hot.accept('./js/sum.js', function (sum) {
    console.log('module.hot sum called');
  })
}

上面这样写会很麻烦,有多少个js就要配置多少个,所以实际开发我们会使用其他loader来解决。

比如:vue-loader 和 react-hot-loader。

OneOf

打包时每个文件都会经过所有 loader 处理,虽然 test 正则原因实际没有处理上,但是都要过一遍,是比较慢的。OneOf 的作用是只匹配一个loader, 剩下的就不匹配了。
修改 config/webpack.dev.js 和 webpack.prod.js,如下:

// 加载器
module: {
  rules: [
    {
      oneOf: [
        {
          // 匹配css结尾文件
          test: /\.css$/,
          // loader执行顺序是从右到左
          use: ['style-loader', 'css-loader']
        },
        {
          test: /\.less$/,
          use: ["style-loader", "css-loader", "less-loader"],
        },
        {
          test: /\.s[ac]ss$/,
          use: ["style-loader", "css-loader", "sass-loader"],
        },
        {
          test: /\.styl$/,
          use: ["style-loader", "css-loader", "stylus-loader"],
        },
        {
          test: /\.(png|jpe?g|gif|webp|svg)$/,
          type: "asset",
          parser: {
            // 小于10kb的图片转base64
            // 优点:减少请求数量  缺点:体积会增大三分之一
            dataUrlCondition: {
              maxSize: 20 * 1024 // 20kb
            }
          },
          generator: {
            // 将图片文件输出到 static/imgs 目录中
            // 将图片文件命名 [hash:8][ext][query]
            // [hash:8]: hash值取8位
            // [ext]: 使用之前的文件扩展名
            // [query]: 添加之前的query参数
            filename: "static/imgs/[hash:8][ext][query]",
          }
        },
        {
          /**
           * 处理图标字体、媒体等资源
           * type: "asset/resource" 相当于file-loader, 将文件转化成 Webpack 能识别的资源,其他不做处理
             type: "asset" 相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式
           */
          test: /\.(ttf|woff2?|map4|map3|avi)$/,
          type: "asset/resource",
          generator: {
            filename: "static/media/[hash:8][ext][query]",
          },
        },
        {
          test: /\.js$/,
          exclude: /node_modules/, // 排除node_modules代码不编译
          loader: "babel-loader",
        },
      ]
    }
  ]
},

Include/Exclude

开发时我们需要使用第三方的库或插件,所有文件都下载到node_modules 中了,而这些文件是不需要编译可以直接使用的。
所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。

  • include:包含,只处理 xxx 文件
  • exclude:排除,除了 xxx 文件以外其他文件都处理

    使用

    修改 config/webpack.dev.js 和 config/webpack.prod.js,如下:

    ...
    {
    test: /\.js$/,
    // exclude: /node_modules/, // 排除node_modules代码不编译
    include: path.resolve(__dirname, '../src'),
    loader: "babel-loader",
    },
    ...
    new ESLintWebpackPlugin({
    // 指定检查文件的根目录
    context: path.resolve(__dirname, '../src'),
    exclude: 'node_modules'// 默认值
    }),

Cache

每次打包时 js 文件都要经过 Eslint 检查和 Babel 编译,速度比较慢。
我们可以缓存之前的 Eslint 检查和 Babel 编译结果,这样第二次打包时速度就会更快了。
修改 config/webpack.dev.js 和 config/webpack.prod.js,如下:

...
{
  test: /\.js$/,
  // exclude: /node_modules/, // 排除node_modules代码不编译
  include: path.resolve(__dirname, '../src'),
  loader: "babel-loader",
  options: {
    cacheDirectory: true,// 开启babel编译缓存
    cacheCompression: false // 缓存文件不要压缩
  }
},
...
new ESLintWebpackPlugin({
  // 指定检查文件的根目录
  context: path.resolve(__dirname, '../src'),
  exclude: 'node_modules',// 默认值
  cache: true, // 开启缓存
  cacheLocation: path.resolve(__dirname, '../node_modules/.cache/.eslintcache') // 缓存目录
}),

Thread

当项目越来越庞大时,打包速度越来越慢,甚至于需要一个下午才能打包出来代码,这个速度是比较慢的。
我们想要继续提升打包速度,其实就是要提升 js 的打包速度,因为其他文件都比较少。
而对 js 文件处理主要就是 eslint、babel、Terser 三个工具,所以我们要提升它们的运行速度。
我们可以开启多进程同时处理 js 文件,这样速度就比之前的单进程打包更快了。

多进程打包

多进程打包:开启电脑的多个进程同时干一件事,速度更快。

请仅在特别耗时的操作中使用,因为每个进程启动就有大约为 600ms 左右的开销。

使用

获取CPU核数

修改 config/webpack.dev.js 和 config/webpack.prod.js

// nodejs核心模块,直接使用
const os = require("os");
// cpu核数
const threads = os.cpus().length;

安装依赖

npm i thread-loader -D

配置

// nodejs核心模块
const os = require('os')
...
const TerserPlugin = require("terser-webpack-plugin");
// cpu核数
const threads = os.cpus().length
...
{
  test: /\.js$/,
  // exclude: /node_modules/, // 排除node_modules代码不编译
  include: path.resolve(__dirname, '../src'),
  use: [
    {
      loader: 'thread-loader',//开启多进程
      options: {
        workers: threads //数量
      }
    },
    {
      loader: "babel-loader",
      options: {
        cacheDirectory: true,// 开启babel编译缓存
        cacheCompression: false // 缓存文件不要压缩
      }
    }
  ]
},
...
new ESLintWebpackPlugin({
  // 指定检查文件的根目录
  context: path.resolve(__dirname, '../src'),
  exclude: 'node_modules',// 默认值
  cache: true, // 开启缓存
  cacheLocation: path.resolve(__dirname, '../node_modules/.cache/.eslintcache'), // 缓存目录
  threads,//开启多进程
}),
...
// 配置多进程
new TerserPlugin({
  parallel: threads // 开启多进程
})
...

在配置中有 css 压缩等,借用 optimization 优化配置

optimization: {
  minimize: true,
  minimizer: [
    // css压缩
    new CssMinimizerPlugin(),
    // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
    new TerserPlugin({
      parallel: threads // 开启多进程
    })
  ]
}

完整配置文件

// nodejs核心模块
const os = require('os')
const path = require('path') // nodejs核心模块,用来处理路径问题
const ESLintWebpackPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
// cpu核数
const threads = os.cpus().length

// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env", // 能解决大多数样式兼容性问题
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean);
};

module.exports = {
  // 入口
  entry: './src/main.js',
  // 输出
  output: {
    // 文件输出路径
    // __dirname nodejs变量,代表当前文件的文件夹目录
    path: path.resolve(__dirname, '../dist'),
    // 入口文件打包输出文件名
    filename: 'static/js/main.js',
    clean: true, // 自动将上次打包目录资源清空
  },
  // 加载器
  module: {
    rules: [
      {
        oneOf: [
          {
            // 匹配css结尾文件
            test: /\.css$/,
            // loader执行顺序是从右到左
            use: ['style-loader', 'css-loader']
          },
          {
            test: /\.less$/,
            use: ["style-loader", "css-loader", "less-loader"],
          },
          {
            test: /\.s[ac]ss$/,
            use: ["style-loader", "css-loader", "sass-loader"],
          },
          {
            test: /\.styl$/,
            use: ["style-loader", "css-loader", "stylus-loader"],
          },
          {
            test: /\.(png|jpe?g|gif|webp|svg)$/,
            type: "asset",
            parser: {
              // 小于10kb的图片转base64
              // 优点:减少请求数量  缺点:体积会增大三分之一
              dataUrlCondition: {
                maxSize: 20 * 1024 // 20kb
              }
            },
            generator: {
              // 将图片文件输出到 static/imgs 目录中
              // 将图片文件命名 [hash:8][ext][query]
              // [hash:8]: hash值取8位
              // [ext]: 使用之前的文件扩展名
              // [query]: 添加之前的query参数
              filename: "static/imgs/[hash:8][ext][query]",
            }
          },
          {
            /**
             * 处理图标字体、媒体等资源
             * type: "asset/resource" 相当于file-loader, 将文件转化成 Webpack 能识别的资源,其他不做处理
               type: "asset" 相当于url-loader, 将文件转化成 Webpack 能识别的资源,同时小于某个大小的资源会处理成 data URI 形式
             */
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: "asset/resource",
            generator: {
              filename: "static/media/[hash:8][ext][query]",
            },
          },
          {
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, '../src'),
            use: [
              {
                loader: 'thread-loader',//开启多进程
                options: {
                  workers: threads //数量
                }
              },
              {
                loader: "babel-loader",
                options: {
                  cacheDirectory: true,// 开启babel编译缓存
                  cacheCompression: false // 缓存文件不要压缩
                }
              }
            ]
          },
        ]
      }
    ]
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, '../src'),
      exclude: 'node_modules',// 默认值
      cache: true, // 开启缓存
      cacheLocation: path.resolve(__dirname, '../node_modules/.cache/.eslintcache'), // 缓存目录
      threads,//开启多进程
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, '../public/index.html'),
      inject: 'head'
    }),
    // 提取css成单独文件
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: "static/css/main.css",
    }),
    // css压缩也可以写到optimization.minimizer里面,效果一样的
    // new CssMinimizerPlugin(),
    // 配置多进程
    // new TerserPlugin({
    //   parallel: threads // 开启多进程
    // })
  ],
  optimization: {
    minimize: true,
    minimizer: [
      // css压缩
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
      new TerserPlugin({
        parallel: threads // 开启多进程
      })
    ]
  },
  //开发服务器
  // devServer: {
  //   host: '127.0.0.1', // 域名
  //   port: '3000', // 端口
  //   open: true, // 是否自动打开浏览器
  //   hot: true // 开启HMR
  // },
  // 模式
  mode: 'production',
  devtool: "source-map",
}

在文件比较少时,多进程速度可能会更慢,因为每个进行启动需要时间,文件数量较多时就会体现出多进程的优势了,速度就会变快。

点赞
  1. Pokerdomded说道:
    Google Chrome Windows 10
    https://t.me/officials_pokerdom/4007
  2. BluffMaster说道:
    Google Chrome Windows 10
    https://t.me/s/iGaming_live/4866
  3. BluffMaster说道:
    Google Chrome Windows 10
    https://t.me/s/Martin_officials
  4. BluffMaster说道:
    Google Chrome Windows 10
    http://images.google.ki/url?q=https://t.me/officials_7k/1054
  5. RoyalFlusher说道:
    Google Chrome Windows 10
    В мире азарта, где каждый сайт стремится заманить обещаниями быстрых джекпотов, рейтинг интернет казино на деньги превращается как раз той картой, что ведет мимо ловушки обмана. Тем профи плюс новичков, которые пресытился с ложных посулов, такой средство, чтоб ощутить реальную выплату, как тяжесть выигрышной фишки в ладони. Минус ненужной воды, просто надёжные клубы, в которых отдача не лишь цифра, а конкретная фортуна.Собрано из поисковых поисков, как ловушка, которая захватывает топовые горячие тренды по интернете. В нём отсутствует пространства к стандартных трюков, всякий пункт как карта в столе, там обман проявляется мгновенно. Игроки знают: на рунете манера письма и подтекстом, в котором юмор маскируется как намёк, позволяет избежать ловушек.На https://www.youtube.com/@Don8Play/posts такой список находится будто раскрытая раздача, готовый на старту. Посмотри, коли нужно почувствовать ритм реальной ставки, минуя иллюзий и неудач. Тем что ценит тактильность приза, такое словно держать фишки у пальцах, а не глядеть на монитор.

发表回复

电子邮件地址不会被公开。必填项已用 * 标注