写一个基于webpack的cli工具

写一个基于webpack的cli

更好阅读体验请访问:https://zhangzippo.github.io/posts/2019/06/08/_08customecli.html

前言

我们经常接触到各种各样的前端脚手架和cli工具,像vue-cli就是一款基于webpack的cli工具,我们有时候也有针对自己个性化项目快速构建的需求,这时候我们可以自己写一个cli工具。

准备

这里我们介绍基于webpack去写一个cli工具,从前往后讲,我们写一个cli,首先要先定义命令,通过命令触发webpack的构建流程。那么我们基本上需要以下几个工具包:

commander:命令行接口的完整解决方案,可以方便的创建自定义命令行
inquirer:用于创建可交互的命令行
webpack4: 构建的基础
有了以上几个核心工具包之后我们就可以开始编写我们的cli工具了

开始

先看一下我们的工作目录:

├── bin --定义自定义命令
├── src
│ ├── build -- 构建流程
│ ├── develop -- 开发流程
│ ├── produce
│ └── utils
└── webpack --webpack配置文件

配置命令

打开package.json,创建bin字段,为我们的命令起一个名字(例如block),执行文件指向我们的bin目录。

"bin": {
    "block": "./bin/block-cli.js"
  },

这意味着我们的命令是以block开头的,例如block build,block init 等等。然后我们进行block-cli.js的编写,注意这个文件的开头一定要有#!/usr/bin/env node,关于这行的作用大家就自行百度吧。这里我们需要引入commander这个工具包了。这里简单介绍一下commander工具的用法:

#!/usr/bin/env node
const program = require('commander');
/**
 * project init
 */
program
  .command('init')
  .description('project init')
  .action(() => {
    init();
  });
/**
 * create modules and pages
 */
program
  .command('create <paths...>')
  .description('create module‘s or page’s path')
  .action((paths) => {
    createTemplete(paths);
  });
/**
 * build progress
 */
program
  .command('build')
  .option('-m, --module [module]', 'Specified the build module')
  .option('-p, --page [page]', 'Specified the build page')
  .description('build the project')
  .action((options) => {
    process.env.NODE_ENV = 'production';
    const build = require('../lib/build/build');
    build(options.module, options.page);
  });
program.parse(process.argv);

我们主要使用到3个方法,option,command,action, 这里分别介绍一下,option为我们创建命令的相关参数,例如commander会为我们创建一个默认的选项--help,例如我们可以执行block --help,这时控制台会输出我们定义的所有命令以及选项参数的帮助信息,如下:

$ block --help 
Options:
  -V, --version                  output the version number
  -conf, --config <config-name>  output the info of target config
  -h, --help                     output usage information

Commands:
  init                           project init
  create <paths...>              create module‘s or page’s path

如果我们想自定义帮助信息的输出,我们可以这样做(当调用--help时会触发--help事件执行这个回调):

program.on('--help', () => {
  console.log(`\r\nRun ${chalk.greenBright('block <command> --help')} for detailed usage of given command.`);
});

下面来看第一行第一行我们调用command('init'),我们就创建了一个init命令(block init).description为该命令创建一个描述,这样会在帮助信息里面显示出来,action里是我们要为这个命令执行的事情,这里我们执行了一个init方法,该方法内容是创建几个固定的目录这里就不展开了。
我们的第3个命令block build,是命令结合参数的用法,可以看到我们调用了.option方法设定了两个参数,-m和-p全称module和page,我这里的逻辑是构建特定的模块和页面。这段写法在执行的时候这样:
block -m a -p b
action中的options参数中包含这两个option的值,program.parse(process.argv)方法放到命令的最后,这个方法会终结命令的执行。

配置构建流程

这里我们可以把我们预先定义好的webpack配置文件放到我们webpack文件中,这里就不举例子了,大家使用自己的配置文件即可。

执行构建流程

既然是我们自己定义的cli工具,在使用的时候我们就不再使用webpack的cli调用方式了,比如我们正常执行构建的时候会使用命令webpack --config,在我们自己的构建工具中我们应该更优雅的采用webpack的node-api方式执行构建,例子:

import webpack from 'webpack';
import configuration from '../../webpack/webpack.config';

module.exports = () => {
  webpack(configuration, (err, stats) => {
    if (err) {
      console.error(err.stack || err);
      // if (err.details) {
      //   console.error(err.details);
      // }
      return;
    }
    console.log(stats.toString({
      colors: true,
      env: true,
    }));
    const info = stats.toJson();
    if (stats.hasErrors()) {
      console.error(info.errors);
    }

    if (stats.hasWarnings()) {
      console.warn(info.warnings);
    }
  });
};

这里的configuration就是我们预先准备的webpack配置文件,我们调用webpack()方法将配置传入,回调里包含err和stats两个参数负责输出错误信息和我们平时看到的构建流程的信息。
我们有时候也可能会用到webpack-dev-server,它也是提供node-api方式调用的,下面是例子:


import webpack from 'webpack';
import path from 'path';
import webpackDevServer from 'webpack-dev-server';
import merge from 'webpack-merge';
import chalk from 'chalk';
import getCommonConfig from '../utils/getCommonConfig';

module.exports = (modules = '*', pages = '*') => {
  const rootPath = process.cwd();
  const configuration = merge(getCommonConfig('development', modules, pages), {
    mode: 'development',
    devtool: 'source-map',
    plugins: [
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin(),
    ]
  });
  const options = {
    contentBase: path.join(rootPath, 'static'),
    overlay: true,
    open: false,
    hot: true,
    compress: true,
    port: 8081,
    stats: {
      colors: true,
    },
    host: 'localhost',
    publicPath: '/'
  };
  webpackDevServer.addDevServerEntrypoints(configuration, options);
  const compiler = webpack(configuration);
  const server = new webpackDevServer(compiler, options);
  server.listen(8081, 'localhost', () => {
    console.log(chalk.greenBright('block dev-server listening on port 8081'));
  });
};
// module.exports = serverStart;

这边为了支持hotreload我们需要调用 webpackDevServer.addDevServerEntrypoints,另外使用api的时候以往我们写在配置文件中的devserve的配置我们以这个方法的options传入,不需要再在配置文件中定义了。
我们把执行的方法导出,在命令行定义处使用,就完成了我们自定义的cli工具的调用。

最后

以上我们就完成了一个简单的cli工具的基本流程,大家可以根据自己的需要去丰富流程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容