FrankKai / FrankKai.github.io

FE blog
https://frankkai.github.io/
363 stars 39 forks source link

实用webpack插件之webpack-chain #207

Open FrankKai opened 4 years ago

FrankKai commented 4 years ago

image

通过阅读这篇文章,可以学习到如何使用webpack-chain插件使得前端项目更加工程化。vue-cli3.0的vue.config.js采用webpack-chain插件的方式进行链式配置,所以这个插件非常值得一学。

命令式编程->声明式编程(函数式编程)

使用前的声明式编程:

const path = require('path');
const config = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.(js)$/,
        use: 'babel-loader'
      }
    ]
  },
};
module.exports = config;

使用后的声明式编程(函数式编程):

const Config = require('webpack-chain');
const config = new Config();
config.
    .entry('index')
        .add('src/index.js')
        .end()
    .output
         .path('dist')
         filename('my-first-webpack.bundle.js');
config.module
    .rule('compile')
        .test(/\.js$/)
        .use('babel')
             .loader('babel-loader')
module.exports = config;

通过对比我们发现webpack-chain有以下优点:

vue-cli3.0和webpack4.0时代,webpack-chain插件是必会插件,因为在vue.config.js中,configureWebpack和chainWebpack中的cb中注入的config对象,其实都是一个webpack-chain实例,如果想对webpack的一些插件的配置做修改,那么就必须先理解webpack-chain。

为什么要使用webpack-chain插件?

webpack-plugin使用示例

webpack.core.js

const Config = require('webpack-chain');
const config = new Config();
// 入口出口文件配置
config.
    .entry('index')
        .add('src/index.js')
        .end()
    .output
         .path('dist')
         filename('[name].bundle.js');

// 创建之后可以修改的命名规则
config.module
    .rule('lint')
        .test(/\.js$/)
        .pre()
        .include
            .add('src')
            end()
         .use('eslint')
             .loader('eslint-loader')
             options({
                 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 }]
                  ]
              ]);
config
    .plugin('clean')
        .use(cleanPlugin, [['dist'], { root: '/dir' }]);
module.exports = config;

webpack.dev.js

const config = require('./webpack.core');
// ...
module.exports = config.toConfig();

webpack.prod.js

const config = require('./webpack.core');
// ...
module.exports = config.toConfig();

一些常用的webpack-chain 缩写方法

ChainedMap的有些key,可以直接作为方法调用,这些缩写方法也同样会返回原始实例,方便后续的链式调用。

devServer.hot(true);
devServer.set('hot', true);

引入webpack-chain后如何配置plugin?

新增插件
config
    .plugin(name)
    .use(WebpackPlugin, args)
// 直接引入
config
  .plugin('hot')
  .use(webpack.HotModuleReplacementPlugin);
// 可以通过requrire('')的方式引入插件。
config
  .plugin('env')
  .use(require.resolve('webpack/lib/EnvironmentPlugin'), [{ 'VAR': false }]);
修改参数
config
    .plugin(name)
    .tap(args => newArgs)
// 为arguments新增一个'SECRET_KEY'
config
    .plugin('env')
    .tap(args => [...args, 'SECRET_KEY'])
修改实例
config
    .plugin(name)
    .init((Plugin, args) => new Plugin(...args));
删除插件
config.plugins.delete(name)
某个插件前调用插件odering before

不能在同一个插件上既使用before又使用after。

config
    .plugin(name)
      .before(otherName)
// 例子
config
    .plugin('html-template')
        .use(HtmlWebpackTemplate)
        .end()
    .plugin('script-ext')
        .use(ScriptExtWebpackPlugin)
        before('html-template')
某个插件后调用插件ordering after

不能在同一个插件上既使用before又使用after。

config
    .plugin(name)
      .after(otherName)
// 例子
config
    .plugin('html-template')
        .use(HtmlWebpackTemplate)
       .after('script-ext')
        .end()
    .plugin('script-ext')
        .use(ScriptExtWebpackPlugin)

vue-cli3.0引入webpack-chain后,如何配置最常用的DefinePlugin呢?

vue.config.js

const apiConfig = require('./config/api');

module.exports = {
    chainWebpack: config => {
        config
            .plugin('define')
            .tap(args => { 
                args[0].API_CONFIG = JSON.stringify(apiConfig)
                return args
            })
    }
}

需要注意的是,在vue-cli3.0中,我们不能直接SET NODE_ENV=production或者EXPORT NODE_ENV=production。 因为vue-cli-servive有3种模式,serve默认为development,build为production,若想修改vue-cli-service包中的NODE_ENV,需要通过vue-cli-service serve --mode production进行切换。 就像下面这样:

{
  "scripts": {
    "dev": "vue-cli-service serve", // mode默认为development 
    "production": "vue-cli-service serve --mode production", 
  },
}

注意:我们只能在development, production或者test 3个模式下进行切换,不能引入类似preproduction之类的自定义node环境,但是实际上这3个环境已经足以满足大多数的开发情况。

为什么vue-cli 3.0中的DefinePlugin可以用config.plugin('define')修改入参?

源码文件base.js中,有下面的代码:

    webpackConfig
      .plugin('define')
        .use(require('webpack/lib/DefinePlugin'), [
          resolveClientEnv(options)
        ])

这一点很关键!我们在vue.config.js中拿到的config.plugin('define'),实际上时vue-service内部创建的webpack.DefinePlugin实例的引用 ! 明确了这一点,我们在以后增强webpack默认插件配置时,需要先到vue-service的源码中寻找一番,看看有没有对应plugin的引用,若有,必须根据vue-service定义的名字直接引用,否则会修改失败。