【干货】打造自己的web前端工作流(一)--- 交互的命令行工具模板篇

前言

web前端领域技术日新月异,技术栈也不断丰富,在日常工作中涉及到的内容也不断增加,一个前端项目从开发到发布涉及的步骤也很多,很多重复工作内容,因此我们需要开发一些工作来减少这些工作量---工作流。工作流现在也存在很多解决方案,大都是采用GUI方式+自定义脚本方式,相比GUI的方式很多人更爱命令行的的方式,轻量化,可以方便自定义开发,更好适应现有业务的情况。

本文章目的,基于一个命令行模板工具,循序渐进的告诉读者,开发一个命令行工具,会用到哪些现有的轮子,如何让你的工具变得丰满起来。同时我也会简要介绍这些轮子是用来做什么的,以及在实际操作中具体的基本用法。

关于web前端工作流 我计划分为 采用三个篇文章来介绍其他两个主题:

  1. 构建篇
  2. 发布篇

为何要做一个工作流工具?

我们在做这件事的时候 主要是基于以下三个痛点:

项目初始化

通常来说一个团队涉及到的项目都会很多,以我们 企鹅辅导 来说,前端项目非常之多, 并且我们团队也有自己的一些开发规范和要求,对于新开发一个项目,同样需要遵循规范和约束,以前我们的做法大都是复制现有项目,然后删减其中我们觉得不需要的内容,然后基于此继续开发。

那么问题来了,复制的项目通常有太多新项目用不上的内容,并且我们很难区分哪些是需要删除的、保留的。 这时候就出现了我们的模板工具,通过开发一个模板工具,通过交互是的命令行初始化项目。 但是如果仅仅做一个命令行工具,就为了初始化项目(低频操作),是很难用起来的。

构建工具

现在的前端项目几乎都有构建工具,涉及到构建,又需要做构建优化,这包括:构建本身速度优化,构建的静态内容优化等等,其次构建本身的升级也需要跟上时代步伐,通常的做法都是每个项目独立维护一份构建配置,这就造成优化无法快速应用到项目中,需要在每个项目重复这些工作,如何让这些能力通用,这也可以纳入工作流工具中。

项目发布

以我们 企鹅辅导 产品来说,发布一个需求大致需要经历如下过程:

  1. 构建系统构建
  2. 测试环境部署
  3. 创建merge request
  4. 申请发布单
  5. 发布群发布知会
  6. 预发布环境部署与验证
  7. 正式发布与验证(发布静态资源、nodejs服务、APP离线包)
  8. 代码合并主干、关闭发布单
  9. 完成发布

显而易见,发布一个需求涉及到的步骤非常多,并且每个不同的步骤都可能是在不同系统中完成,对我们开发者也增加了很多不必要的工作。从实际情况来看,任何一个修改,就算是小到一个wording的修改,都需要经历这些步骤,这就造成任何一个发布没有数小时,基本没法完成。

基于这些原因,才有了团队工具流开发的必要性,将一个项目从初始化到发布上线的所有过程都集中到工具中,简化工作内容,保证发布流程。对于不同的团队情况不同,部分内容也不一定适用,具体是否有必要依赖具体业务情况。

命令行工具

接下来我将演示,如何看法自己的模板项目命令行工具

创建命令行项目,基本目录如下

imt/
├── bin/
│   └── cli.js
├── LICENSE
├── package.json
├── README.md
├── src/
│   ├── commands/
│   │   ├── create.js
│   ├── const.js
│   ├── index.js
│   ├── plugins/
│   │   └── index.js
│   ├── Service.js
│   ├── utils/
│   │   ├── index.js
│   │   ├── logger.js
│   │   ├── spinner.js

处理命令行参数,解析参数并执行不同操作, 下面这个脚本就是一个nodejs脚本,其中第一行是为了告诉bash 使用 node 执行脚本

./bin/cli.js

#!/usr/bin/env nodeconst program = require('commander'); // 一个命令行参数解析工具const pkg = require('../package.json'); // 这里是为了自动设置代理,有的仓库在外网,需要设置代理才能够下载program  .version(pkg.version)
  .option('-P, --proxy <proxy>', '设置代理')
  .option('-x, --defaultProxy', '使用内网代理', false);// 定义一个action,匹配后会执行指定的函数program  .command('create [template] [dir]')
  .description('初始化项目')
  .action((template, dir, options) => {
    require('../src/commands/create')(template, dir, options);
  });
  program.parse(process.argv); // 解析命令行参数if (!process.argv.slice(2).length) { // 没有参数直接输出帮助信息
  program.outputHelp();} else if (program.proxy) { // 将proxy设置到环境变量
  process.env.proxy = program.proxy;}

配置可执行命令 让我们工具连接到全局并且可直接执行, 配置package.json

 "bin": {
    "imt": "./bin/cli.js"
  },

执行 npm link ,将命令软连接到全局命令搜索目录下, 执行完毕后,直接在terminal中输入imt 然后回车键,会看到如下信息帮助信息:

Usage: imt [options] [command]

Options:
  -V, --version             output the version number
  -P, --proxy <proxy>       设置代理
  -x, --defaultProxy        使用内网代理
  -h, --help                output usage information

Commands:
  create [template] [dir]   初始化项目

模板代码下载和初始化

现在你的的命令行工具已经安装成功,接下来就是根据命令行输入的参数,执行函数。我们看如何实现模板功能,模板通常我们不止一种,因此具体模板我们不会放在这个工具中,是通过imt create gitName/projectName 中的参数gitName/projectName获取具体模板所在仓库位置。

共需要做三件事情:

  1. 提示用户选择默认模板
  2. 下载模板仓库
  3. 初始化模板仓库并执行命令。

当用户没有输入任何模板地址时,你需要提供给默认的选择,交互式选择项目模板

const inquirer = require('inquirer');// 内置模板const defaultTemplates = [
    { 
     name: 'React应用', 
     value: 'hxfdarling/imt-react-template' 
    },
    { 
     name: '微信小程序', 
     value: 'hxfdarling/imt-mp-template' 
    }];// ... 省略其他代码
  async getTemplate() {
    let { template } = this;
    if (!template) {
      ({ template } = await inquirer.prompt([
        {
          name: 'template',
          message: '选择内置模板',
          type: 'list',
          choices: defaultTemplates,
        },
      ]));
    }
    if (/\.zip$/.test(template)) {
      template = `direct:${template}`;
    }
    this.template = template;
  }
 // ... 省略其他代码 

我们通过inquirer模块 快速的生成交互式的选择界面,该工具支持多种不通过的交互式输入方式。这里使用到了他的list列表选择能力,具体效果如下:

得到目标模板地址后,需要下载模板的仓库代码:

const download = require('download-git-repo');const chalk = require('chalk');const ora = require('ora');//... 省去部分代码
  downloadTemplate() {
    const { template } = this; 
    const spinner = ora('模板下载中').start();
    return new Promise(resolve => {
      download(template, this.templateDir, {}, error => {
        if (error) {
          console.log(chalk.red(`下载模板失败:${template},请确认网络是否正常`));
          console.error(error);
          process.exit(1);
        } else {
          spinner.stop();
          console.log(chalk.green('模板下载成功'));
          resolve();
        }
      });
    });
  }

这里下载仓库代码,我使用了download-git-repo快速实现地址解析和下载,下载过程我们需要美化一下,通过ora工具支持添加一个loading图标,表示正在处理。具体效果如下:

接下来就是初始化我们的模板项目并执行模板项目中的代码,以初始化项目,具体代码如下:

//... 省略其他代码
  await this.confirmDir(); // 提示用户当前目录不为空(如果不为空)
  await this.getTemplate();// 获取仓库地址
  await this.downloadTemplate(); // 下载模板
  console.log(chalk.yellow('初始化模板'));
  shell.cd(templateDir); // 切换命令行执行目录到模板目录
 // 由于内网,检测用户是否安装了tnpm,安装了就使用tnpm命令
  let npmCmd = 'npm';
  if (!shell.exec('tnpm -v', { silent: true }).stderr) {
    npmCmd = 'tnpm';
  }
  // 执行模板项目的node_modules初始化
  shell.exec(`${npmCmd} i`, { silent: true });
  const main = require(`${templateDir}/package.json`).main || 'index.js';
  // 运行模板项目的代码
  spawnSync('node', [`.template/${main}`], {
    cwd: this.projectDir,
    // 由于是在child_process中执行,可能有交互命令,需要继承父进程的标准输入,否则子进程无法获取到键盘输入
    stdio: 'inherit',
  });
  // 清理模板项目目录
  fs.emptyDirSync(templateDir);
  fs.removeSync(templateDir);//... 省略其他代码

上面这段代码,也用到了一个高度封装的轮子shelljs。

至此我们一个完整的命令行工具搞定

具体如何初始化模板代码,我们继续看模板项目中的实现:

模板项目

通常来说项目的大多数技术栈都统一,但是具体到不同业务中,实际用到的框架、库可能也不尽相同。因此模板项目并非一个简单的复制仓库代码搞定,我们需要让模板在初始化时,可以选择功能。

这里以React应用模板为例,具体代码地址imt-react-template,这个模板代码支持初始化多页面应用和单页面引用,是否使用rem,是否初始化index.html内容等可选项。

项目目录

imt-react-template/
├── bin/
│   └── cli.js
├── index.js // 模板执行脚本
├── package.json
├── README.md
└── templates/
    ├── common/ 两种不同模板共享文件
    ├── multi/ 多页面模板
    └── single/ 单页面模板

我们看一下具体模板如何初始化, 这里诺列具体代码了,说一下具体步骤:

  1. 弹出选择框选择初始化类型:单页面应用,多页面应用
  2. 是否初始化REM代码,用于H5开发
  3. 单页面应用是否初始化webapp
  4. 等等,其他选项
  5. 接下来就是复制文件

复制文件需要用到一个工具yeoman-generator,该工具,提供模板变量和逻辑的能力,在复制文件时,通过传入对象,中的所有属性可以直接在模板代码的全局内访问,具体使用如下:

复制模板,第三个参数是模板执行的上下文对象

this.fs.copyTpl(
    this.templatePath('./common/.imtrc.js'),
    this.destinationPath('.imtrc.js'), 
    {
        app:"single",
        webapp:true
    });

.imtrc.js文件模板代码如下:

module.exports = {
  // 应用类型
  mode:"<%= app %>",
  <% if (webapp) { %>
  // 支持webapp插件
  webappConfig:{
  },
  <% } %>
};

这里通过<%= app %> 实现访问this.props中的属性,直接打印到当前位置,通过<% if (webapp) { %> xxx <% } %>实现条件判断,是否需要输出到最终文件,其他更多的语法参见 yeoman文档

最终效果我们看一张动图看完整效果

当然这只是一个初级的模板初始化工具,我们还可以再丰富它,例如:是否使用某些库、支持添加空页面模板等。

工具用到的模块介绍

介绍一些用于开发命令行工具会用到的工具,下面这些工具都可直接在github中搜索,都是开源项目。

工具名称

介绍

lint-staged

可以用于实现提交前代码格式化,eslint等处理

husky

git钩子,例如提交前的一些脚本处理,提交消息检测等

commitlint

用于git仓库提交的message规范检测,统一团队项目提交规范,这里有一个简单的库,能够快速接入这个能力到项目中commitlint-config-imt

chalk

命令行颜色工具,命令行工具输入日志时,带有颜色

commander

命令行工具,必备工具,简化参数解析和帮助信息输出

inquirer

交互式命令行工具,让你可以再命令行中实现可交互输入

semver

版本工具,可以用于提示用户你的命令行支持版本的nodejs

yeoman-generator

快速项目初始化的模板工具,功能相当强大,具体能力参考官方文档

debug

很好用的日志工具,可以给不同日志设置标题,能够快速调整日志打印策略

shelljs

shell执行工具,非常方便的在js代码中执行shell命令,甚至直接在js代码中使用shell命令!

tapable

webpack插件基础库,提供了多种插件执行的模式,并行、异步、同步等等,如果需要让你的工具支持插件机制,使用这个库将这个事儿变得非常方便

ora

简单来说,提供命令行中loading图标,和步骤标示能力,具体效果看git仓库

当然,这些工具只是冰山一角。

--------------------------------------------------------------------------

原文作者:腾讯高级工程师刘华

  来源:腾讯内部KM论坛

你也想成为腾讯工程师?

也想年终奖人手一部 Iphone X?

那就快加入NEXT学院吧!

NEXT学院课程「Web前端工程师NEXT学位完整课程」火热招生中!

  感兴趣的同学赶紧点击原文了解详情吧~

原文发布于微信公众号 - 腾讯NEXT学院(Next_Academy)

原文发表时间:2018-11-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏问天丶天问

K8S 基础名词概念

28040
来自专栏IT笔记

分享一款JVM线程堆栈在线分析工具

JVM大家可能都知道是个什么玩意-Java虚拟机,但是到底是个什么鬼?相信即使工作3-5年的程序员可能也不大了解。 ? 如题所述,今天与大家分享的是如何分析...

3.1K60
来自专栏锦小年的博客

pycharm使用笔记2-远程连接

随着科技的发展,远程办公已经是一种趋势,远程开发能力对于每一个程序员来说都是必不可少的。有时候就算在公司,在进行开发的时候有许多的数据都是储存在服务器上的,所以...

411100
来自专栏散尽浮华

nginx+php负载均衡集群环境中的session共享方案梳理

在网站使用nginx+php做负载均衡情况下,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,就会出现很多问题,比如说最常见的登...

84770
来自专栏云计算教程系列

如何在Ubuntu 14.04上配置Apache以使用自定义错误页面

Apache是世界上最受欢迎的Web服务器。它功能强大,功能丰富且灵活。在设计网页时,有助于自定义那些客户将看的所有内容,当然这些内容也包括他们请求不可用内容时...

11700
来自专栏北京马哥教育

10个方法助你轻松完成Linux系统恢复

在Linux中有一些应用程序可以帮助你保存系统快照。大多数应用程序都是针对于新手的,并不需要高级的Linux操作技巧。我们在这里挑选了10个,你可以从中选择适合...

55350
来自专栏idba

主从替换之后的复制风暴

一套MySQL主-备-备-备数据库,其中的备库升级到主库之后,系统监控报警 Seconds_Behind_Master 瞬间为0,瞬间为数十万秒。第一感觉是遇...

20220
来自专栏企鹅号快讯

g4e基础篇#4 了解Git存储库

Git 存储库看上去就是一个文件夹,只是在这个文件夹中不仅仅保存了所有文件的当前版本,也同时保存了所有的历史记录,这些额外的信息都保存在当前文件夹下面的.git...

24360
来自专栏数据和云

运维经验:回滚段异常的特殊救急方法

? 冷菠 冷菠,资深DBA,著有《Oracle高性能自动化运维》,有近10年的数据库运维、团队管理以及培训经验。擅长数据库备份恢复、数据库性能诊断优化以及数据...

45090
来自专栏大数据人工智能

小白请点进来!零基础搭建Hadoop大数据处理环境

由于hadoop需要运行在Linux环境中,而且是分布式的,因此个人学习只能装虚拟机,本文都以VMware Workstation为准,安装CentOS7,具体...

521100

扫码关注云+社区

领取腾讯云代金券