文章目录
chainWebpack长用配置集合
webpack 配置很多,这里我们探讨比较经常需要修改的:
- 不复杂,可以在 configWebpack 中操作:
- mode
- devtool
- 配置复杂,可以在 chainWebpack 中操作:
- module.rules
- plugins
- optimization
2、使用vue inspect
可以查看当前 webpack 配置
vue inspect > output.js
chainWebpack长用配置方式
//vue.config.js
module.exports={
chainWebpack:config=>{
}
}
}
1 输入输出配置
module.exports = {
chainWebpack: config => {
// 清理所有默认入口配置
config.entryPoints.clear();
// 增加一个入口main
config.entry("main").add("./src/main.js");
// 增加一个入口about
config.entry("about").add("./src/about.js");
config.output
.path("dist")//输出目录
.filename("[name].[chunkhash].js")//输出文件名
.chunkFilename("chunks/[name].[chunkhash].js")//输出chunk名
.libraryTarget("umd")//输出格式
.library();//输出库
}
};
// 其他的output配置
config.output
.auxiliaryComment(auxiliaryComment)
.chunkFilename(chunkFilename)
.chunkLoadTimeout(chunkLoadTimeout)
.crossOriginLoading(crossOriginLoading)
.devtoolFallbackModuleFilenameTemplate(devtoolFallbackModuleFilenameTemplate)
.devtoolLineToLine(devtoolLineToLine)
.devtoolModuleFilenameTemplate(devtoolModuleFilenameTemplate)
.filename(filename)
.hashFunction(hashFunction)
.hashDigest(hashDigest)
.hashDigestLength(hashDigestLength)
.hashSalt(hashSalt)
.hotUpdateChunkFilename(hotUpdateChunkFilename)
.hotUpdateFunction(hotUpdateFunction)
.hotUpdateMainFilename(hotUpdateMainFilename)
.jsonpFunction(jsonpFunction)
.library(library)
.libraryExport(libraryExport)
.libraryTarget(libraryTarget)
.path(path)
.pathinfo(pathinfo)
.publicPath(publicPath)
.sourceMapFilename(sourceMapFilename)
.sourcePrefix(sourcePrefix)
.strictModuleExceptionHandling(strictModuleExceptionHandling)
.umdNamedDefine(umdNamedDefine)
2.设置别名
const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set("@$", resolve("src"))
.set("assets", resolve("src/assets"))
.set("components", resolve("src/components"))
.set("layout", resolve("src/layout"))
.set("base", resolve("src/base"))
.set("static", resolve("src/static"))
.delete("base"); // 删掉指定的别名
// .clear() 会把全部别名都删掉
}
};
3. 配置代理
module.exports = {
chainWebpack: config => {
config.devServer
.port("8080")
.open(true)
.proxy({
"/api": {
target: "http://www.baidu.com",
changeOrigin: true,
pathRewrite: {
"^/api": ""
}
}
});
}
};
// 注意:changeOrigin 属性名字具有误导性,它并不是表示修改 Origin 头部,而是表示修改 Host 头部的 Origin(原始值)。从上面官网的那句话中 the origin of the host 也可以理解出来。
//f12中查看未生效是正常的,因为vite node服务中代理了一次浏览器的请求,所以冲浏览器的角度来看host还是它发出请求的host,vite node代理服务器看host才是改的值
// chain其余proxy的配置
config.devServer
.bonjour(bonjour)
.clientLogLevel(clientLogLevel)
.color(color)
.compress(compress)
.contentBase(contentBase)
.disableHostCheck(disableHostCheck)
.filename(filename)
.headers(headers)
.historyApiFallback(historyApiFallback)
.host(host)
.hot(hot)
.hotOnly(hotOnly)
.https(https)
.inline(inline)
.info(info)
.lazy(lazy)
.noInfo(noInfo)
.open(open)
.openPage(openPage)
.overlay(overlay)
.pfx(pfx)
.pfxPassphrase(pfxPassphrase)
.port(port)
.progress(progress)
.proxy(proxy)
.public(public)
.publicPath(publicPath)
.quiet(quiet)
.setup(setup)
.socket(socket)
.staticOptions(staticOptions)
.stats(stats)
.stdin(stdin)
.useLocalIp(useLocalIp)
.watchContentBase(watchContentBase)
.watchOptions(watchOptions)
5. 使用插件和添加、删除插件参数
使用插件
// 添加API
config
.plugin(name)
.use(WebpackPlugin, args)
// 一个例子
const fileManager = require("filemanager-webpack-plugin");
...
//注意:use部分,不能使用new的方式建立插件实例
webpackConfig.plugin("zip").use(fileManager, [
{
onEnd: {
archive: [
{
source: "dist",
destination: zipName
}
]
}
}
]);
修改参数、添加参数
module.exports = {
chainWebpack: config => {
// 可使用tap方式,修改插件参数
config.plugin(name).tap(args => newArgs);
// 一个例子
config
.plugin("env")
//使用tag修改参数
.tap(args => [...args, "SECRET_KEY"]);
//更改html插件的title
config.plugin("html").tap(args => {
args[0] = "documnet的title";
return args[0];
});
}
};
6.修改插件初始化和删除插件
修改插件
module.exports = { chainWebpack: config => { config.plugin(name).init((Plugin, args) => new Plugin(...args)); }};
删除插件
module.exports = { chainWebpack: config => { config.plugins.delete("prefetch"); // 移除 preload 插件 config.plugins.delete("preload"); }};
7.有前后顺序的插件
// A插件之前要调用B插件;config .plugin(name) .before(otherName)module.exports = { chainWebpack: config => { config .plugin("b") .use(B) .end() .plugin("a") .use(A) .before(B); }};
// A插件之后要调用B插件;config.plugin(name).after(otherName);module.exports = { chainWebpack: config => { config .plugin("a") .after("b") .use(A) .end() .plugin("b") .use(B); }
9. performace性能警告阀配置
config.performance .hints(hints)//false | "error" | "warning"。打开/关闭提示 .maxEntrypointSize(maxEntrypointSize)//入口起点表示针对指定的入口,对于全部资源,要充分利用初始加载时(initial load time)期间。此选项根据入口起点的最大致积,控制 webpack 什么时候生成性能提示。默认值是:250000 .maxAssetSize(maxAssetSize)//资源(asset)是从 webpack 生成的任何文件。此选项根据单个资源体积,控制 webpack 什么时候生成性能提示。默认值是:250000 .assetFilter(assetFilter)//此属性容许 webpack 控制用于计算性能提示的文件
使用、修改、删除loader
module.exports = { chainWebpack: config => { config.module .rule(name) .use(name) .loader(loader) .options(); }};//修改参数module.exports = { chainWebpack: config => { config.module.rule(name); use(name) .loader(loader) .tap(options => { let newOptions = { ...options, xx: "黑黑" }; return newOptions; }); }};// 覆盖原来的loadermodule.exports = { chainWebpack: config => { const svgRule = config.module.rule("svg"); // 清除已有的全部 loader。 // 若是你不这样作,接下来的 loader 会附加在该规则现有的 loader 以后。 svgRule.uses.clear(); svgRule.use("vue-svg-loader").loader("vue-svg-loader"); }};//使原来loader忽略某个目录,使用新的loadermodule.exports = { chainWebpack: config => { config.module .rule("svg") .exclude.add(resolve("src/icons")) .end(); config.module .rule("icons") .test("/.svg$/") .include.add(resolve("src/icons")) .end() .use("svg-sprite-loader") .options({ symbolId: "icon-[name]" }) .end(); }};
条件
config.when(process.env.NODE_ENV !== "development", config => { config .plugin("ScriptExtHtmlWebpackPlugin") .after("html") .use("script-ext-html-webpack-plugin", [ { // `runtime` must same as runtimeChunk name. default is `runtime` inline: /runtime\..*\.js$/ } ]) .end(); config.optimization.splitChunks({ chunks: "all", cacheGroups: { libs: { name: "chunk-libs", test: /[\\/]node_modules[\\/]/, priority: 10, chunks: "initial" // only package third parties that are initially dependent }, elementUI: { name: "chunk-elementUI", // split elementUI into a single package priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm }, commons: { name: "chunk-commons", test: resolve("src/components"), // can customize your rules minChunks: 3, // minimum common number priority: 5, reuseExistingChunk: true } } }); // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk config.optimization.runtimeChunk("single"); });
删
删除单个规则中的全部 loader
// vue.config.jsmodule.exports = { chainWebpack: config => { const svgRule = config.module.rule('svg') // 清除已有规则 svg 的所有 loader。 // 如果你不这样做,接下来的 loader 会附加在该规则现有的 loader 之后。 svgRule.uses.clear() // 添加要替换的 loader svgRule .use('vue-svg-loader') .loader('vue-svg-loader') }}
删除单个规则中的一个loader
// 删除前:{ test: /\.m?jsx?$/, exclude: [ function () { /* omitted long function */ } ], use: [ /* config.module.rule('js').use('cache-loader') */ { loader: 'cache-loader', options: { cacheDirectory: 'D:\\webproject\\webapp-jy\\node_modules\\.cache\\babel-loader', cacheIdentifier: '519fc596' } }, /* config.module.rule('js').use('babel-loader') */ { loader: 'babel-loader' } ]}// 删除后:const jsRule = config.module.rule('js')// 删除 cache-loaderjsRule.uses.delete('cache-loader'){ test: /\.m?jsx?$/, exclude: [ function () { /* omitted long function */ } ], use: [ /* config.module.rule('js').use('babel-loader') */ { loader: 'babel-loader' } ]}
删除单个规则
const moduleRule = config.module// 删除命名为 js 的规则moduleRule.rules.delete('js')
删除插件
config.plugins.delete(name)
删除 optimization.minimizers
config.optimization.minimizers.delete(name)
增
增加规则
// loader 默认是从下往上处理// enforce: 决定现有规则调用顺序// - pre 优先处理// - normal 正常处理(默认)// - inline 其次处理// - post 最后处理module.exports = { chainWebpack: config => { config.module .rule('lint') // 定义一个名叫 lint 的规则 .test(/\.js$/) // 设置 lint 的匹配正则 .pre() // 指定当前规则的调用优先级 .include // 设置当前规则的作用目录,只在当前目录下才执行当前规则 .add('src') .end() .use('eslint') // 指定一个名叫 eslint 的 loader 配置 .loader('eslint-loader') // 该配置使用 eslint-loader 作为处理 loader .options({ // 该 eslint-loader 的配置 rules: { semi: 'off' } }) .end() .use('zidingyi') // 指定一个名叫 zidingyi 的 loader 配置 .loader('zidingyi-loader') // 该配置使用 zidingyi-loader 作为处理 loader .options({ // 该 zidingyi-loader 的配置 rules: { semi: 'off' } }) config.module .rule('compile') .test(/\.js$/) .include .add('src') .add('test') .end() .use('babel') .loader('babel-loader') .options({ presets: [ ['@babel/preset-env', { modules: false }] ] }) }}
最后将解析为如下配置:
{
module: {
rules: [
/* config.module.rule('lint') */
{
test: /\.js$/,
enforce: 'pre',
include: ['src'],
use: [
/* config.module.rule('lint').use('eslint') */
{
loader: 'eslint-loader',
options: {
rules: {
semi: 'off'
}
}
},
/* config.module.rule('lint').use('zidingyi') */
{
loader: 'zidingyi-loader',
options: {
rules: {
semi: 'off'
}
}
}
]
},
/* config.module.rule('compile') */
{
test: /\.js$/,
include: ['src', 'test'],
use: [
/* config.module.rule('compile').use('babel') */
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { modules: false }]
]
}
}
]
}
]
}
}
config.optimization.splitChunks
Webpack 4给我们带来了一些改变。包括更快的打包速度,引入了SplitChunksPlugin插件来取代(之前版本里的)CommonsChunksPlugin插件。在这篇文章中,你将学习如何分割你的输出代码,从而提升我们应用的性能。
SplitChunks插件(webpack 4.x以前使用CommonsChunkPlugin
)允许我们将公共依赖项提取到现有的entry chunk
或全新的代码块中。
代码分割的理念
首先搞明白: webpack里的代码分割是个什么鬼? 它允许你将一个文件分割成多个文件。如果使用的好,它能大幅提升你的应用的性能。其原因是基于浏览器会缓存你的代码这一事实。每当你对某一文件做点改变,访问你站点的人们就要重新下载它。然而依赖却很少变动。如果你将(这些依赖)分离成单独的文件,访问者就无需多次重复下载它们了。
使用webpack生成一个或多个包含你源代码最终版本的“打包好的文件”(bundles),(概念上我们当作)它们由(一个一个的)chunks组成。
首先 webpack 总共提供了三种办法来实现 Code Splitting,如下:
- 入口配置:entry 入口使用多个入口文件;
- 抽取公有代码:使用 SplitChunks 抽取公有代码;
- 动态加载 :动态加载一些代码。
这里我们姑且只讨论使用 SplitChunks
splitChunks配置
在src目录下创建三个文件pageA.js、pageB.js和pageC.js。代码详情见文章开头git仓库。
// src/pageA.js
var react = require('react');
var reactDom = require('react-dom');
var utility1 = require('../utils/utility1');
var utility2 = require('../utils/utility2');
new Vue();
module.exports = "pageA";
// src/pageB.js
var react = require('react');
var reactDom = require('react-dom');
var utility2 = require('../utils/utility2');
var utility3 = require('../utils/utility3');
module.exports = "pageB";
// src/pageC.js
var react = require('react');
var reactDom = require('react-dom');
var utility2 = require('../utils/utility2');
var utility3 = require('../utils/utility3');
module.exports = "pageC";
入口文件 && 出口文件
entry: {
pageA: "./src/pageA", // 引用utility1.js utility2.js
pageB: "./src/pageB", // 引用utility2.js utility3.js
pageC: "./src/pageC", // 引用utility2.js utility3.js
},
output: {
path: path.join(__dirname, "dist"),
filename: "[name].[hash:8].bundle.js"
},
配置optimization
首先我们配置optimization如下:
optimization: {
splitChunks: {
chunks: "all",
},
执行npm run build打包命令之后,查看dist目录
可以发现,打包出来的除了三个page文件,还存在一个vendorspageApageB~pageC.[hash].bundle.js文件(此文件中保存了pageA、pageB、pageC和node_modules中共有的size大于30KB的文件)。事实上这全靠了配置中本身默认固有一个cacheGroups的配置项:
splitChunks: {
chunks: "all",
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, // 匹配node_modules目录下的文件
priority: -10 // 优先级配置项
},
default: {
minChunks: 2,
priority: -20, // 优先级配置项
reuseExistingChunk: true
}
}
}
在默认设置中,会将 node_mudules 文件夹中的模块打包进一个叫 vendors的bundle中,所有引用超过两次的模块分配到 default bundle 中。更可以通过 priority 来设置优先级。
参数说明如下:
- chunks:表示从哪些chunks里面抽取代码,除了三个可选字符串值 initial、async、all 之外,还可以通过函数来过滤所需的 chunks;
- minSize:表示抽取出来的文件在压缩前的最小大小,默认为 30000;
- maxSize:表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
- minChunks:表示被引用次数,默认为1;上述配置commons中minChunks为2,表示将被多次引用的代码抽离成commons。
值得注意的是,如果没有修改minSize属性的话,而且被公用的代码(假设是utilities.js)size小于30KB的话,它就不会分割成一个单独的文件。在真实情形下,这是合理的,因为(如分割)并不能带来性能确实的提升,反而使得浏览器多了一次对utilities.js的请求,而这个utilities.js又是如此之小(不划算)。
- maxAsyncRequests:最大的按需(异步)加载次数,默认为 5;
- maxInitialRequests:最大的初始化加载次数,默认为 3;
- automaticNameDelimiter:抽取出来的文件的自动生成名字的分割符,默认为 ~;
- name:抽取出来文件的名字,默认为 true,表示自动生成文件名;
- cacheGroups: 缓存组。(这才是配置的关键)
缓存组会继承splitChunks的配置,但是test、priorty和reuseExistingChunk只能用于配置缓存组。cacheGroups是一个对象,按上述介绍的键值对方式来配置即可,值代表对应的选项。除此之外,所有上面列出的选择都是可以用在缓存组里的:chunks, minSize, minChunks, maxAsyncRequests, maxInitialRequests, name。可以通过optimization.splitChunks.cacheGroups.default: false禁用default缓存组。默认缓存组的优先级(priotity)是负数,因此所有自定义缓存组都可以有比它更高优先级(译注:更高优先级的缓存组可以优先打包所选择的模块)(默认自定义缓存组优先级为0)
现在我们再重新来看一下pageA、pageB、pageC三个js文件,这三个文件中都引入了utility2.js文件,但是此文件size很明显小于30KB,所以这部分公用代码并没有分割出来。如果想要分割出来很简单,只需要:
optimization: {
splitChunks: {
chunks: "all",
minSize: 0
},
执行npm run build打包命令之后,查看dist目录
显然多了一个pageApageBpageC.[hash].bundle.js文件。查看文件可得知此文件中存储了utility2.js中的代码。如下图所示(借助于webpack-bundle-analyzer插件,详情文章末尾附录)。
上图可以看出,React相关代码均放在了vendorspageApageB~pageC.[hash].bundle.js文件中,如果我们想要抽离出React代码,应该怎么做呐?
splitChunks: {
chunks: "all",
cacheGroups: {
commons: {
chunks: "initial",
minChunks: 2,
name: "commons",
maxInitialRequests: 5,
minSize: 0, // 默认是30kb,minSize设置为0之后
// 多次引用的utility1.js和utility2.js会被压缩到commons中
},
reactBase: {
test: (module) => {
return /react|redux|prop-types/.test(module.context);
}, // 直接使用 test 来做路径匹配,抽离react相关代码
chunks: "initial",
name: "reactBase",
priority: 10,
}
}
},
run build之后如下图所示。
看似非常完美,但是reactBase文件中竟然包含了node_modules,神奇的问题?室友都睡觉了,这键盘声影响不好,明天接着看。
附录
我们再安装一个 webpack-bundle-analyzer,这个插件会清晰的展示出打包后的各个bundle所依赖的模块:
npm i webpack-bundle-analyzer -D
引入:
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
使用,在plugins数组中添加即可:
new BundleAnalyzerPlugin()
config.when(process.env.NODE_ENV!== 'development',config=>{
config.optimization.splitChunks({
chunks: "all", // 共有3个值"initial","async"和"all"。配置后,代码分割优化仅选择初始块,按需块或所有块
minSize: 30000, // (默认值:30000)块的最小大小
minChunks: 1, // (默认值:1)在拆分之前共享模块的最小块数
maxAsyncRequests: 5, //(默认为5)按需加载时并行请求的最大数量
maxInitialRequests: 3, //(默认值为3)入口点的最大并行请求数
automaticNameDelimiter: '~', // 默认情况下,webpack将使用块的来源和名称生成名称,例如vendors~main.js
name: true,
cacheGroups: { // 以上条件都满足后会走入cacheGroups进一步进行优化的判断
vendors: {
test: /[\\/]node_modules[\\/]/, // 判断引入库是否是node_modules里的
priority: -10, // 数字越大优先级越高 (-10大于-20)
filename: 'vendors.js' // 设置代码分割后的文件名
},
default: { //所有代码分割快都符合默认值,此时判断priority优先级
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 允许在模块完全匹配时重用现有的块,而不是创建新的块。
}
}
})
})
mixin 全局注入
css: {
// varibles mixin 全局注入
loaderOptions: {
sass: {
data: `@import "~@/styles/variables.scss";@import "~@/styles/mixin.scss";`
}
}
},