likes
comments
collection
share

如何颗粒化修改vue-cli中的webpack配置?

作者站长头像
站长
· 阅读数 8

引言

在使用vue-cli作为vue项目的脚手架时,由于其已经内置了许多的loaderplugin,免去了我们从头配置webpack的诸多烦恼。但就因为vue-cli内置了这些loaderplugin,当我们想更颗粒化更改这些配置时,不免有些头疼,甚至想直接把vue-cli替换了,又换成原生的webpack(又回到痛苦的原点)?或是直接引入一个新的loader或者plugin。就像这样:

configureWebpack:{
    ...
    plugins:[
        ...
         new CompressPlugin({
          algorithm: 'gzip',
          test: /.(js|css)$/,
          threshold: 10000,
          deleteOriginalAssets: false
        }),
        ...
    ]
}

如果你是这样操作的,那你对vue-cli了解的还是不够,我们的这些颗粒化地需求vue-cli早就考虑到了。接下来就让笔者带着你了解一些vue-cli的高级操作吧。

前置知识

阅读本文,你需要对webpackvue-cli比较熟悉,尤其是webpack。但你只需要了解其中的entryoutputloaderplugin等概念即可,未曾了解请先移步下方的文档。

正文开始

简要介绍

或许你看到过vue.config.js中的配置项chainWebpack,其为一个函数,该函数只有一个参数,为ChainableConfig的实例,将允许我们颗粒化地修改webpack的配置。

如何颗粒化修改vue-cli中的webpack配置?

官方也告诉你,其底层由webpack-chain提供。感谢这个作者,让webpack的配置可以像JQuery那样支持链式调用。

介绍完了chainWebpack,也只是知道要从哪修改我们的默认webpack配置,还得知道默认的webpack配置是什么才行(都不知道vue-cli的默认配置改个鸡儿? )。于是,vue-cli-service inspect闪亮登场了。

如何颗粒化修改vue-cli中的webpack配置?

我们可以通过命令,将默认配置输出到一个文件中,便于观察:

vue inspect > output.js # 开发模式的配置
vue inspect --mode production >output.js #生产模式的配置

如何颗粒化修改vue-cli中的webpack配置?

是不是很熟悉?这就是vue-cli帮我们生成的webpack配置了。

通过vue inspect命令输出的配置,我们将发现形如

/* config.module.rule('vue') */
/* config.plugin('define') */

这样的注释,这告诉我们可以在chainWebpack中,通过config.xxx的形式拿到moduleplugin相关的配置,来进行进一步地颗粒化修改。

例如:

//指定入口文件,不然找的是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,我们可以看到许多有用的方法,支持我们颗粒化地修改我们的输出。

如何颗粒化修改vue-cli中的webpack配置?

颗粒化配置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可能会被实例化多次,数组的每一项就代表着pluginvue-cli实例化了一次。

如何颗粒化修改vue-cli中的webpack配置?

那么,我们如何知道plugin()方法中的nameextract-css,而不是其它,比如plugin('extract-css-plugin')呢?

还记得上文的vue inspect吗?

在我们输出的文件output.js中,用编辑器的查找功能:config.plugin(',我们就能看到所有plugin的配置信息,就可以找到mini-css-extract-plugin这个plugin的配置了(修改之前的默认配置)。

如何颗粒化修改vue-cli中的webpack配置?

上方的注释十分贴心地告诉你,可以通过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的配置中,否则给错了名字,打包时会报错,大概如下:

如何颗粒化修改vue-cli中的webpack配置?

增加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)

如何颗粒化修改vue-cli中的webpack配置?

移除plugin

要移除某个plugin,可以通过此语句移除:

config.plugins.clear()//清除所有的plugins配置
config.plugins.delete('compress')//移除特定的plugin配置

颗粒化配置loader

取到对应的loader

chainWebpack中既然能颗粒化地修改webpack的配置,自然也能修改loader的配置。

首先,我们可以通过vue inspect查看所有的rule:

如何颗粒化修改vue-cli中的webpack配置?

可以看到,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' }
]

这时,细心的你就会发现loaderplugin都是通过一个tap()方法来修改其原有的配置,都需要传入一个带返回值的函数,只不过loader的参数类型是LoaderOptionsplugin的是PluginOptions[]

增添loader

我们开发时根据具体的业务情况可能会需要对某个rule配置增添loader,但请注意,loader配置时一个文件可能有多个loader,且处理顺序从右到左(从下到上)

如何颗粒化修改vue-cli中的webpack配置?

(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')

如何颗粒化修改vue-cli中的webpack配置?

  • 通过before指定在某个loader之前,实际执行在其之后
config.module
      .rule('scss')
      .oneOf('vue')
      .use('vue-style')
      .loader('vue-style-loader')
      .before('css-loader')

如何颗粒化修改vue-cli中的webpack配置?

  • 通过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
评论
请登录