Webpack 4.0 打包优化策略详解:从配置到性能提升

作者:白帽猫时间:2026-10-22 13:29:23浏览:371

本文系统梳理了Webpack 4.0的打包优化策略,涵盖Loader配置优化、模块解析路径调整、noParse使用、HappyPack多线程构建、DLL动态链接库配置以及Tree Shaking代码剔除。通过具体配置示例,帮助开发者显著提升构建速度与最终包体积。

Webpack 4.0 打包优化概述

本文总结了Webpack 4.0环境下的打包优化方案,旨在为开发者提供实用的性能提升建议。

关于Webpack 4新特性的参考资料。

当前项目中依赖包的版本信息。

Webpack 4.0 打包优化概述 

Loader 配置优化

1. 优化Loader配置

1.1 缩小文件匹配范围(include/exclude)

通过排除node_modules目录下的文件,可以显著缩小Loader的搜索范围,从而提高命中目标文件的概率。


  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/, // 排除不处理的目录
        include: path.resolve(__dirname, 'src') // 精确指定要处理的目录
      }
    ]
  }

1.2 缓存Loader执行结果(cacheDirectory)

cacheDirectory是Loader的一个特定选项,默认值为false。通过指定目录(如使用 'babel-loader?cacheDirectory=cacheLoader')可以缓存Loader的执行结果,从而减少Webpack构建时Babel的重新编译过程。如果设置为空值('babel-loader?cacheDirectory')或true('babel-loader?cacheDirectory=true'),将使用默认的缓存目录(node_modules/.cache/babel-loader)。如果在根目录下未找到node_modules目录,则会降级回退到操作系统默认的临时文件目录。


module: {
  rules: [
    {
      test: /\.js$/,
      use: 'babel-loader?cacheDirectory', // 缓存loader执行结果 发现打包速度已经明显提升了
      exclude: /node_modules/,
      include: path.resolve(__dirname, 'src')
    }
  ]
}

Resolve 模块解析优化

2. 优化Resolve配置

2.1 优化模块查找路径(resolve.modules)

Webpack的resolve.modules配置模块库(即node_modules)所在的位置。当JS代码中出现import 'vue'这种既非相对也非绝对路径的写法时,Webpack会去node_modules目录下查找。默认配置采用向上递归搜索的方式,但通常项目根目录只有一个node_modules。为了减少搜索范围,可以直接写明node_modules的全路径。同样,对于别名(alias)的配置,也可以采用这种方式。

Webpack 4.0 打包优化概述

const path = require('path');
function resolve(dir) { // 转换为绝对路径
  return path.join(__dirname, dir);
}

resolve: {
  modules: [ // 优化模块查找路径
    path.resolve('src'),
    path.resolve('node_modules') // 指定node_modules所在位置 当你import 第三方模块时 直接从这个路径下搜索寻找
  ]
}

配置好src目录所在位置后,由于util目录位于src内,因此可以使用以下方式引入util中的工具函数。


// main.js

import dep1 from 'util/dep1';
import add from 'util/add';

2.2 配置路径别名(resolve.alias)

创建import或require的路径别名,使模块引入更加简洁。配置项通过别名将原导入路径映射为新的导入路径。需要注意的是,此优化方法可能会影响使用Tree Shaking去除无效代码的效果。

例如,一些位于src/文件夹下的常用模块:


alias: {
 Utilities: path.resolve(__dirname, 'src/utilities/'),
 Templates: path.resolve(__dirname, 'src/templates/')
}

现在,替换“在导入时使用相对路径”这种方式,操作如下:


import Utility from '../../utilities/utility';

你可以这样使用别名:


import Utility from 'Utilities/utility';

resolve: {
  alias: { // 别名配置 通过别名配置 可以让我们引用变的简单
    'vue$': 'vue/dist/vue.common.js', // $表示精确匹配
    src: resolve('src') // 当你在任何需要导入src下面的文件时可以 import moduleA from 'src/moduleA' src会被替换为resolve('src') 返回的绝对路径 而不需要相对路径形式导入
  }
}

也可以在给定对象的键后末尾添加$,以表示精准匹配:


alias: {
  util$: resolve('src/util/add.js')
}

这将产生以下结果:


import Test1 from 'util'; // 精确匹配,所以 src/util/add.js 被解析和导入
import Test2 from 'util/dep1.js'; // 精确匹配,触发普通解析 util/dep1.js

2.3 resolve.extensions

当引入模块时不带文件后缀,Webpack会根据此配置自动解析确定的文件后缀。

  1. 后缀列表尽可能小
  2. 频率最高的文件后缀放在前面
  3. 导出语句中尽可能带上文件后缀

resolve: {
  extensions: ['.js', '.vue']
}

忽略模块解析与多线程构建

3. module.noParse

使用了noParse配置的模块将不会被Loaders解析。因此,当我们使用的库体积较大,且其中不包含import、require或define调用时,可以使用此项配置来提升性能,让Webpack忽略对部分未采用模块化文件的递归解析处理。


// 忽略对jquery lodash的进行递归解析
module: {
  // noParse: /jquery|lodash/

  // 从 webpack 3.0.0 开始
  noParse: function(content) {
    return /jquery|lodash/.test(content)
  }
}

4. HappyPack

HappyPack将Webpack对Loader的执行过程从单一进程扩展为多进程模式,即将任务分解给多个子进程并发执行,子进程处理完后再将结果发送给主进程,从而加速代码构建。与DLL动态链接库结合使用效果更佳。


npm i happypack@next -D

webpack.config.js配置示例


const HappyPack = require('happypack');
const os = require('os'); // node 提供的系统操作模块

 // 根据我的系统的内核数量 指定线程池个数 也可以其他数量
const happyThreadPool = HappyPack.ThreadPool({size: os.cpus().lenght})

module: {
  rules: [
    {
      test: /\.js$/,
      use: 'happypack/loader?id=babel',
      exclude: /node_modules/,
      include: path.resolve(__dirname, 'src')
    }
  ]
},
plugins: [
  new HappyPack({ // 基础参数设置
    id: 'babel', // 上面loader?后面指定的id
    loaders: ['babel-loader?cacheDirectory'], // 实际匹配处理的loader
    threadPool: happyThreadPool,
    // cache: true // 已被弃用
    verbose: true
  });
]

HappyPack提供的Loader是对文件实际匹配处理的Loader。这里HappyPack提供的Loader与Plugin的衔接匹配,是通过id=happypack来完成的。


npm run dev

DLL 动态链接库配置

5. DLL动态链接库

在一个动态链接库中可以包含其他模块调用的函数和数据。动态链接库只需被编译一次,在后续的构建过程中,被动态链接库包含的模块将不会被重新编译,而是直接使用动态链接库中的代码。

  1. 将Web应用依赖的基础模块抽离出来,打包到单独的动态链接库中。一个链接库可以包含多个模块。
  2. 当需要导入的模块存在于动态链接库中时,该模块不会再次打包,而是去动态链接库中获取。
  3. 页面依赖的所有动态链接库都需要被加载。

5.1 定义DLL配置

依赖的两个内置插件:DllPlugin和DllReferencePlugin。

5.1.1 创建一个DLL配置文件webpack_dll.config.js


module.exports = {
  entry: {
    react: ['react', 'react-dom']
  },
  output: {
    filename: '[name].dll.js', // 动态链接库输出的文件名称
    path: path.join(__dirname, 'dist'), // 动态链接库输出路径
    libraryTarget: 'var', // 链接库(react.dll.js)输出方式 默认'var'形式赋给变量 b
    library: '_dll_[name]_[hash]' // 全局变量名称 导出库将被以var的形式赋给这个全局变量 通过这个变量获取到里面模块
  },
  plugins: [
    new webpack.DllPlugin({
      // path 指定manifest文件的输出路径
      path: path.join(__dirname, 'dist', '[name].manifest.json'),
      name: '_dll_[name]_[hash]', // 和library 一致,输出的manifest.json中的name值
    })
  ]
}

5.1.2 output.libraryTarget规定了以哪一种形式导出你的库,默认以全局变量形式(浏览器支持的形式)。

Webpack 4.0 打包优化概述

具体包括如下:

  1. "var" - 以直接变量输出(默认library方式)var Library = xxx (default)
  2. "this" - 通过设置this的属性输出this["Library"] = xxx
  3. "commonjs" - 通过设置exports的属性输出exports["Library"] = xxx
  4. "commonjs2" - 通过设置module.exports的属性输出module.exports = xxx
  5. "amd" - 以amd方式输出
  6. "umd" - 结合commonjs2/amd/root

5.1.3 打包生成动态链接库


webpack --config webpack_dll.config.js --mode production

Webpack 4.0 打包优化概述

在dist目录下多出react.dll.js和react.manifest.json。

  1. react.dll.js是动态链接库,里面包含了react和react-dom的内容。
  2. react.manifest.json描述了链接库(react.dll)中的信息。

5.2 在主配置文件中使用动态链接库文件


// webpack.config.js

const webpack = require('webpack');

plugins: [
  // 当我们需要使用动态链接库时 首先会找到manifest文件 得到name值记录的全局变量名称 然后找到动态链接库文件 进行加载
  new webpack.DllReferencePlugin({
    manifest: require('./dist/react.manifest.json')
  })
]

5.3 将动态链接库文件加载到页面中

需要借助两个Webpack插件。

html-webpack-plugin用于产出html文件。

html-webpack-include-assets-plugin将js和css资源添加到html中,扩展html插件的功能。


npm i html-webpack-plugin html-webpack-include-assets-plugin -D

配置webpack.config.js


const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlIncludeAssetsPlugin = require('html-webpack-include-assets-plugin');

pluings: [
  new webpack.DllReferencePlugin({
    manifest: require('./dist/react.manifest.json')
  }),
  new HtmlWebpackPlugin({
    template: path.join(__dirname, 'src/index.html')
  }),
  new HtmlIncludeAssetsPlugin({
    assets: ['./react.dll.js'], // 添加的资源相对html的路径
    append: false // false 在其他资源的之前添加 true 在其他资源之后添加
  });
]

此时react.dll.js和main.js被自动引入到页面中,并且dll文件在main.js之前加载。

Webpack 4.0 打包优化概述 

并行压缩与 Tree Shaking

6. ParallelUglifyPlugin

这个插件可以帮助拥有很多入口点的项目加快构建速度。它将JS文件的串行压缩变为开启多个子进程并行进行uglify。


cnpm i webpack-parallel-uglify-plugin -D

// webpck.config.js

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');

plugins: [
  new ParallelUglifyPlugin({
    workerCount: 4,
    uglifyJS: {
      output: {
        beautify: false, // 不需要格式化
        comments: false // 保留注释
      },
      compress: { // 压缩
        warnings: false, // 删除无用代码时不输出警告
        drop_console: true, // 删除console语句
        collapse_vars: true, // 内嵌定义了但是只有用到一次的变量
        reduce_vars: true // 提取出出现多次但是没有定义成变量去引用的静态值
      }
    }
  });
]

执行压缩操作。


webpack --mode production

7. Tree Shaking

剔除JavaScript中用不上的代码。它依赖静态的ES6模块化语法,例如通过import和export导入导出。

CommonJS模块与ES6模块的区别。

CommonJS模块:

1. 动态加载模块:CommonJS是运行时加载,能够轻松实现懒加载,优化用户体验。

2. 加载整个模块:CommonJS模块中,导出的是整个模块。

3. 每个模块皆为对象:CommonJS模块被视作一个对象。

4. 值拷贝:CommonJS的模块输出和函数的值传递相似,都是值的拷贝。

ES6模块

1. 静态解析:ES6模块在编译时加载,即在解析阶段就确定输出的模块的依赖关系,所以ES6模块的import一般写在被引入文件的开头。

2. 模块不是对象:在ES6里,每个模块并不会当做一个对象看待。

3. 加载的不是整个模块:在ES6模块中,一个模块中有好几个export导出。

4. 模块的引用:ES6模块中,导出的并不是模块的值的拷贝,而是这个模块的引用。

7.1 保留ES6模块化语法


// .babelrc

{
  "presets": [
    [
      "env", {
        modules: false // 不要编译ES6模块
      },
      "react",
      "stage-0"
    ]
  ]
}

7.2 执行生产编译,默认已开启Tree Shaking。


webpack --mode production

什么是Tree Shaking?

假设有个funs.js,里面有两个函数。


// funs.js
export const sub = () => 'hello webpack!';
export const mul = () => 'hello shaking!';

main.js中依赖funs.js。


// main.js
import {sub} from './funs.js'

sub();

在main.js中只使用了里面的sub函数,默认情况下也会将funs.js里面其他没有使用的函数也打包进来。如果开启Tree Shaking,在生产编译时:


webpack --mode production //此时funs.js中没有被用到的代码并没打包进来 而被剔除出去了

未完待续...

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章