在16年年底的时候,同事聊起脚手架。由于公司业务的多样性,前端的灵活性,让我们不得不思考更通用的脚手架。而不是伴随着前端技术的发展,不断的把时间花在配置上。于是chef-cli诞生了。 18年年初,把过往一年的东西整理和总结下,重新增强了原有的脚手架project-next-cli, 不单单满足我们团队的需求,也可以满足其他人的需求。

<!--more-->

project-next-cli

面向的目标用户:

  • 公司业务杂,但有一定的积累
  • 爱折腾的同学和团队
  • 借助github大量开发模板开发

发展

前端这几年(13年-15年)处于高速发展,主要表现:

备注:以下发展过程出现,请不要纠结出现顺序 [捂脸]

  • 库/框架:jQuery, backbone, angular,react,vue
  • 模块化:commonjs, AMD(CMD), UMD, es module
  • 任务管理器:npm scripts, grunt, gulp
  • 模块打包工具: r.js, webpack, rollup, browserify
  • css预处理器:Sass, Less, Stylus, Postcss
  • 静态检查器:flow/typescript
  • 测试工具:mocha,jasmine,jest,ava
  • 代码检测工具:eslint,jslint

开发

当我们真实开发中,会遇到各种各样的业务需求(场景),根据需求和场景选用不同的技术栈,由于技术的进步和不同浏览器运行时的限制,不得不配置对应的环境等,导致我们从而满足业务需求。

画了一张图来表示,业务,配置(环境),技术之间的关系

前端配置工程师

于是明见流传了一个新的职业,前端配置工程师 O(∩_∩)O~

社区现状

专一的脚手架

社区中存在着大量的专一型框架,主要针对一个目标任务做定制。比如下列脚手架

  1. vue-cli

vue-cli提供利用vue开发webpack, 以及 远程克隆生成文件等 pwa等模板,本文脚手架参考了vue-cli的实现。

  1. dva-cli

dva-cli 针对dva开发使用的脚手架

  1. think-cli

think-cli 针对 thinkjs项目创建项目

通用脚手架

  1. yeoman

yeoman是一款强壮的且有一系列工具的通用型脚手架,但yeoman发布指定package名称,和用其开发工具。具体可点击这里查看yeoman添加生成器规则

开发初衷和目标

由于公司形态决定了,业务类型多样,前端技术发展迭代,为了跟进社区发展,更好的完成下列目标而诞生。

  • 完成业务:专心,稳定,快速
  • 团队规范:代码规范,测试流程,发布流程
  • 沉淀:专人做专事,持续稳定的迭代更新,跟进时代
  • 效益:少加班,少造轮子,完成kpi,做更有意义的事儿

实现准备

依托于Github,根据Github API来实现,如下:

  1. 获取项目
curl -i https://api.github.com/orgs/project-scaffold/repos
  1. 获取版本
curl -i https://api.github.com/repos/project-scaffold/cli/tags

实现逻辑

根据github api获取到项目列表和版本号之后,根据输入的名称,选择对应的版本下载到本地私有仓库,生成到执行目录下。核心流程图如下:。

总体设计

  1. 规范
  • 使用Node进行脚手架开发,版本选择 >=6.0.0
  • 选用async/await开发,解决异步回调问题
  • 使用babel编译
  • 使用ESLint规范代码
  1. 功能

遵守单一职责原则,每个文件为一个单独模块,解决独立的问题。可以自由组合,从而实现复用。以下是最终的目录结构:

├── LICENSE
├── README.md
├── bin
│   └── project
├── package.json
├── src
│   ├── clear.js
│   ├── config.js
│   ├── helper
│   │   ├── metalAsk.js
│   │   ├── metalsimth.js
│   │   └── render.js
│   ├── index.js
│   ├── init.js
│   ├── install.js
│   ├── list.js
│   ├── project.js
│   ├── search.js
│   ├── uninstall.js
│   ├── update.js
│   └── utils
│       ├── betterRequire.js
│       ├── check.js
│       ├── copy.js
│       ├── defs.js
│       ├── git.js
│       ├── loading.js
│       └── rc.js
└── yarn.lock

配置和主框架

使用babel-preset-env保证版本兼容

{"presets": [["env", {"targets": {"node": "6.0.0"}}]]
}

使用eslint管理代码

  • eslint demo
{"parserOptions": {"ecmaVersion": 7,"sourceType": "module","ecmaFeatures": {"jsx": true}},"extends": "airbnb-base/legacy","rules": {"consistent-return": 1,"prefer-destructuring": 0,"no-mixed-spaces-and-tabs": 0,"no-console": 0,"no-tabs": 0,"one-var":0,"no-unused-vars": 2,"no-multi-spaces": 2,"key-spacing": [2,{"beforeColon": false,"afterColon": true,"align": {"on": "colon"}}],"no-return-await": 0},"env": {"node": true,"es6": true}
}

使用husky检测提交

使用husky, 来定义git-hooks, 规范git代码提交流程,这里只做 commit校验

package.json配置如下:

"husky": {"hooks": {"pre-commit": "npm run lint"}
}

入口

统一配置和入口,分发到不同单一文件,执行输出。核心代码

function registerAction(command, type, typeMap) {command.command(type).description(typeMap[type].desc).alias(typeMap[type].alias).action(async () => {try {if (type === 'help') {help();} else if (type === 'config') {await project('config', ...process.argv.slice(3));} else {await project(type);}} catch (e) {console.log(e);help();}});return command;
}

本地配置读和写

配置用来获取脚手架的基本设置, 如registry, type等基本信息。

  1. 使用
project config set registry koajs # 设置本地仓库下载源project config get registry # 获取本地仓库设置的属性project config delete registry # 删除本地设置的属性
  1. 逻辑
判定本地设置文件存在 ===> 读/写

本地配置文件, 格式是 .ini
若中间每一步 数据为空/文件不存在 则给予提示

  1. 核心代码
switch (action) {case 'get':console.log(await rc(k));console.log('');return true;case 'set':await rc(k, v);return true;case 'remove':await rc(k, v, true);return true;default:console.log(await rc());

下面每个命令的实现逻辑。

下载

  1. 使用
project i
  1. 逻辑
Github API ===> 获取项目列表 ===> 选择一个项目 ===> 获取项目版本号 ===> 选择一个版本号 ===> 下载到本地仓库

获取项目列表

  • https://api.github.com/orgs/p...

获取tag列表

若中间每一步 数据为空/文件不存在 则给予提示

请求代码

  • request
function fetch(api) {return new Promise((resolve, reject) => {request({url    : api,method : 'GET',headers: {'User-Agent': `${ua}`}}, (err, res, body) => {if (err) {reject(err);return;}const data = JSON.parse(body);if (data.message === 'Not Found') {reject(new Error(`${api} is not found`));} else {resolve(data);}});});
}

下载代码

  • download-git-repo
export const download = async (repo) => {const { url, scaffold } = await getGitInfo(repo);return new Promise((resolve, reject) => {downloadGit(url, `${dirs.download}/${scaffold}`, (err) => {if (err) {reject(err);return;}resolve();});});
};
  1. 核心代码
// 获取github项目列表const repos = await repoList();choices = repos.map(({ name }) => name);answers = await inquirer.prompt([{type   : 'list',name   : 'repo',message: 'which repo do you want to install?',choices}]);// 选择的项目const repo = answers.repo;// 项目的版本号劣币爱哦const tags = await tagList(repo);if (tags.length === 0) {version = '';} else {choices = tags.map(({ name }) => name);answers = await inquirer.prompt([{type   : 'list',name   : 'version',message: 'which version do you want to install?',choices}]);version = answers.version;}// 下载await download([repo, version].join('@'));

生成项目

  1. 使用
project init
  1. 逻辑
获取本地仓库列表 ===> 选择一个本地项目 ===> 输入基本信息 ===> 编译生成到临时文件 ===> 复制并重名到目标目录

若中间每一步 数据为空/文件不存在/生成目录已重复 则给予提示

  1. 核心代码
// 获取本地仓库项目const list = await readdir(dirs.download);// 基本信息const answers = await inquirer.prompt([{type   : 'list',name   : 'scaffold',message: 'which scaffold do you want to init?',choices: list}, {type   : 'input',name   : 'dir',message: 'project name',// 必要的验证async validate(input) {const done = this.async();if (input.length === 0) {done('You must input project name');return;}const dir = resolve(process.cwd(), input);if (await exists(dir)) {done('The project name is already existed. Please change another name');}done(null, true);}}]);const metalsmith = await rc('metalsmith');if (metalsmith) {const tmp = `${dirs.tmp}/${answers.scaffold}`;// 复制一份到临时目录,在临时目录编译生成await copy(`${dirs.download}/${answers.scaffold}`, tmp);await metal(answers.scaffold);await copy(`${tmp}/${dirs.metalsmith}`, answers.dir);// 删除临时目录await rmfr(tmp);} else {await copy(`${dirs.download}/${answers.scaffold}`, answers.dir);}

其中模板引擎编译实现核心代码如下:

// metalsmith逻辑
function metal(answers, tmpBuildDir) {return new Promise((resolve, reject) => {metalsmith.metadata(answers).source('./').destination(tmpBuildDir).clean(false).use(render()).build((err) => {if (err) {reject(err);return;}resolve(true);});});
}
// metalsmith render中间件实现
function render() {return function _render(files, metalsmith, next) {const meta = metalsmith.metadata();/* eslint-disable */Object.keys(files).forEach(function(file){const str = files[file].contents.toString();consolidate.swig.render(str, meta, (err, res) => {if (err) {return next(err);}files[file].contents = new Buffer(res);next();});})}
}

升级/降级版本

  1. 使用
project update
  1. 逻辑
获取本地仓库列表 ===> 选择一个本地项目 ===> 获取版本信息列表 ===> 选择一个版本 ===> 覆盖原有的版本文件

若中间每一步 数据为空/文件不存在 则给予提示

  1. 核心代码
  // 获取本地仓库列表const list = await readdir(dirs.download);// 选择一个要升级的项目answers = await inquirer.prompt([{type   : 'list',name   : 'scaffold',message: 'which scaffold do you want to update?',choices: list,async validate(input) {const done = this.async();if (input.length === 0) {done('You must choice one scaffold to update the version. If not update, Ctrl+C');return;}done(null, true);}}]);const repo = answers.scaffold;// 获取该项目的版本信息const tags = await tagList(repo);if (tags.length === 0) {version = '';} else {choices = tags.map(({ name }) => name);answers = await inquirer.prompt([{type   : 'list',name   : 'version',message: 'which version do you want to install?',choices}]);version = answers.version;}// 下载覆盖文件await download([repo, version].join('@'))

搜索

搜索远程的github仓库有哪些项目列表

  1. 使用

project search
  1. 逻辑
获取github项目列表 ===> 输入搜索的内容 ===> 返回匹配的列表

若中间每一步 数据为空 则给予提示

  1. 核心代码
 const answers = await inquirer.prompt([{type   : 'input',name   : 'search',message: 'search repo'}]);if (answers.search) {let list = await searchList();list = list.filter(item => item.name.indexOf(answers.search) > -1).map(({ name }) => name);console.log('');if (list.length === 0) {console.log(`${answers.search} is not found`);}console.log(list.join('\n'));console.log('');}

总结

以上是这款通用脚手架产生的背景,针对用户以及具体实现,该脚手架目前还有一些可以优化的地方:

  1. 不同源,存储不同的文件
  2. 支持离线功能

硬广:如果您觉得project-next-cli好用,欢迎star,也欢迎fork一块维护。

搭建一个通用的脚手架相关推荐

  1. 前端如何搭建一个成熟的脚手架

    前言 有了之前的基础(前端如何搭建一个简单的脚手架),我们现在可以讲讲一个成熟的脚手架是怎么做了. 这里我们参考vue-cli的源码,基于rollup和typescript一步步搭建.vue-cli作 ...

  2. 知识图谱问答系列文档(四)——从零开始搭建一个通用知识图谱问答【问题训练集生成】

    (四)问题训练集生成 问题类型 在构建问题训练集时,首先应当确定通用问答要实现的问答功能,再就各项功能分别生成对应的训练集,本文所构建的问答分为一下五种场景: SP->O(实体的属性值问答,例如 ...

  3. 从0到1搭建一个自己的cli脚手架

    从0到1搭建一个自己的脚手架 源码地址 什么是脚手架 顾名思义,脚手架就是帮助我们配置一些环境.工具,能够让我们方便的直接开始开发,专注于我们的业务,不用花时间去配置开发环境.例如常见的vue-cli ...

  4. 使用webpack4搭建一个基于Vue的组件库

    组内负责的几个项目都有一些一样的公共组件,所以就着手搭建了个公共组件开发脚手架,第一次开发 library,所以是参考着 iview 的配置来搭建的.记录如何使用webpack4搭建一个library ...

  5. 从0开始搭建一个战棋游戏的AI(初级教程)

    战棋类游戏一直以高策略性著称,其中不乏经典之作如"三国志英杰传"."三国曹操传"."炎龙骑士团"."金庸群侠传"等等. ...

  6. 利用脚手架工具搭建一个新的react项目

    利用脚手架工具搭建一个新的react项目 一,工程架构 1.使用的是create-react-app脚手架工具搭建的工程架构 npm install create-react-app -g全局安装 c ...

  7. 根据vue的脚手架 简单的搭建一个单页面

    根据vue的脚手架 简单的搭建一个单页面 1.首先需要安装npm node.js 自带,所以去安装node.js 就可以了. node中文网址 这里是网址 或者自行百度,网上很好找.另外还需要手动安装 ...

  8. 使用vue3 +vite + typeScript + elementPlus搭建一个项目脚手架

    使用vue3 + vite + elementPlus搭建一个项目脚手架 这篇文章就教大家如何使用vue3+vite+ts+element-plus搭建一个项目,步骤详细,献给不爱看文档的诸位,希望这 ...

  9. SSM整合(搭建一个Web脚手架)

    文章目录 引入Maven依赖 Mybatis配置 Spring SpringMVC 集成三个配置文件 配置WEB Controller例子 SSM基础在这里了 MyBatis笔记(从零到一) Spri ...

最新文章

  1. NAT类型及检测方法
  2. python入门常识
  3. 如何进行聚类可视化_如何使用matplotlib包进行数据可视化
  4. Arduino笔记-解决上传时出现avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00问题
  5. 4.2.#{}和${}的用法
  6. 计数排序(Counting sort)
  7. 按照这个步骤来刷题,迷茫的你两个月亦能成为王者
  8. android listview动态添加viewpager,请教大神,android如何在viewpager里添加listview,谢谢啦...
  9. 网络高可用性99 999 9999 99999
  10. 闭包与setTimeout
  11. L298N电机驱动模块详解
  12. Mac如何取消远程控制?
  13. 示波器同步的调节方法
  14. ubuntu记录pdf手写笔记: 数位板(硬件)+xournal(软件)
  15. ps----高低频磨皮--------中性灰磨皮-----------双曲线磨皮
  16. Finalize、dispose、dispose(bool disposing)
  17. vue项目引用美图秀秀图片编辑器
  18. 【nvidia】1.命令行方式安装nvidia显卡驱动
  19. 单调栈-leetcode-739. 每日温度
  20. 云服务器被攻击了怎么解决?恢复需要多久?

热门文章

  1. c语言cnn实现ocr字符,端到端的OCR:基于CNN的实现
  2. mysql2005错误_SQL Server 2005 还原数据库错误解决方法
  3. qt on android 桌面鼠标事件,關於Qt on Android,程序安裝到手機,界面只占到一小部分。...
  4. 武汉大学计算机学院毕业合影,武大校长对毕业合影有求必应
  5. 实体类dao接口mysql_利用MyBatis生成器自动生成实体类、DAO接口和Mapping映射文件...
  6. linux sendto 对方关闭后性能,Gateway的请问sendToUid是否有性能问题?
  7. 【AI初识境】给深度学习新手开始项目时的10条建议
  8. 全球及中国二叔丁基氢醌行业容量规模与供求趋势分析报告2022版
  9. WeakMap 本身释放,而 keyObject 没有释放的情况下,value 会释放吗?
  10. DIV盒子模型介绍 div用法