web前端领域技术日新月异,技术栈也不断丰富,在日常工作中涉及到的内容也不断增加,一个前端项目从开发到发布涉及的步骤也很多,很多重复工作内容,因此我们需要开发一些工作来减少这些工作量---工作流。工作流现在也存在很多解决方案,大都是采用GUI方式+自定义脚本方式,相比GUI的方式很多人更爱命令行的的方式,轻量化,可以方便自定义开发,更好适应现有业务的情况。
本文章目的,基于一个命令行模板工具,循序渐进的告诉读者,开发一个命令行工具,会用到哪些现有的轮子,如何让你的工具变得丰满起来。同时我也会简要介绍这些轮子是用来做什么的,以及在实际操作中具体的基本用法。
关于web前端工作流 我计划分为 采用三个篇文章来介绍其他两个主题:
我们在做这件事的时候 主要是基于以下三个痛点:
项目初始化
通常来说一个团队涉及到的项目都会很多,以我们 企鹅辅导 来说,前端项目非常之多, 并且我们团队也有自己的一些开发规范和要求,对于新开发一个项目,同样需要遵循规范和约束,以前我们的做法大都是复制现有项目,然后删减其中我们觉得不需要的内容,然后基于此继续开发。
那么问题来了,复制的项目通常有太多新项目用不上的内容,并且我们很难区分哪些是需要删除的、保留的。 这时候就出现了我们的模板工具,通过开发一个模板工具,通过交互是的命令行初始化项目。 但是如果仅仅做一个命令行工具,就为了初始化项目(低频操作),是很难用起来的。
构建工具
现在的前端项目几乎都有构建工具,涉及到构建,又需要做构建优化,这包括:构建本身速度优化,构建的静态内容优化等等,其次构建本身的升级也需要跟上时代步伐,通常的做法都是每个项目独立维护一份构建配置,这就造成优化无法快速应用到项目中,需要在每个项目重复这些工作,如何让这些能力通用,这也可以纳入工作流工具中。
项目发布
以我们 企鹅辅导 产品来说,发布一个需求大致需要经历如下过程:
显而易见,发布一个需求涉及到的步骤非常多,并且每个不同的步骤都可能是在不同系统中完成,对我们开发者也增加了很多不必要的工作。从实际情况来看,任何一个修改,就算是小到一个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
获取具体模板所在仓库位置。
共需要做三件事情:
当用户没有输入任何模板地址时,你需要提供给默认的选择,交互式选择项目模板
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/ 单页面模板
我们看一下具体模板如何初始化, 这里诺列具体代码了,说一下具体步骤:
单页面应用
,多页面应用
复制文件需要用到一个工具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)
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2018-11-21
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句