Webpack Plugin & Loader

313 阅读17分钟

历史

JavaScript引入模块化的早期, 在浏览器中使用JavaScript有两种方式。 第一种,引用一些脚本存放;弊端是难扩展,脚本过多,不利于页面加载。第二种,使用一个包含所有项目代码的大型.js文件;弊端是作用域问题,单文件过大,代码可读性、可维护性差。

立即调用函数表达式(IIFE:Immediately invoked function expressions) 是一个在定义时就会立即执行的Javascript函数;函数的变量是不能在外部访问的,也就不会污染window 环境;它解决了大型项目作用域问题,基于此可以安全组合.js文件; IIFE这种方式,产生了Make, Gulp, Grunt, BroccoliBrunch 等工具,它们会把所有的项目文件拼接在一起,这些工具被称为任务执行器。但这种方式无法实现跨文件重用脚本;当使用三方库时,也无法删除无用的代码,比如使用lodash库中的某个函数时,却会引入整个库。 示例如图: image.png

Node.js 发布于20095月,是一个基于Chrome引擎的运行环境,可以在浏览器环境之外的计算机和服务器中使用。Node.js没有htmlscript标签,它使用CommonJSrequire 机制来加载Javascript代码块,它的问世才使得Javascript逐步具备模块化能力;但CommonJS仅是Node.js的模块化解决方案,起初浏览器是不支持CommonJS的模块化方案的,为了实现基于CommonJS模块化编程,并且能够使其运行于浏览器的打包工具,于是便有了Browserify, RequireJSSystemJS等打包工具。下图为使用RequireJS实现模块化的示例:

image.png

ECMAScript Modules 简称ESM,是官方标准模块格式,于 2015 年推出,用于打包JavaScript,以便重复使用。(ECMAScript : 一种JavaScript的标准)。它的模块是使用各种 importexport 语句定义的。Node.js是完全支持ESM的,并提供了与CommonJS的互操作性。目前浏览器大部分都已经支持了。

image.png

概念

虽然时至今时,浏览器对应ESM提供了良好的支持,但webpack依然是Javascript工具链的关键部分。webpack是个用于现代JavaScript应用程序的静态模块打包工具。 它不仅可以支持ESMCommonJS模块化编程,而且还可以支持或扩展支持许多不同的静态资源,例如:Files,Images, Fonts, JS, CSS, HTML等等。同时也提供多种功能,如合并模块,代码最小化(消除空格,备注,垃圾代码或减少代码)、SASSTS编译等。

webpack打包应用程序时,会从一个或多个入口文件开始递归构建一个依赖关系图,然后将项目所需的每个模块组合成一个或多个bundles, 它们均为静态资源,可由浏览器加载。这些依赖可以是代码资源,也可以是非代码资源,如Files,Images, Fonts等。

image.png

创建项目

接下来我们从01,逐步搭建一个基于webpack的项目,并针对开发、生产模式下一些场景,逐步示例介绍,来让我们对webpack从使用和概念上进一步的了解。

#1.创建一个项目文件夹并接入文件夹
mkdir mywebpackapp
cd mywebpackapp
#2.创建一个package.json
npm init -y
#3.安装webpack、webpack-cli
npm install --save-dev webpack webpack-cli
#4.创建 webpack.config.js
touch webpack.config.js
#5.创建 index.html、index.js
touch index.html
touch index.js
touch index.css

补充对应内容后的项目骨架如下图:

image.png

Entry&Output

webpack配置中 entryoutput分别用来指示程序从何处开始构建,以何种方式输出到何处。entry入口起点可以是一个也可以是多个:

  • 当只有一个入口时,可以通过output.filename命名输出后的文件,也可以通过修改entry值为对象,则其属性的key为输出后的文件名称,value为入口文件路径(当entry为字符串或字符串数组时,默认名称为main)。

image.png

  • 当有多个入口时,需要保证每个文件名称都具有唯一性,此时需要使用占位符(可替换模板字符串),比如,[id][name][chunkhash][contenthash]

image.png

配置output.clean = true,可以每次打包新的产物时,清除旧的产物。

Plugin

插件webpack的支柱功能,它是一个具有apply方法的JavaScript对象。apply方法会被webpack compiler调用,并且插件在其整个生命周期都可以访问到compiler对象,因此他可以hook整个编译的生命周期。

image.png

上述示例,配置的最终产物,缺少了index.html,只有js文件;因此我们需要产出index.html并且引入我们每次编译后的js bundle文件。

html-webpack-plugin此插件可以生成html5文件,并且会使用script标签引入我们的js bundle,我们使用它来构建我们的index.html文件,首先我们需要安装插件包:

npm install --save-dev html-webpack-plugin

配置与最终结果如下图:

image.png

此时在浏览器运行index.html,便会执行js文件。

loader

loader用于对模块的源代码转换,它相当于编译期间的一个任务。起初webpack只理解javaScript文件,但是webpack将每个作为模块导入的文件视为依赖项,并将其添加到依赖关系图中。因此为了处理静态资源的导入,例如:Files,Images, Fonts, CSS, Json等,webpack使用Loader来将这些文件加载到bundle中。

Load CSS

上述示例,通过js的方式为页面增加了内容,并设置了css样式,如下图:

image.png

一切准备就绪,执行打包脚本,发现报错:You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file

为了能够导入CSS,我们需要安装style-loadercss-loader,并在module.rules下配置这些loader:

npm install --save-dev style-loader css-loader

具体配置如下:

image.png

重新打包后,浏览器运行index.html,会发现css样式生效了;不过需要注意的是 导入css文件,应保证 loader 的先后顺序:style-loader 在前,而 css-loader 在后。 否则webpack可能会抛出错误

loader 可以链式调用, 链中的每个 loader 都将对资源进行依次转换和传递。最后,webpack期望链中的最后一个loader返回javaScript

静态资源模块

webpack5之前的版本中,加载静态资源模块,需要使用raw-loaderurl-loaderfile-loader

  • raw-loader: 加载文件的原始内容(utf8),将文件作为字符串进行导入;
  • file-loader: 将文件导出到输出文件夹,并返回相对的URL
  • url-loader: 和file-loader一样,但是当文件小于限制的大小时便会返回data URI(如:data:image/svg+xml;base64,....);
const config = {
    //...
    module: {
        rules: [
            // {
            //     test: /\.(png|svg|jpg|jpeg|gif)$/i,
            //     use: [
            //         'url-loader?limit=8192',//小于8k时内联为data urI
            //     ]
            // },,
            {
                test: /\.(png|jpg)$/i,
                use: [
                    {
                        loader: 'url-loader',
                        options: {
                            limit: 8192 //小于8k时内联为data urI
                        }
                    }
                ]
            },
            {
                test: /\.txt$/i,
                use: 'raw-loader',
            },
            {
                test: /\.(svg|gif)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[path][name].[ext]',
                        },
                    },
                ],
            },
        ]
    }

}

webpack5开始,新增资源模块类型(asset modules type),通过四种新的模块类型,替换了所有这些loader:

  1. asset/resource: 输出文件并导出相对URL;之前使用file-loader实现。
  2. asset/inline: 导出资源的data URI; 之前使用url-loader实现。
  3. asset/source: 导出资源的源代码;之前使用raw-loader实现。
  4. asset: 在导出一个data URI和导出一个单独的文件之间自动选择;之前使用url-loader,并配置资源大小限制来实现。
const config = {
    //...
    module: {
        rules: [
            //...
            {
                test: /\.png/,
                type: 'asset/resource' //导出文件返回相对URL
            },
            {
                test: /\.svg/,
                type: 'asset/inline', //以`data URI`内联导入资源
            },
            {
                /*
                import exampleText from './example.txt';
                block.textContent = exampleText; // 'Hello world'
                */
                test: /\.txt/,
                type: 'asset/source', //导入资源的源代码
            },
        ]
    }

}

自定义输出文件名

默认情况下,asset/resource 模块以 [hash][ext][query] 文件名导出到输出目录。有两种方式可以修改输出的文件名:

  1. 配置output.assetModuleFilename
const config = {
    entry: {
        main: './index.js',
        code: './code.js'
    },//入口文件,默认输出为main.js
    output: {
        clean: true,//每次打包清除旧的编译产物
        path: path.resolve(__dirname, 'dist'),//输出到dist目录下
        filename: '[name].[chunkhash].js',
        assetModuleFilename: 'images/[name][hash][ext]' ///修改输出的资源文件名称
    },
    module: {
        rules: [
            ///...
            {
                type: 'asset/resource',
                test: /\.(png|svg|jpg|jpeg|gif)$/i
            }
        ]
    }

}
  1. 发送资源到指定目录
const config = {
    entry: {
        main: './index.js',
        code: './code.js'
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[chunkhash].js',
    },
    module: {
        rules: [
            ///...
            {
                type: 'asset/resource',
                test: /\.(png|svg|jpg|jpeg|gif)$/i,
                generator: {
                    filename: 'static/[name][hash][ext]' ///指定输出到dist/static/...
                }
            }
        ]
    }

}

rule.generator.filenameoutput.assetModuleFilename 相同,并且仅适用于 assetasset/resource 模块类型。

自定义data URI生成器

webpack 输出的 data URI,默认是使用 Base64 算法编码的文件内容。如果需要自定义,则需要自定义函数来编码文件内容:


const svgToMiniDataURI = require('mini-svg-data-uri');

const config = {
    ///...
    module: {
        rules: [
            ///...
            {
                test: /\.svg/,
                type: 'asset/inline',
                generator: {
                    dataUrl: content => {
                        content = content.toString();
                        return svgToMiniDataURI(content);
                    }
                }
            }
        ]
    }
}

通用资源类型

通用资源类型asset会按照默认的条件在resourceinline之间进行选择;默认条件为:小于8kb为文件视为inline模块类型,否则视为resource模块类型。可以通过Rule.parser.dataUrlCondition.maxSize 来修改此条件:

const svgToMiniDataURI = require('mini-svg-data-uri');

const config = {
    ///...
    module: {
        rules: [
            ///...
            {
                test: /\.svg/,
                type: 'asset/inline',
                parser: {
                    dataUrlCondition: {
                        maxSize: 12 * 1024 // 12kb
                    }
                },///小于12kb inline 否则 resource
                generator: {
                    dataUrl: content => {
                        content = content.toString();
                        return svgToMiniDataURI(content);
                    }
                }
            }
        ]
    }
}

resolve

模块化编程中,一个模块可以作为另一个模块的依赖模块,并通过require/import语句进行引用,当最终需要打包模块时,需要使用绝对路径来找到模块的代码。webpack内部使用enhanced-resolve来解析文件路径,而webpack配置项resolve可以将我们的自定义选项传递给enhanced-resolvewebpack使用enhanced-resolve能解析三种文件路径:

  1. 直接引入的绝对路径
  2. 拼接上下文目录的相对路径
  3. 指定检索的模块路径。通过resolve.modules配置;也可以通过resolve.alias配置别名替换初始模块路径的方式实现。
import '/home/me/file';//绝对路径
//or
import '../../file';//相对路径
//or
import 'module/lib/file'; //模块路径

resolve.modules

配置模块解析时检索的目录,比如解析import _ from 'lodash';默认为node_modules

module.exports = {
  //...
  resolve: {
    modules: ['node_modules'],
  },
};

如果需要添加一个优先于node_modules搜索的目录:

const path = require('path');

module.exports = {
  //...
  resolve: {
    modules: [path.resolve(__dirname, 'src'), 'node_modules'],
  },
};

resolve.alias

模块解析时,webpack配置的别名可以替换初始模块的路径,这种方式可以让我们在引入模块时变的简单。

比如,项目中常见的引入语句:

import { daysBetween } from '../../../utils/date'

当我们移动了代码内容后,便需要重新修改import的模块路径。因此我们可以配置为其配置别名:

const path = require('path');
module.exports = {
  //...
  resolve: {
    alias: {
      Utils: path.resolve(__dirname, 'src/utils/'),
    },
  },
};

之后导入模块,便可以使用别名来替换初始模块的路径。

import { daysBetween } from 'Utils/date'

也可以在别名(alias对象的键)的末尾添加$表示精准匹配:

const path = require('path');

module.exports = {
  //...
  resolve: {
    alias: {
      xyz$: path.resolve(__dirname, 'path/to/file.js'),
    },
  },
};

这种配置产生的结果:

import Test1 from 'xyz'; // 精确匹配,所以 path/to/file.js 被解析和导入
import Test2 from 'xyz/file.js'; // 非精确匹配,触发普通解析

resolve.extensions

在进行模块解析时,如果解析到的文件没有扩展名称时,webpack便会通过resolve.extensions提供的扩展名选项为其进行匹配,基于此我们可以使用import 'index'代替import 'index.js'(不必使用扩展)。

如果有多个文件有相同的名字,但后缀名不同,webpack 会解析列在数组首位的后缀的文件 并跳过其余的后缀。

module.exports = {
  //...
  resolve: {
    extensions: ['.js', '.json', '.vue'],
  },
};

需要注意的是resolve.extensions会覆盖默认数组,这就意味使用自定义后webpack将不会使用默认的扩展来解析模块,如果要继续使用默认扩展名,可以在数组中加入...:

module.exports = {
  //...
  resolve: {
    extensions: ['.ts', '...'],
  },
};

代码分离

代码分离是webpack最亮眼的特性之一,可以把代码分离到不同的bundle中,并按需加载或者并行加载这些文件。代码分离可以获取最小的bundle,控制资源加载的优先级。

常用的代码分离有三种:

  1. 入口起点:使用entry手动配置分离。
  2. 防止重复:使用entry配置下的dependOn或者SplitChunksPlugin 去重和分离chunk
  3. 动态导入:通过模块内联函数的调用来分离代码。

入口起点

这种代码分离的方式比较直观,也存在问题。以下是示例工程目录及文件:

image.png webpack通过入口起点分离代码的配置如下:

const config = {
    entry: {
        main: './index.js',
        code: './code.js'

    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[chunkhash].js',
        assetModuleFilename: 'images/[name][hash][ext]'
    },
    //...
}

module.exports = config;

编译输出的结果:

image.png

这种方式存在的问题:

  1. main.jscode.js中的lodash会被重复引入到各自的bundle中。
  2. 不够灵活,不能动态分离main.jscode.js的公共代码。

防止重复

入口依赖

webpack通过入口依赖分离代码的配置如下:

const config = {
    entry: {
        main: {
            import: './index.js',
            dependOn: 'commonChunk'
        },
        code: {
            import: './code.js',
            dependOn: 'commonChunk'
        },
        commonChunk: ['lodash']
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[chunkhash].js',
        assetModuleFilename: 'images/[name][hash][ext]'
    },
    //...
}

编译输出:

image.png 为了避免一个html页面上使用多个入口会遇到的问题,还需设置optimization.runtimeChunk: 'single'

搞清楚runtimeChunk之前,我们需要先知道webpack构建程序的三种代码类型:

  1. 自己编写的代码
  2. 第三方代码或库
  3. webpackruntimemanifest,管理所有的模块交互。

manifest: 是webpack编译后用来记录所有模块详细信息的数据。 runtime: 是webpack编译后的代码在浏览器中运行时,webpack用来连接模块化应用程序需要的所有代码;包括模块加载和解析逻辑、连接模块逻辑、延迟加载逻辑。它会通过manifest中的模块数据来进行加载和解析。

optimization.runtimeChunk可以将webpackruntime代码拆分为一个单独的chunk。将其设置为single可以为所有的chunk创建一个runtime bundle

基于此修改配置如下:

const config = {
    entry: {
        main: {
            import: './index.js',
            dependOn: 'commonChunk'
        },
        code: {
            import: './code.js',
            dependOn: 'commonChunk'
        },
        commonChunk: ['lodash']
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[chunkhash].js',
        assetModuleFilename: 'images/[name][hash][ext]'
    },
    //...
    optimization:{
        runtimeChunk: 'single' 
    }
}

编译后的产物:

image.png

通过编译产物发现这种方式不仅分离了代码还分离了公共模块commonChunk,同时还多了一个runtime

基于入口依赖的多页面应用配置:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
    entry: {
        main: {
            import: './index.js',
            dependOn: 'commonChunk'
        },
        code: {
            import: './code.js',
            dependOn: 'commonChunk'
        },
        commonChunk: ['lodash']
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js',
        assetModuleFilename: 'images/[name][hash][ext]'
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename:'main.html',
            template: './index.html',
            chunks: ['main','commonChunk'],//默认是all
        }),
        new HtmlWebpackPlugin({
            filename:'code.html',
            template: './index.html',
            chunks: ['code','commonChunk'],//默认是all
        }),
    ],
    //...

}
module.exports = config;

编译输出的产物

image.png

对比发现多了一个页面,并且页面运行各自加载js代码。

SplitChunksPlugin

SplitChunksPlugin插件可以将公共的依赖模块提取到已有的入口chunk中或提取到一个新生成的chunk中,基于此我们可以使用它将示例中重复的lodash模块提取成单独的chunk

SplitChunksPluginwebpack中是开箱即用的,它通过以下条件来自动拆分chunks:

  1. 新的chunk可以被共享,或模块来自于node_moudles文件夹
  2. 新的chunk体积大于20kb
  3. 当按需加载chunks时,并行请求的最大数量小于或等于30?
  4. 当加载初始化页面时,并发请求的最大数量小于或等于30?

当尝试满足后两个条件时,最好使用较大的chunks。?

webpack通过SplitChunksPlugin分离代码的配置如下:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
    entry: {
        main: './index.js',
        code: './code.js',
    },//入口文件,默认输出为main.js
    output: {
        clean: true,//每次打包清除旧的编译产物
        path: path.resolve(__dirname, 'dist'),//输出到dist目录下
        filename: '[name].[chunkhash].js',
        assetModuleFilename: 'images/[name][hash][ext]'
    },
    //...
    optimization:{
        splitChunks: {
            // 有效值为 all,async 和 initial
            // all 意味着 chunk 可以在异步和非异步 chunk 之间共享
            chunks: 'all'
        }
    }

}

module.exports = config;

通过编译后的产物可以看出成功分离了三方库lodash

image.png

动态导入

使用ESMimport()或者require.ensure来实现动态导入。改变index.js中内容:

// import _ from 'lodash';
import './index.css'///执行导入
// function component() {
//   const element = document.createElement('div');
//   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
//   element.className = 'title'
//   return element;
// }

async function component() {
  try {
    const { default: _ } = await import('lodash');
    const element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.className = 'title';
    return element;
  } catch (error) {
    console.log('加载组件失败');
  }
}
// document.body.appendChild(component());

component().then(component=>{
  document.body.appendChild(component);
})

修改webpack的配置文件:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
    entry: {
        main: './index.js',
        // code: './code.js',
    },//入口文件,默认输出为main.js
    //....
    // optimization:{
    //     splitChunks: {
    //         // 有效值为 all,async 和 initial
    //         // all 意味着 chunk 可以在异步和非异步 chunk 之间共享
    //         chunks: 'all'
    //     }
    // }

}

module.exports = config;

编译后产物分离了三方库lodash

image.png

缓存

前端项目的上线流程简单总结主要就是webpack打包生成可部署的/dist目录,最后将/dist目录中的内容部署到server上。如此便可以在client(浏览器)访问server上的网站及资源,这个过程中资源的获取是比较耗时的,因此浏览器中使用了缓存的技术(命中要素通常为资源文件名是否变化),可以通过命中缓存以降低网络流量,使网页的加载速度更快。

为了有效利用client的缓存技术,webpack针对缓存的配置是很有必要的;它可以保证编译产物能够被client缓存,而在文件内容变化后,能够请求到新的文件。

Output Filename

通过output.filename提供的可替换模板字符串(如,[id][name][chunkhash])来配置输出的文件名为[contenthash],这个占位符字符串将根据资源内容创建出唯一的hash。也就是说当资源内容发生变化时[contenthash]也会变化。

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
    entry: {
        main: './index.js',
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js',/// 资源名称
        assetModuleFilename: 'images/[name][hash][ext]'
    },
   //...
}

module.exports = config;

编译输出的产物:

image.png

资源内容不作改变再次编译后的产物:

image.png

如果不作修改再次构建,文件名有可能会发生改变,这种不确定的输出主要是和webpack的版本有关系,新版本可能不会发生改变,但webpack仍旧推荐提取引导模板

提取引导模板

引导模板(boilerplate): webpack运行时所需要的引导代码,主要是runtimemanifest。每次从config.entry中配置的入口chunk开始编译,会包含这部分的引导代码。

简单讲就是将rumtime拆分成一个单独的模块,正如代码分离所讲,可以配置optimization.runtimeChunk: 'single'来为所有的chunk创建一个runtime bundle

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const config = {
    entry: {
        main: './index.js',
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js',/// 资源名称
        assetModuleFilename: 'images/[name][hash][ext]'
    },
   //...
    optimization: {
        runtimeChunk: 'single',
    },
}

module.exports = config;

编译后的产物多了runtime chunk:

image.png

提取三方库

第三方库在项目中使用时,几乎不会频繁的改动,因此将这部分代码提取到单独的vendor chunk中,可以有效的利用client的缓存机制,有效减少资源请求。这部分的提取主要依赖SplitChunksPlugin,我们可以通过配置optimization.splitChunks来为SplitChunksPlugin传递参数,从而实现三方库的提取。

webpack.config.js

const config = {
    entry: {
        main: './index.js',
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js',
        assetModuleFilename: 'images/[name][hash][ext]'
    },
    //...
    optimization: {
        runtimeChunk: 'single',
        splitChunks: {
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all',
                },
                ///提取特定的库
                // lodash: {
                //     name: 'lodash',
                //     test: /[\\/]node_modules[\\/]lodash[\\/]/,
                //     chunks: 'all',
                //     priority: 3, //优先级
                //     reuseExistingChunk: true,
                //     enforce: true
                // },
            }
        }
    },

}

module.exports = config;

编译后输出的产物:

image.png

模块标识符

index.js导入一个来自code.js的方法。

image.png

再次基于上一步的webpack配置进行编译,输出产物如下:

image.png

通过对比发现runtime hash因为manifest包含了一个新的模块引用,而发生了改变;main hash因为内容的改变也发生了改变。这一切都是符合预期的,但是demowebpack的版本是最新的5.75.0,因此vendorhash并没有发生变化,但是在其他较低的webpack版本中,vendor输出的hash可能会发生变化,这是因为每个module.id会默认的基于解析的顺序进行增量改变,即当解析顺序发生改变,module.id也会随之改变,这就会导致vendorhash也会因不同的module.id而发生变化。

webpack可以通过optimization.moduleIds = 'deterministic'来解决这种问题。

webpack.config.js

const config = {
    entry: {
        main: './index.js',
    },
    output: {
        clean: true,
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[contenthash].js',
        assetModuleFilename: 'images/[name][hash][ext]'
    },
    //...
    optimization: {
        moduleIds: 'deterministic',
        runtimeChunk: 'single',
        splitChunks: {
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all',
                },
                ///提取特定的库
                // lodash: {
                //     name: 'lodash',
                //     test: /[\\/]node_modules[\\/]lodash[\\/]/,
                //     chunks: 'all',
                //     priority: 3, //优先级
                //     reuseExistingChunk: true,
                //     enforce: true
                // },
            }
        }
    },

}

module.exports = config;

参考

webpack.docschina.org/ dev.to/alexeagleso…