阅读 2568

认识 webpack-chain、以及 loader、plugin 的简单实现

什么是webpack-chain?

webpack-chain 尝试通过提供可链式或顺流式的 API 创建和修改webpack 配置。API的 Key 部分可以由用户指定的名称引用,这有助于 跨项目修改配置方式 的标准化。 应用一个链式 API 来生成和简化 2-4 版本的webpack的配置的修改。

为什么使用webpack-chain?

webpack 的核心配置的创建和修改基于一个有潜在难于处理的 JavaScript 对象。虽然这对于配置单个项目来说还是 OK 的,但当你尝试跨项目共享这些对象并使其进行后续的修改就会变的混乱不堪。

我们可以用webpack-chain做什么?

搭建可插拔的开发环境跟生产环境,灵活配置项目,完善定制化解决方案。

webpack-chain入门

当你安装了 webpack-chain, 你就可以开始创建一个webpack的配置。下面的代码示例来自于官方示例。基本配置 webpack.config.js

// 导入 webpack-chain 模块,该模块导出了一个用于创建一个webpack配置API的单一构造函数。
const Config = require('webpack-chain');

// 对该单一构造函数创建一个新的配置实例
const config = new Config();

// 用链式API改变配置
// 每个API的调用都会跟踪对存储配置的更改。

config
  // 修改 entry 配置
  .entry('index')
    .add('src/index.js')
    .end()
  // 修改 output 配置
  .output
    .path('dist')
    .filename('[name].bundle.js');

// 创建一个具名规则,以后用来修改规则
config.module
  .rule('lint')
    .test(/\.js$/)
    .pre()
    .include
      .add('src')
      .end()
    // 还可以创建具名use (loaders)
    .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' }]);

// 导出这个修改完成的要被webpack使用的配置对象
module.exports = config.toConfig();
复制代码

共享配置也很简单。仅仅导出配置 和 在传递给webpack之前调用 .toConfig() 方法将配置导出给webpack使用

如果你学习使用过jquery,那么上面的链式调用,相信你能很快上手,但是我们还要了解一下webpack-chain 中的核心API接口。

  1. ChainedMap
  2. ChainedSet

** ChainedMap和ChainedSet的操作类似于JavaScript Map, 为链式和生成配置提供了一些便利**

详细的示例介绍请查看下官方文档,比较详细,webpack-chain,接下来我们看下,如何一步步的搭建我们的生产环境配置。

目录

│── build
│   │── config.js                 // 公共配置
│   │── build.js
│   └── dev.js
│── config
│   │── base.js                 // 基础配置
│   │── css.js                  // css 配置
│   │── HtmlWebpackPlugin.js    // html 配置
│   └── MiniCssExtractPlugin.js // 提取css样式
│── public                      // 公共资源
│   └── index.html              // html 模版
└── src                         // 开发目录
    │
    │── index.css               //测试样式
    └── main.js                // 主入口
复制代码

build/base.js

const { findSync } = require('../lib');
const Config = require('webpack-chain');
const config = new Config();
const files = findSync('config');
const path = require('path');
const resolve = p => {
  return path.join(process.cwd(), p);
};

module.exports = () => {
  const map = new Map();

  files.map(_ => {
    const name = _.split('/')
      .pop()
      .replace('.js', '');
    return map.set(name, require(_)(config, resolve));
  });

  map.forEach(v => v());

  return config;
};
复制代码

构建生产环境

build/build.js

const rimraf = require('rimraf');
const ora = require('ora');
const chalk = require('chalk');
const path = require('path');
rimraf.sync(path.join(process.cwd(), 'dist'));// 删除 dist 目录

const config = require('./config')();
const webpack = require('webpack');
const spinner = ora('开始构建项目...');
spinner.start();

webpack(config.toConfig(), function(err, stats) {
  spinner.stop();
  if (err) throw err;
  process.stdout.write(
    stats.toString({
      colors: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false
    }) + '\n\n'
  );

  if (stats.hasErrors()) {
    console.log(chalk.red('构建失败\n'));
    process.exit(1);
  }

  console.log(chalk.cyan('build完成\n'));
});
复制代码

构建开发环境

build/dev.js

const config = require('./config')();
const webpack = require('webpack');
const chalk = require('chalk');
const WebpackDevServer = require('webpack-dev-server');
const port = 8080;
const publicPath = '/common/';

config.devServer
  .quiet(true)
  .hot(true)
  .https(false)
  .disableHostCheck(true)
  .publicPath(publicPath)
  .clientLogLevel('none');

const compiler = webpack(config.toConfig());
const chainDevServer = compiler.options.devServer;
const server = new WebpackDevServer(
  compiler,
  Object.assign(chainDevServer, {})
);

['SIGINT', 'SIGTERM'].forEach(signal => {
  process.on(signal, () => {
    server.close(() => {
      process.exit(0);
    });
  });
});
server.listen(port);// 监听端口

new Promise(() => {
  compiler.hooks.done.tap('dev', stats => {
    const empty = '    ';
    const common = `App running at:
    - Local: http://127.0.0.1:${port}${publicPath}\n`;
    console.log(chalk.cyan('\n' + empty + common));
  });
});
复制代码

css 提取 loader 配置

config/css.js

module.exports = (config, resolve) => {
  return (lang, test) => {
    const baseRule = config.module.rule(lang).test(test);
    const normalRule = baseRule.oneOf('normal');
    applyLoaders(normalRule);
    function applyLoaders(rule) {
      rule
        .use('extract-css-loader')
        .loader(require('mini-css-extract-plugin').loader)
        .options({
          publicPath: './'
        });
      rule
        .use('css-loader')
        .loader('css-loader')
        .options({});
    }
  };
};
复制代码

css 提取插件 MiniCssExtractPlugin

config/MiniCssExtractPlugin.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = (config, resolve) => {
  return () => {
    config
      .oneOf('normal')
      .plugin('mini-css-extract')
      .use(MiniCssExtractPlugin);
  };
};
复制代码

自动生成 html

config/HtmlWebpackPlugin.js

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

module.exports = (config, resolve) => {
  return () => {
    config.plugin('html').use(HtmlWebpackPlugin, [
      {
        template: 'public/index.html'
      }
    ]);
  };
};
复制代码

以上是基于webpack-chain的基本配置,接下来让我们尝试自己动手写一个基础loader和plugin,动手之前,先回顾一下loader和plugin的基础概念。

  • loader webpack 原生只支持 JS 和 JSON 两种文件类型,通过 Loaders 去支持其它文件类型并且把他们转化成有效的模块。loader 的执行顺序时从右往左,右边的执行结果作为参数传到左边。
  • plugin 插件用于 bundle 文件的优化,资源管理和环境变量注入,任何 loaders 无法处理的事情可以通过 plugins 完成,作用于整个构建过程。

自定义loader

options-chain-loader.js

module.exports = function(content) {
    //正则匹配,content为loader加载的文件内容
  return content.replace(new RegExp(/([\$_\w\.]+\?\.)/,'g'),function(res) {
    let str  = res.replace(/\?\./,'');
    let arrs = str.split('.');
    let strArr = [];
    for(let i = 1; i <= arrs.length; i++) {
      strArr.push(arrs.slice(0,i).join('.')); 
    }
    let compile = strArr.join('&&');
    const done = compile + '&&' + str + '.'
    return  done;
  });
};
复制代码

wepakc-chain配置中使用

module.exports = (config, resolve) => {
  const baseRule = config.module.rule('js').test(/.js|.tsx?$/);
  const normalRule = baseRule.oneOf('normal');
  return () => {
    normalRule
      .use('options-chain')
      .loader(resolve('options-chain-loader'))
  }
}
复制代码

自定义plugins

fileListPlugins.js

class FileListPlugin { 
  apply(compiler) {
    // console.log(compiler)
    //emit 是异步 hook,使用 tapAsync 触及它,还可以使用 tapPromise/tap(同步) 
     compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => { 
       // 在生成文件中,创建一个头部字符串: 
       var filelist = 'In this build:\n\n'; 
       // 遍历所有编译过的资源文件, 
       // 对于每个文件名称,都添加一行内容。 
       for (var filename in compilation.assets) { 
        //  console.log(filename)
         filelist += '- ' + filename + '\n'; 
        }
         // 将这个列表作为一个新的文件资源,插入到 webpack 构建中: 
         compilation.assets['filelist.md'] = { 
           source: function() {
              return filelist; 
            }, 
            size: function() { 
              return filelist.length; 
            } 
          }; 
          callback(null,11); 
        }); 
      } 
    // compiler.plugin('done', function() {
    //   console.log('Hello World!');
    // });
    // }
    // compiler.plugin("compilation", function(compilation) {

    //   // 现在,设置回调来访问 compilation 中的步骤:
    //   compilation.plugin("optimize", function() {
    //     console.log("Assets are being optimized.");
    //   });
    // });
} 
module.exports = FileListPlugin;

复制代码

wepakc-chain配置中使用

const FileListPlugin = require("../fileListPlugins");

module.exports = (config, resolve) => {
  return () => {
    config
      .plugin('file-list-plugin')
      .use(FileListPlugin)
  }
}
复制代码
文章分类
前端
文章标签