Webpack的支柱功能模块 plugin及其优化方法

webpack最重要的四大模块

  1. 入口起点:开始构建的起始文件。

  2. 出口文件:构建的文件,存放的位置、名称以及是否覆盖上一次构建文件等。

  3. 加载器loader:webpack只能理解 js和json 文件,使用 loader 之后可以处理其他类型的文件,并将他们转换成webpack可识别的模块。比如babel-loader: 将es6转成es5,react-loader: 加载react文件,vue-loader同理,以及css-loader,加载css文件,sass-loader将sass代码转成css代码,image-loader,ts-loader:将ts转成js代码等。

  4. 插件plugin:打包优化,资源管理,注入环境变量。

前面几篇文章,详细介绍过入口起点、出口文件加载器loader,感兴趣的可以前往查看

plugin插件

plugins 选项用于以各种方式自定义 webpack 构建过程。webpack 附带了各种内置插件,可以通过 webpack.[plugin-name] 访问这些插件。

请查看 内置插件 ,获取插件列表和对应文档,但请注意这只是其中一部分,社区中还有许多插件。如果其中没有想要的插件,还可以编写一个插件

插件目的在于解决 loader 无法实现的其他事

现在能在webpack中使用的插件,种类很多,功能也很强大,下面介绍几种,其他使用方法都类似

CleanWebpackPlugin插件

这个插件是用来清除打包的文件。当我们修改要打包的文件名称时,如果我们不加上这个插件,就会另外生成一个打包的文件,原来的那个不会自动删除,所以这个插件是用来偷懒的,会自动帮助我们删除原来打包的文件。

js 复制代码
// webpack 4中的使用方法
const path = require('path');
const {CleanWebpackPlugin}  = require('clean-webpack-plugin')
module.exports = {
	mode:'production',
	entry:'./src/index.js',
	output:{
		path:path.resolve(__dirname,'dist'),
		filename:'index.js'
	},
	//注册插件
	//webpack 会自动调用 apply 方法,把插件注册到 webpack 中
	plugins:[
		new CleanWebpackPlugin();
	]
}

// webpack 5中的使用方法
const path = require('path');
module.exports = {
	mode:'production',
	entry:'./src/index.js',
	output:{
		path:path.resolve(__dirname,'dist'),
		filename:'index.js',
		//webpack 5使用这个这个方法
		clean:true
	}
}

HTMLWebpackPlugin插件

简化了 HTML 文件的创建,并且自动在 html 引入 js 文件。有了这个插件,就可以在修改了打包文件的名称后,自动修改 index.html 中引用打包文件的名称。

安装

css 复制代码
npm install --save-dev html-webpack-plugin

只需添加该插件到你的 webpack 配置中,该插件将为你生成一个 HTML5 文件, 在 body 中使用 script 标签引入你所有 webpack 生成的 bundle。

js 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

module.exports = {
  entry: 'index.js',
  output: {
    path: path.resolve(__dirname, './dist'),
    filename: 'index_bundle.js',
  },
  plugins: [new HtmlWebpackPlugin()],
};

这将会生成一个包含以下内容的 dist/index.html 文件

js 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>webpack App</title>
  </head>
  <body>
    <script src="index_bundle.js"></script>
  </body>
</html>

MiniCssExtractPlugin 插件

这个插件是用来处理当我们使用 css-loader 时候,我们样式会展示在 html 中的 header 中的 style 里面这样不方便我们以后进行优化,这个插件可以把相应的 css 生成一个单独的 css 文件中,放在打包的目录中。

js 复制代码
const path = require('path');
module.exports = {
	mode:'production',
	entry:'./src/index.js',
	output:{
		path:path.resolve(__dirname,'dist'),
		filename:'index.js',
		//webpack 5使用这个这个方法
		clean:true
	},
	module:{
		//使用raw-loader,用来处理txt文本文件
		rules:[
		{
			test:/\.css$/,
			//同一类loader中执行顺序,先下后上,先右后左。先执行css-loader->style-loader
			use:[
			//替换style-loader 为插件内置的loader
				{
					loader:MiniCssExtractPlugin.loader
				},
				'css-loader'
			]
		},
	plugins:[
		new MiniCssExtractPlugin({
		//这个插件内置一个loader,需要我们替换掉 style-loader
			filename:'[name].css'
		})
	]
}

访问内置方法

js 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // 访问内置的插件
const path = require('path');

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        use: 'babel-loader',
      },
    ],
  },
  plugins: [
    new webpack.ProgressPlugin(), // ProgressPlugin方法就是内置方法
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};

写一个插件

plugin原理:

webpack 在编译代码过程中,会通过compilation触发一系列 Tapable 钩子事件。我们可以注入事件,拥有更强的构建能力。Tapable 还统一暴露了三个方法。

tap:可以注册同步钩子和异步钩子。

tapAsync:回调方式注册异步钩子。

tapPromise:tapPromise 方式注册异步钩子。

需求:可以删除console.log()

js 复制代码
class DelConsoleWebpackPlugin {
    apply(compiler) {
        // 在资源输出之前触发
        compiler.hooks.emit.tapAsync("pluginName", (compilation,callback) => {
            // 遍历编译后的资产对象
            for (const name in compilation.assets) {
                //文件内容 compilation.assets[name].source()
                let source = compilation.assets[name].source()
                //匹配文件 name.endsWith('.js')
                if (name.endsWith('.js')) {
                    // 匹配所有的 `console.log` 语句
                    let rConsole =/console.log(.*?)/g;
                    // 将源文件内容中的所有 `console.log` 语句替换为空字符串
                    source = source.replace(rConsole, "");
                    // 将修改后的源文件重新赋值给资产对象
                    
                    compilation.assets[name] = {
                        source: function () {
                            return source;
                        },
                        size: function () {
                            return source.length;
                        }
                    }
                }
            }
            callback()
        }); 
    }
}

module.exports = DelConsoleWebpackPlugin;

这个插件使用 compiler.hooks.emit.tapAsync 方法在 Webpack 构建的资源输出之前触发。然后,它遍历编译后的资产对象,检查文件名是否以 .js 结尾。如果是,它使用正则表达式 console.log(.*?)/g 来匹配所有的 console.log 语句,并使用 replace 方法将其替换为空字符串。

最后,它将修改后的源文件重新赋值给资产对象,并返回该对象。

模式(Mode)

提供 mode 配置选项,告知 webpack 使用相应模式的 内置优化

支持三个配置项:

选项 描述
development 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPluginTerserPlugin
none 不使用任何默认优化选项

开发模式(development)

  1. 启用快速迭代和调试功能,例如实时重新加载(hot module replacement)、详细的错误信息和Source Map。
  2. 优化侧重于快速的构建时间和快速的开发体验。
  3. 不会进行代码压缩、tree-shaking(摇树优化)等优化操作,以确保代码的可调试性。
  4. 生成的代码体积较大,不适合用于生产环境。

生产模式(production)

  1. 启用优化功能,例如代码压缩、tree-shaking、代码分割(Code Splitting)等。
  2. 优化侧重于减小构建后的代码体积和提高性能。
  3. 会启用各种优化插件,如 UglifyJS、Terser 等,以压缩和混淆代码。
  4. 会删除不必要的代码和注释,以减小代码体积。
  5. 生成的代码体积较小,适合用于生产环境。

用法:

js 复制代码
module.exports = {
  mode: 'development', // 开发模式
  mode: 'production', // 生产模式
  mode: 'none', 
};

tree shaking 【摇树】(过滤掉没有调用到的代码)

检查整个链路中属性的使用情况,确定没有使用就去掉,但是只会在生产模式下才会过滤,开发模式都会打包。

js 复制代码
// 生产模式就会触发 摇树
module.exports = {
  mode: 'production', // 生产模式
};

optimization(优化)

webpack中优化有很多配置项,根据不同需求,选择合适的方法即可

下面介绍一个常用的,压缩优化和代码拆分

optimization.minimize

用于控制是否启用代码压缩,false表示不启用代码压缩,即生成的代码不会被压缩。如果设置为true,则会启用代码压缩,生成的代码体积会更小。

js 复制代码
module.exports = {
  //...
  optimization: {
    minimize: false,
  },
};

注意:想在开发环境使用优化,要将optimization.minimize 设置为 true

optimization.minimizer

用于配置代码压缩的插件

js 复制代码
// webpack.config.js
let path = require('path');
let HtmlWebpackPlugin = require("html-webpack-plugin"); // html 插件 自动在html 引入js文件
let MiniCssExtractPlugin = require('mini-css-extract-plugin'); //插件 抽取css作为单独的文件 
let TerserPlugin = require('terser-webpack-plugin') // js压缩
let OptimizeCssPlugin = require('optimize-css-assets-webpack-plugin') // css压缩
let CssPlugin = require('css-minimizer-webpack-plugin') // css压缩

module.exports = {
    optimization: {
        //压缩
        minimizer: [new TerserPlugin(), new CssPlugin()]
    },
    //入口  出口  loader  plugin
    entry: "./src/index.js", //输入
    mode: 'production',  //production development // 模式
    output: { // 输出
        //__dirname 表示当前目录
        path: path.resolve(__dirname, "dist"),  //绝对路径
        filename: "index.js"                    //输出的文件名
    },
    devServer: {  //配置webpack-dev-server
        port: 8083,   //配置web服务端口
        open: true,   //自动打开浏览器
        progress: true,   //进度
        contentBase: './dist'  //指定web服务器的根目录
    },
    module: { //装载器
        rules: [
            {
                test:/\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            "@babel/preset-env"
                        ],
                        "plugins": [
                            ["@babel/plugin-proposal-decorators", { "legacy": true }],
                            ["@babel/plugin-proposal-class-properties", { "loose" : true }]
                        ]
                    }
                }
            },
            {
                test: /\.css$/,
                // use: ['style-loader','css-loader'] // 执行顺序:从右到从,其他规则一致
                use: [MiniCssExtractPlugin.loader, 'css-loader']
            },
            {
                test: /\.less$/,
                // use: ['style-loader','css-loader','less-loader']
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
            }
        ]
    },
    plugins: [  // 插件
        new HtmlWebpackPlugin({
            template: './src/index.html',   //指定输出的文件模板
            filename: 'index.html'           //指定输出dist的文件名
        }),
        new MiniCssExtractPlugin({
            filename: 'index.css'
        })
    ]
}

optimization.usedExports

上面说到的摇树,也是优化的一种,不在生产环境中,也是可以用usedExports属性实现

js 复制代码
module.exports = {
  //...
  optimization: {
    usedExports: true, // boolean方式
    // 或者
    usedExports: 'global', // string方式
  },
};

optimization.splitChunks

使用 SplitChunks 优化,可以将代码根据配置项,拆分成较小的块

js 复制代码
optimization: {
      splitChunks: { // 控制代码分割
        // 有效值为 `all`,`async` 和 `initial`
        chunks: 'all', // 对所有的块进行分割
        name: true, // 表示为每个块生成一个唯一的名称
        minSize: 1, // 表示最小分割块的大小为 1 字节
        minChunks: 1, // 表示如果一个块被多个模块引用,它将被分割
        cacheGroups: { // 定义了多个缓存组,用于根据不同的规则对代码进行分割
          default: false, // 禁用任何默认缓存组
          defaultVendors: { // 缓存组适用于 node_modules 目录下的所有模块
            name: 'common', // 缓存组的名称,用于在缓存中标识
            chunks: 'initial', // initial 表示应用程序的入口模块
            minChunks: 1, // 最小的 chunk 数量,只有在满足最小 chunk 数量时才会创建缓存
            enforce: true, // 是否强制使用缓存。如果为 `true`,则在满足条件时必须使用缓存,否则将抛出错误。
            test: /node_modules/, // 一个正则表达式,用于匹配要缓存的模块
            reuseExistingChunk: true // -   -   是否重用现有的 chunk。如果为 `true`,则如果已经存在匹配的缓存,则直接使用,而不是创建新的缓存。
          },
          vuePhotoPreviewVenodr: {
            test: /(vue-photo-preview)/, // 表示匹配 `vue-photo-preview` 模块
            priority: 103, // 缓存的优先级。优先级越高,越先被使用。
            name: 'vuePhotoPreviewVenodr', 
            chunks: 'async' // `async`表示适用于异步加载的 chunks
          },
          'async-commons': { // 这个缓存组适用于其他异步加载的包
            // 其余异步加载包
            chunks: 'async',
            minChunks: 2,
            name: 'async-commons',
            priority: 90
          },
          commons: { // 这个缓存组适用于其他同步加载的包
            // 其余同步加载包
            chunks: 'all',
            minChunks: 2,
            name: 'commons',
            priority: 80
          },
          styles: { // 这个缓存组适用于所有的样式文件
            name: 'common',
            chunks: 'initial',
            minChunks: 1,
            test: /\.(css|less|scss|stylus)$/,
            enforce: true,
            priority: 50
          }
        }
      }
    }

上面的例子中,只是其中一部分配置项

外部扩展(Externals)

防止 import 引入的依赖包也进行了打包(将依赖排除在打包外),在运行时再去外部获取这些依赖,可以减小构建的 bundle 的大小,提高加载速度,并且可以更好地管理依赖关系。

有多种不同的方式

字符串

js 复制代码
module.exports = {
  //...
  externals: 'jquery',
};

注意:外部模块设置为字符串时,Webpack 会将其视为一个全局变量,并在构建的代码中使用该全局变量来引用外部模块。如果你希望将外部模块作为一个模块来使用,而不是作为一个全局变量,可以使用其他方式来配置 externals

对象

js 复制代码
module.exports = {
  //...
  externals: {
    jquery: 'jquery', // 全局变量
  },
};

还可以根据不同的环境,来指定不同的别名,再使用不同的别名,来将外部模块排除掉

js 复制代码
module.exports = {
  //...
  externals: {
    react: 'react',
  },

  // 或者
  externals: {
    lodash: {
      commonjs: 'lodash',
      amd: 'lodash',
      root: '_', // 指向全局变量
    },
  },
};
  • commonjs: 'lodash':指定了在 CommonJS 环境下的别名,即在使用 require('lodash') 时,Webpack 会将其解析为 require('lodash')
  • amd: 'lodash':指定了在 AMD 环境下的别名,即在使用 define(['lodash'], function(lodash) {}) 时,Webpack 会将其解析为 define(['lodash'], function(lodash) {})
  • root: '_':指定了在根目录下的别名,即在代码中使用 _ 时,Webpack 会将其解析为 lodash

数组

js 复制代码
module.exports = {
  //...
  externals: {
    subtract: ['./math', 'subtract'],
  },
};

外部模块为 subtract['./math', 'subtract']:这是一个数组,其中包含了两个别名。第一个别名是 ./math,表示在代码中使用 import subtract from './math' 时,Webpack 会将其解析为 import subtract from './math'。第二个别名是 subtract,表示在代码中使用 import subtract from 'subtract' 时,Webpack 会将其解析为 import subtract from './math'

函数

用函数匹配一个正则表达式,将匹配成功的,都进行外部化(排除)

js 复制代码
module.exports = {
  //...
  externals: [
    function ({ context, request }, callback) {
      if (/^yourregex$/.test(request)) {
        // 使用 request 路径,将一个 commonjs 模块外部化
        return callback(null, 'commonjs ' + request);
      }

      // 继续下一步且不外部化引用
      callback();
    },
  ],
};

正则

正则表达式匹配成功的依赖,都将从输出包中排除。

js 复制代码
module.exports = {
  //...
  externals: /^(jquery|$)$/i,
};

混用语法

将前面的字符串、对象、数组、函数、正则混合使用,在externals数组中,使用多个语法。

js 复制代码
module.exports = {
  //...
  externals: [
    {
      // 字符串
      react: 'react',
      // 对象
      lodash: {
        commonjs: 'lodash',
        amd: 'lodash',
        root: '_', // indicates global variable
      },
      // 字符串数组
      subtract: ['./math', 'subtract'],
    },
    // 函数
    function ({ context, request }, callback) {
      if (/^yourregex$/.test(request)) {
        return callback(null, 'commonjs ' + request);
      }
      callback();
    },
    // 正则表达式
    /^(jquery|$)$/i,
  ],
};

devtool 源映射

此选项控制是否生成,以及如何生成 source map(映射文件)

作用:

它将编译后的代码映射回原始的源代码,使得在调试时能够在代码中显示行号和列号等信息,方便定位错误和进行调试。

配置项很多,下面举个例子

js 复制代码
// vue.config.js
const isBuildProduction = process.env.NODE_ENV === 'production';

module.exports = {
  lintOnSave: !isBuildProduction,
  configureWebpack: {
    // 生产环境: 打包 source-map 且不泄露源码,并且报错时有组件信息而不是编译后代码的信息
    devtool: isBuildProduction
      ? 'nosources-source-map'
      : 'eval-cheap-module-source-map',
   }
}
  • devtool:指定构建时使用的 source map 类型。在生产环境下,使用'nosources-source-map'表示不包含源文件的 source map,以保护源代码的隐私。在非生产环境下,使用'eval-cheap-module-source-map'表示使用快速的 eval 源映射,以便在开发时更好地调试代码。

使用 SourceMapDevToolPlugin 插件进行更细粒度的配置。

使用 source-map-loader 来处理已有的源映射。

相关推荐
莹雨潇潇4 分钟前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr13 分钟前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho1 小时前
【TypeScript】知识点梳理(三)
前端·typescript
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记3 小时前
【复习】HTML常用标签<table>
前端·html
程序员大金3 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
丁总学Java3 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele3 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀3 小时前
CSS——属性值计算
前端·css