MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Webpack 高级应用

2021-12-062.9k 阅读

Webpack 高级应用之优化策略

Webpack 作为前端构建工具,优化其配置对提升项目性能至关重要。优化策略主要集中在提升打包速度与减小打包体积两个方面。

提升打包速度

  1. 缓存 Webpack 本身具备缓存机制,在 webpack.config.js 中配置 cache 选项可开启。
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename]
    }
  },
  // 其他配置...
};

type 设置为 filesystem 表示使用文件系统缓存,buildDependencies 中指定配置文件发生变化时才重新缓存,避免不必要的缓存失效。这样在后续构建时,如果文件没有变化,Webpack 可以直接读取缓存,大大加快打包速度。

  1. 多进程构建 对于大型项目,使用 thread-loader 可开启多进程构建。它会将任务分配到多个子进程中并行处理。 安装:npm install thread-loader -D 配置如下:
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          'thread-loader',
          {
            loader: 'babel-loader',
            options: {
              // babel 配置
            }
          }
        ]
      }
    ]
  }
};

thread-loader 需放在其他 loader 之前,它会开启多个 worker 线程,将后续 loader 的处理任务分配到这些线程中,充分利用多核 CPU 的优势,提升打包速度。但要注意,多进程也会带来进程管理开销,对于小型项目可能效果不明显甚至会降低性能。

  1. 缩小文件搜索范围webpack.config.js 中,通过 resolve 配置可缩小文件搜索范围。
module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    },
    extensions: ['.js', '.jsx', '.json']
  }
};

alias 配置别名,如将 @ 指向 src 目录,在引入模块时使用 @ 代替冗长的路径,同时 Webpack 也只需在 src 目录下搜索。extensions 配置指定文件扩展名,Webpack 在解析模块时会按照此顺序尝试添加扩展名,避免不必要的搜索。

减小打包体积

  1. 代码分割 Webpack 的 splitChunks 可实现代码分割,将公共代码提取出来,避免重复打包。
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

chunks 设置为 all 表示对所有类型的 chunks 进行分割。minSize 定义了最小分割大小,小于此大小的模块不会被分割。cacheGroups 可定义不同的缓存组,如 vendors 缓存组将 node_modules 中的模块提取出来,default 缓存组处理其他公共模块。这样在页面加载时,公共模块只需加载一次,减少了加载体积。

  1. Tree Shaking Tree Shaking 用于去除未使用的代码。要实现 Tree Shaking,需满足以下条件:
  • 使用 ES6 模块语法(importexport)。
  • 使用 mode: 'production',Webpack 在生产模式下会自动启用 Tree Shaking。 例如,有如下模块:
// utils.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// main.js
import { add } from './utils.js';
console.log(add(1, 2));

在生产模式下,Webpack 会分析 main.js 中实际使用的导出,将未使用的 subtract 函数去除,从而减小打包体积。

  1. 压缩代码 Webpack 在生产模式下会默认启用 terser-webpack-plugin 对代码进行压缩。也可自定义配置:
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          compress: {
            drop_console: true
          }
        }
      })
    ]
  }
};

parallel 设置为 true 开启并行压缩,提升压缩速度。terserOptions.compress.drop_console 设置为 true 可去除代码中的 console.log 语句,进一步减小体积。同时还有其他如 drop_debugger 等选项可用于去除调试相关代码。

Webpack 高级应用之多环境配置

在实际项目开发中,通常需要针对不同环境(开发、测试、生产)进行不同的配置。Webpack 提供了多种方式来实现多环境配置。

使用环境变量

  1. 设置环境变量package.json 中可设置不同脚本命令来指定环境变量。
{
  "scripts": {
    "dev": "cross - env NODE_ENV = development webpack - serve --config webpack.dev.js",
    "test": "cross - env NODE_ENV = test webpack --config webpack.test.js",
    "build": "cross - env NODE_ENV = production webpack --config webpack.prod.js"
  }
}

这里使用了 cross - env 来跨平台设置环境变量。不同的脚本命令会设置不同的 NODE_ENV 环境变量。

  1. 根据环境变量配置 Webpackwebpack.common.js 中定义通用配置:
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset - env']
          }
        }
      }
    ]
  }
};

webpack.dev.js 中继承通用配置并添加开发环境配置:

const { merge } = require('webpack - merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline - source - map',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 3000
  }
});

webpack.prod.js 中继承通用配置并添加生产环境配置:

const { merge } = require('webpack - merge');
const common = require('./webpack.common.js');
const MiniCssExtractPlugin = require('mini - css - extract - plugin');

module.exports = merge(common, {
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css - loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'styles.css'
    })
  ]
});

通过 webpack - merge 插件将通用配置与不同环境的特定配置合并,实现根据环境变量加载不同配置。

使用配置文件切换

除了环境变量,也可手动指定使用不同的配置文件。 在 package.json 中设置脚本:

{
  "scripts": {
    "dev": "webpack - serve --config webpack.dev.js",
    "test": "webpack --config webpack.test.js",
    "build": "webpack --config webpack.prod.js"
  }
}

这种方式不需要依赖环境变量,直接通过指定配置文件来区分不同环境。每个配置文件的内容与前面基于环境变量的方式类似,分别针对不同环境进行配置。例如 webpack.test.js 可能会添加一些测试相关的配置,如设置测试覆盖率工具等。

Webpack 高级应用之插件开发

Webpack 插件是其强大功能的重要组成部分,通过开发自定义插件可以实现各种特定的构建需求。

插件基础结构

一个 Webpack 插件本质上是一个具有 apply 方法的 JavaScript 对象。

class MyPlugin {
  constructor(options) {
    // 插件初始化,接收配置选项
    this.options = options;
  }
  apply(compiler) {
    // 挂载到 compiler 上,compiler 包含了 Webpack 构建的所有状态
    compiler.hooks.compile.tap('MyPlugin', compilation => {
      console.log('开始编译');
    });
  }
}

module.exports = MyPlugin;

webpack.config.js 中使用该插件:

const MyPlugin = require('./MyPlugin');

module.exports = {
  // 其他配置...
  plugins: [
    new MyPlugin({
      // 传递给插件的选项
    })
  ]
};

上述示例中,插件在 compile 钩子触发时打印一条日志。compiler.hooks 包含了众多钩子,如 beforeRunrunemit 等,可在不同的构建阶段执行自定义逻辑。

插件编写实战 - 自定义文件注入插件

假设我们要编写一个插件,在打包后的 HTML 文件中注入一段自定义脚本。

  1. 安装依赖 需要 html - webpack - plugin 来处理 HTML 文件,fs - extra 用于文件操作。
npm install html - webpack - plugin fs - extra - D
  1. 编写插件
const fs = require('fs - extra');
const path = require('path');

class InjectScriptPlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('InjectScriptPlugin', compilation => {
      const htmlPlugin = compilation.getPlugin('HtmlWebpackPlugin');
      if (htmlPlugin) {
        htmlPlugin.tapAsync('InjectScriptPlugin', (htmlPluginData, callback) => {
          const scriptPath = path.join(__dirname, this.options.scriptPath);
          const scriptContent = fs.readFileSync(scriptPath, 'utf8');
          htmlPluginData.html = htmlPluginData.html.replace('</body>', `<script>${scriptContent}</script></body>`);
          callback(null, htmlPluginData);
        });
      }
    });
  }
}

module.exports = InjectScriptPlugin;
  1. 配置插件webpack.config.js 中:
const HtmlWebpackPlugin = require('html - webpack - plugin');
const InjectScriptPlugin = require('./InjectScriptPlugin');

module.exports = {
  // 其他配置...
  plugins: [
    new HtmlWebpackPlugin(),
    new InjectScriptPlugin({
      scriptPath: 'path/to/your/script.js'
    })
  ]
};

该插件通过 html - webpack - pluginhtmlWebpackPluginBeforeHtmlProcessing 钩子,在 HTML 处理前读取指定脚本文件内容并注入到 HTML 文件的 body 标签结束前。

Webpack 高级应用之性能监控与分析

在大型前端项目中,对 Webpack 打包后的性能进行监控与分析有助于发现性能瓶颈并进行针对性优化。

使用 Webpack Bundle Analyzer

  1. 安装
npm install webpack - bundle - analyzer - D
  1. 配置webpack.config.js 中添加如下配置:
const BundleAnalyzerPlugin = require('webpack - bundle - analyzer').BundleAnalyzerPlugin;

module.exports = {
  // 其他配置...
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

运行 Webpack 构建时,该插件会生成一个交互式的可视化报告,展示各个模块的大小、依赖关系等信息。可以直观地看到哪些模块体积较大,是否存在重复依赖等问题。例如,如果发现某个第三方库体积过大,可以考虑是否有更轻量级的替代品,或者是否只引入了部分必要功能。

使用 SpeedMeasurePlugin

  1. 安装
npm install speed - measure - plugin - D
  1. 配置
const SpeedMeasurePlugin = require('speed - measure - plugin');
const smp = new SpeedMeasurePlugin();

const webpackConfig = {
  // 正常的 Webpack 配置
  module: {
    rules: [
      // 各种 loader 配置
    ]
  },
  plugins: [
    // 各种插件配置
  ]
};

module.exports = smp.wrap(webpackConfig);

SpeedMeasurePlugin 会统计每个 loader 和插件的执行时间,输出详细的构建时间报告。通过这个报告,可以了解到哪个 loader 或插件耗时较长,从而进行优化。比如发现 babel - loader 耗时久,可以优化其配置,如开启缓存、缩小文件匹配范围等。

使用 Lighthouse

虽然 Lighthouse 并非专门针对 Webpack,但它可以对最终生成的页面进行全面的性能、可访问性等方面的评估。

  1. 安装 Lighthouse 可作为 Chrome 浏览器插件安装,也可通过 npm 全局安装:
npm install - g lighthouse
  1. 使用 在命令行中运行:
lighthouse http://localhost:3000

它会模拟真实用户环境,对页面进行加载测试,并生成详细报告。报告中会指出页面性能问题,如首次渲染时间过长、资源加载过慢等。结合 Webpack 配置,可以进一步优化构建,例如通过代码分割优化资源加载顺序,提升页面加载性能。同时,Lighthouse 还会检查页面的可访问性、最佳实践等方面,帮助打造高质量的前端应用。

Webpack 高级应用之与其他工具集成

Webpack 在前端开发生态中并非孤立存在,与其他工具的良好集成能进一步提升开发效率与项目质量。

与 Babel 集成

Babel 是 JavaScript 编译器,用于将现代 JavaScript 语法转换为旧版本浏览器可兼容的语法。

  1. 安装
npm install @babel/core @babel/preset - env babel - loader - D
  1. 配置webpack.config.js 中添加如下配置:
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset - env']
          }
        }
      }
    ]
  }
};

@babel/preset - env 会根据目标浏览器环境自动确定需要转换的语法。例如,目标浏览器是 Chrome 58+,则不需要转换 const 声明,因为 Chrome 58 已经支持。通过与 Babel 集成,Webpack 可以处理现代 JavaScript 代码,确保项目在不同浏览器环境下的兼容性。

与 ESLint 集成

ESLint 用于检查 JavaScript 代码是否符合特定的编码规范。

  1. 安装
npm install eslint eslint - loader - D
  1. 配置webpack.config.js 中:
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        enforce: 'pre',
        use: {
          loader: 'eslint-loader',
          options: {
            fix: true
          }
        }
      }
    ]
  }
};

enforce: 'pre' 表示该 loader 在其他 loader 之前执行,以便尽早发现代码错误。options.fix 设置为 true 可自动修复部分可修复的代码问题。同时,还需在项目根目录下配置 .eslintrc.js 文件来定义具体的编码规则。例如:

module.exports = {
  env: {
    browser: true,
    es6: true
  },
  extends: 'eslint:recommended',
  parserOptions: {
    ecmaVersion: 2018,
    sourceType:'module'
  },
  rules: {
    'semi': ['error', 'always'],
      'quotes': ['error', 'double']
  }
};

通过与 ESLint 集成,Webpack 在构建过程中可以及时发现并处理代码规范问题,保证代码质量。

与 PostCSS 集成

PostCSS 是一个用于转换 CSS 样式的工具,通过插件系统可以实现诸如自动添加浏览器前缀、CSS 模块化等功能。

  1. 安装
npm install postcss - loader postcss autoprefixer - D
  1. 配置webpack.config.js 中:
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style - loader',
           'css - loader',
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  require('autoprefixer')
                ]
              }
            }
          }
        ]
      }
    ]
  }
};

autoprefixer 插件会根据目标浏览器环境自动添加 CSS 前缀。例如,对于 display: flex,在目标浏览器为 Safari 时,会自动添加 -webkit - display: flex。通过与 PostCSS 集成,Webpack 可以更好地处理 CSS 样式,提升项目在不同浏览器下的兼容性与开发效率。