如何颗粒化修改vue-cli中的webpack配置?
引言
在使用vue-cli
作为vue
项目的脚手架时,由于其已经内置了许多的loader
和plugin
,免去了我们从头配置webpack
的诸多烦恼。但就因为vue-cli
内置了这些loader
和plugin
,当我们想更颗粒化更改这些配置时,不免有些头疼,甚至想直接把vue-cli
替换了,又换成原生的webpack
(又回到痛苦的原点)?或是直接引入一个新的loader
或者plugin
。就像这样:
configureWebpack:{
...
plugins:[
...
new CompressPlugin({
algorithm: 'gzip',
test: /.(js|css)$/,
threshold: 10000,
deleteOriginalAssets: false
}),
...
]
}
如果你是这样操作的,那你对vue-cli
了解的还是不够,我们的这些颗粒化地需求vue-cli
早就考虑到了。接下来就让笔者带着你了解一些vue-cli
的高级操作吧。
前置知识
阅读本文,你需要对webpack
和vue-cli
比较熟悉,尤其是webpack
。但你只需要了解其中的entry
、output
、loader
、plugin
等概念即可,未曾了解请先移步下方的文档。
正文开始
简要介绍
或许你看到过vue.config.js
中的配置项chainWebpack
,其为一个函数,该函数只有一个参数,为ChainableConfig
的实例,将允许我们颗粒化地修改webpack
的配置。
官方也告诉你,其底层由webpack-chain提供。感谢这个作者,让webpack
的配置可以像JQuery
那样支持链式调用。
介绍完了chainWebpack
,也只是知道要从哪修改我们的默认webpack
配置,还得知道默认的webpack
配置是什么才行(都不知道vue-cli的默认配置改个鸡儿? )。于是,vue-cli-service inspect
闪亮登场了。
我们可以通过命令,将默认配置输出到一个文件中,便于观察:
vue inspect > output.js # 开发模式的配置
vue inspect --mode production >output.js #生产模式的配置
是不是很熟悉?这就是vue-cli
帮我们生成的webpack
配置了。
通过vue inspect
命令输出的配置,我们将发现形如
/* config.module.rule('vue') */
/* config.plugin('define') */
这样的注释,这告诉我们可以在chainWebpack
中,通过config.xxx的形式拿到module
和plugin
相关的配置,来进行进一步地颗粒化修改。
例如:
//指定入口文件,不然找的是main.ts
config.entry('app').clear().add(resolve(__dirname, 'src/main.js'))
//这将重新指定入口文件,这对于生产和开发环境下使用不同的入口文件,或是多页应用指定不同的入口文件很有作用。
颗粒化修改output配置
在生产环境下,我们通常希望我们的打包的文件具有版本或者hash信息,以实现批量发版的功能。
例如,我们可以通过使用webpack
内置的contentHash实现文件内容变动文件重新命名(功能变更和bug修复),以实现浏览器的"自动刷新"。
在vue.config.js
的配置项chainWebpack
中:
config.output.filename('static/js/[name].[chunkhash:8].js')
config.output.chunkFilename('static/js/[name].[chunkhash:8].js')
//此部分在下文中会讨论
config.plugin('extract-css').tap((args) => [
{
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].css'
}
])
通过config.output
,我们可以看到许多有用的方法,支持我们颗粒化地修改我们的输出。
颗粒化配置plugin
修改options
上文中的:
config.plugin('extract-css').tap((args) => [
{
filename: 'static/css/[name].[contenthash:8].css',
chunkFilename: 'static/css/[name].[contenthash:8].css'
}
])
修改的是mini-css-extract-plugin实例化时的options
。.plugin表示拿到这个Plugin
对象,tap方法将接受一个函数,函数的参数是一个数组,数组的每一项表示其Plugin
实例化一次的options,至于args
是数组的原因,显而易见,plugin
可能会被实例化多次,数组的每一项就代表着plugin
被vue-cli
实例化了一次。
那么,我们如何知道plugin()
方法中的name
是extract-css
,而不是其它,比如plugin('extract-css-plugin')
呢?
还记得上文的vue inspect
吗?
在我们输出的文件output.js
中,用编辑器的查找功能:config.plugin('
,我们就能看到所有plugin的配置信息,就可以找到mini-css-extract-plugin
这个plugin的配置了(修改之前的默认配置)。
上方的注释十分贴心地告诉你,可以通过config.plugin('extract-css')
拿到这项配置,加上tap()
方法,就可以修改options了!
例如,我们想修改html-webpack-plugin给我们指定的默认title(通常是package.json
的name字段),我们可以:
config.plugin('html').tap((args) => {
args[0].title = '我是你想要的title,亲亲嘛~~'
return args
})
config.plugin('html')
也是通过观察vue inspect
的输出得到的。
注意了!
务必保证形如'html'确实是存在于
vue-cli
的配置中,否则给错了名字,打包时会报错,大概如下:
增加plugin
如果是新增一个vue-cli并没有内置的plugin,比如compress-webpack-plugin。可以通过config.plugin('compress').use()
添加,就像上面的警告告诉你的那样:
const CompressPlugin = require('compression-webpack-plugin')
......
//使用 compress-webpack-plugin开启gzip压缩
config.plugin('compress').use(CompressPlugin, [
{
algorithm: 'gzip',
test: /.(js|css)$/,
threshold: 10000,
deleteOriginalAssets: false
}
])
如果不想通过vue inspect
查找,你甚至可以通过config.plugins.has('html')
验证vue-cli
是否已经有了此plugin
的配置,通过node.js process.exit(0)
的帮助,你可以快速得到验证,且不用完整运行一次构建程序:
console.log('has html?', config.plugins.has('html'))
process.exit(0)
移除plugin
要移除某个plugin,可以通过此语句移除:
config.plugins.clear()//清除所有的plugins配置
config.plugins.delete('compress')//移除特定的plugin配置
颗粒化配置loader
取到对应的loader
在chainWebpack
中既然能颗粒化地修改webpack
的配置,自然也能修改loader的配置。
首先,我们可以通过vue inspect
查看所有的rule
:
可以看到,js、ts、css和vue包括一些静态资源的处理的loader
一应俱全。
通过config.module.rules.get('css')
拿到具体某一个rule
的配置。.get(name:string)
方法的参数name
就是我们输出时看到的name。
具体的输出信息如下:
loaders===> <ref *2> {
parent: <ref *1> {
parent: {
parent: undefined,
store: [Map],
devServer: [Object],
entryPoints: [Object],
module: [Circular *1],
node: [Object],
optimization: [Object],
output: [Object],
performance: [Object],
plugins: [Object],
resolve: [Object],
resolveLoader: [Object],
shorthands: [Array],
amd: [Function (anonymous)],
bail: [Function (anonymous)],
cache: [Function (anonymous)],
context: [Function (anonymous)],
devtool: [Function (anonymous)],
externals: [Function (anonymous)],
loader: [Function (anonymous)],
mode: [Function (anonymous)],
name: [Function (anonymous)],
parallelism: [Function (anonymous)],
profile: [Function (anonymous)],
recordsInputPath: [Function (anonymous)],
recordsPath: [Function (anonymous)],
recordsOutputPath: [Function (anonymous)],
stats: [Function (anonymous)],
target: [Function (anonymous)],
watch: [Function (anonymous)],
watchOptions: [Function (anonymous)]
},
store: Map(1) { 'noParse' => /^(vue|vue-router|vuex|vuex-router-sync)$/ },
rules: { parent: [Circular *1], store: [Map] },
defaultRules: { parent: [Circular *1], store: Map(0) {} },
shorthands: [ 'noParse', 'strictExportPresence' ],
noParse: [Function (anonymous)],
strictExportPresence: [Function (anonymous)]
},
store: Map(1) { 'test' => /.css$/ },
name: 'css',
names: [ 'css' ],
ruleType: 'rule',
ruleTypes: [ 'rule' ],
uses: { parent: [Circular *2], store: Map(0) {} },
include: { parent: [Circular *2], store: Set(0) {} },
exclude: { parent: [Circular *2], store: Set(0) {} },
rules: { parent: [Circular *2], store: Map(0) {} },
oneOfs: {
parent: [Circular *2],
store: Map(4) {
'vue-modules' => [Object],
'vue' => [Object],
'normal-modules' => [Object],
'normal' => [Object]
}
},
resolve: <ref *3> {
parent: [Circular *2],
store: Map(0) {},
alias: { parent: [Circular *3], store: Map(0) {} },
aliasFields: { parent: [Circular *3], store: Set(0) {} },
descriptionFiles: { parent: [Circular *3], store: Set(0) {} },
extensions: { parent: [Circular *3], store: Set(0) {} },
mainFields: { parent: [Circular *3], store: Set(0) {} },
mainFiles: { parent: [Circular *3], store: Set(0) {} },
modules: { parent: [Circular *3], store: Set(0) {} },
plugins: { parent: [Circular *3], store: Map(0) {} },
shorthands: [
'cachePredicate',
'cacheWithContext',
'concord',
'enforceExtension',
'enforceModuleExtension',
'symlinks',
'unsafeCache'
],
cachePredicate: [Function (anonymous)],
cacheWithContext: [Function (anonymous)],
concord: [Function (anonymous)],
enforceExtension: [Function (anonymous)],
enforceModuleExtension: [Function (anonymous)],
symlinks: [Function (anonymous)],
unsafeCache: [Function (anonymous)]
},
shorthands: [
'enforce',
'issuer',
'parser',
'resource',
'resourceQuery',
'sideEffects',
'test',
'type'
],
enforce: [Function (anonymous)],
issuer: [Function (anonymous)],
parser: [Function (anonymous)],
resource: [Function (anonymous)],
resourceQuery: [Function (anonymous)],
sideEffects: [Function (anonymous)],
test: [Function (anonymous)],
type: [Function (anonymous)]
}
由于此处使用了rule.oneOf,我们可以使用config.module.rules.get('css').oneOfs.get('normal')
拿到某一项的配置。这里的normal大概是处理普通的css文件吧。
再通过config.module.rules.get('css').oneOfs.get('normal').uses.get('css-loader').values()
拿到具体的loader
,看到输出为:
loaders===> [
'...\node_modules\.pnpm\css-loader@6.8.1_webpack@5.88.1\node_modules\css-loader\dist\cjs.js',
{ sourceMap: false, importLoaders: 2 }
]
输出一个元组,第一项是css-loader
的入口路径,第二项是其options。
修改options的配置
参考npmjs的说明,这里,我们想增加一项配置,例如:将exportType
设置为css-style-sheet
(默认是array
)。
可以通过.tap((args:LoaderOptions)=>LoaderOptions)
方法实现
config.module.rules
.get('css')
.oneOfs.get('normal')
.uses.get('css-loader')
.tap((args) => {
args.exportType = 'css-style-sheet'
return args
})
输出为:
loaders222===> [
'...\node_modules\.pnpm\css-loader@6.8.1_webpack@5.88.1\node_modules\css-loader\dist\cjs.js',
{ sourceMap: false, importLoaders: 2, exportType: 'css-style-sheet' }
]
这时,细心的你就会发现loader
和plugin
都是通过一个tap()
方法来修改其原有的配置,都需要传入一个带返回值的函数,只不过loader
的参数类型是LoaderOptions
,plugin
的是PluginOptions[]
。
增添loader
我们开发时根据具体的业务情况可能会需要对某个rule
配置增添loader
,但请注意,loader
配置时一个文件可能有多个loader
,且处理顺序从右到左(从下到上)
(sass-loader>postcss-loader>css-loader>extract-css-loader)
尝试加入一个vue-style-loader,将其插入的use:
数组中的不同位置,如下操作所示:
- 默认加入第一项,处理时最先执行
config.module
.rule('scss')
.oneOf('vue')
.use('vue-style')
.loader('vue-style-loader')
- 通过before指定在某个loader之前,实际执行在其之后
config.module
.rule('scss')
.oneOf('vue')
.use('vue-style')
.loader('vue-style-loader')
.before('css-loader')
- 通过after指定在某个loader之后,实际执行在其之前
config.module
.rule('scss')
.oneOf('vue')
.use('vue-style')
.loader('vue-style-loader')
.after('css-loader')
清除rule配置
config.module.rules.clear() //清除所有
config.module.rules.delete('svg') //清除具体某条规则
config.module.rule('svg').oneOfs.clear() //oneOfs清除所有
config.module.rule('svg').oneOfs.delete('module') //清除某条oneOf
总结
vue-cli
脚手架通过实例化webpack-chain
,给我们提供了颗粒化修改webpack配置的方式,其提供的命令vue inspect
可将所有的配置输出以供检查,以满足我们的个性化需求。不得不说,其极大方便了我们的开发,提升了我们的开发效率。
文章首发原创,转载请注明出处,违者必究。
转载自:https://juejin.cn/post/7270912998927876148