随着开发团队不断发展壮大,在人员增长的同时也带来了协做成本的增长;业务项目愈来愈多,类型也各不相同。常见的类型有基础组件、业务组件、基于React的业务项目、基于Vue的业务项目等等。若是想要对每一个项目进行一些规范上的约束好比Git提交规范、Javascript规范简直难于登天。全部的这些,只是由于还欠缺一个好用的工程化工具,在项目建立的初期自动的将这些目录结构和文件生成、而且集成工程常见的规范来进行约束。html
2.1 谈谈目前团队的痛点前端
l 业务部门从git上拉下模板代码后,还要根据具体的业务须要进行配置,修改代码对于前端框架有必定的学习的成本,比较麻烦,对于业务来说也没有必要学习这些框架知识vue
l 修改配置代码的工做基本上是在项目开始就要完成的,是一次性的,后续不用关心有什么变化node
l 对于页面生成,路由生成这些操做,基本上是固定的操做,每次新建js文件,并在旧的文件中添加固定格式的代码,既繁琐又有规律可循,因此考虑是否是能够一个命令就可以添加一个页面文件,并生成路由呢linux
基于上面几点,考虑作一个前端脚手架项目,经过脚手架来复用项目结构,并把对于项目结构的配置工做和复杂枯燥的操做都打包到这个脚手架的功能上,经过命令行就能够完成之前好几步才能完成的步骤,方便业务开发并提高研发效率。git
好了,说干就干,在网上搜索了一下,发现Yeoman就是干这个事情的,并且是谷歌开发的脚手架工具,尝试了一下很是好,下面就记录一下yeoman的设计思路和使用过程。github
咱们须要给每一个工程类型的项目建立一个generator。按照目前前端技术栈的发展状况来看,一个团队通常会有3~5个generator。把这些generator当作一个个的插件,经过工具上层的CLI命令来暴露给开发者使用。npm
在generator之下,须要开发一系列服务和集成规范。包括和Git仓库打通,也就是经过脚手架初始化目录时,先对开发者鉴权。以后根据开发者输入的项目名称在远程Git仓库里面建立仓库而且授予开发者权限。后期功能完善以后,能够作一些锦上添花的工做,好比进行数据统计,分析各个业务仓库使用的generator版本信息,是否集成了最新的feature等等。json
npmjs 帐号,用于publish到npm。数组
Github 帐号,npm中显示你的源码,同时方便Yeoman官网中能搜到你的generator。正如其描述的:
Your generator must have a GitHub repository description, the yeoman-generator keyword and a description in package.json to be listed.
在已经有装好node和npm的前提下,须要全局安装yo和generator-generator
npm install -g yo
复制代码
以后运行generator-generator来建立咱们本身须要的generator的基础框架
npm install -g generator-generator
复制代码
运行generator-generator来建立咱们的脚手架基础框架,运行以下命令:
yo generator
接下来会有一系列的询问问题,其中generator name须要设置为必须以generator-为前缀,由于generator都是普通全局安装的Node.js模块,因此Yeoman彻底依赖于文件系统找到它们。
基础框架安装完成以后的目录结构以下图:
咱们对于脚手架的定制开发工做主要在下面两个文件夹
l generators/app/templates 目录
放置脚手架代码模板的文件夹
l generators/app/index.js 文件
配置用户输入信息,模板迁移和替换规则,安装项目依赖模块;该文件是Generator的子类,重点完成三个方法的定制,分别是prompting,writing,install,下面会重点讲解。
脚手架的工做方式是:先询问用户的配置需求,好比你的项目名字是什么?要使用哪些工具类?而后根据用户的输入,完成项目文件的初始化工做。
咱们先看下用户输入配置的代码,地址:generators/app/index.js
prompting() { let prompts = []; // 若是没有指定appname则提示用户输入 if (!this.options.appname) { prompts.unshift({ type: 'input', name: 'appname', message: "Input your project's name", default: DEFAULT_APPNAME, validate: name => { return !/\s+/.test(name.trim()); } }); }
prompts = prompts.concat(require('./_prompts'));
return this.prompt(prompts).then(res => { // 后续可经过this.props.xxx使用props; let appname = res.appname || this.options.appname; let options = Object.assign({}, res, { appname }); // 模块依赖 this.renderOpts = options; }); }复制代码
prompting
方法主要是来完成和用户交互的,交互的用户输入信息都放在prompts
数组中:
· name 用户输入项的标识,在获取用户输入值的时候会用到
· message 是给用户的提示信息
· type 非必填,默认是text,即让用户输入文本;confirm是选择输入“Yes/No"
· default 非必填,用户输入的默认值
用户输入详细解读请参考inquirer类库,此处再也不展开www.npmjs.com/package/inq…
上面已经提到generators/app/templates目录存储的是文件的模板文件,是生成新的项目结构的原材料;
一个新的项目的文件和结构,是由三部分组成
· 固定文件 直接从template目录copy到项目目录便可
· 加工文件 根据上一步用户的输入,对templates目录下的模板进行二次加工,再copy到用户指定的目录中,以完成项目的初始化
· 可选文件 根据用户的选择,若是须要则copy,不须要则不copy
对于加工文件,在templates文件中可使用EJS模板语法,把模板和用户的输入信息结合起来,就生成了一个新的项目文件,EJS的模板语法以下:
· 赋值 <%= appName%>
· 表达式 <% if(someAnswer){ xxx } %>
项目文件的模板拷贝和用户输入替换工做都在writing方法中实现,示例代码以下:
writing() { const DestFolder = this.options.current ? '' : Path.join(this.renderOpts.appname, '/'); // 添加隐藏文件 .文件名称在linux下会有问题,因此.xxx在template里改成_xxx let targets = [ // 不须要加工的文件 ['mock/**/*', 'mock', false], ['src/**/*', 'src', false], ['README.md', 'README.md', false], ['_editorconfig', '.editorconfig', false], ['_env', '.env', false], ['_eslintrc', '.eslintrc', false], ['_gitignore', '.gitignore', false], ['_prettierignore', '.prettierignore', false], ['_prettierrc', '.prettierrc', false], // 须要加工的文件 ['package.ejs', 'package.json', true], ['umirc.ejs', 'umirc.js', true] ]; // 遍历文件数组,处理并移动文件到目标文件夹下 _.forEach(targets, file => { let fromFile = ''; let toFile = ''; let opts = {}; fromFile = file[0]; toFile = file[1]; opts = file[2] ? this.renderOpts : opts; this.fs.copyTpl( this.templatePath(fromFile), this.destinationPath(DestFolder + toFile), opts, {}, { globOptions: { dot: true } } ); }); }复制代码
须要重点关注四个方法:
· this.templatePath:返回template目录下文件的地址
· this.destinationPath:指定加工完成后文件的存放地址,通常是项目目录
· this.fs.copy:把文件从一个目录复制到另外一个目录,通常是从template目录复制到你所指定的项目目录,用于固定文件和可选文件(根据用户选择)
· this.fs.copyTpl:和上面的函数做用同样,不过会事先通过模板引擎的处理,通常用来根据用户输入处理加工文件
那么怎么加工文件呢?上面咱们已经介绍了EJS模板的赋值和表达式语法,好比咱们上面有一个输入项是appName
,
const prompts = [
{
name: 'appName',
message: 'your appName name?'
}];复制代码
咱们把这个输入项做为html的title,那么咱们就能够在template文件夹里面放入下面的模板文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title><%= appName %></title>
</head>
<body>
<div id="vue"></div>
</body>
</html>复制代码
好了,项目文件加工完成并复制到了指定的目录,接下来要运行咱们的项目,还须要使用npm install
安装项目的依赖,那么可不能够把这个操做也放到脚手架的安装过程当中呢?下面的 install()
方法就能够:
install() {
// 安装npm依赖和bower依赖
//this.installDependencies();
// 只安装bower依赖
//this.bowerInstall();
// 只安装npm组件 this.npmInstall();
}
复制代码
其中有三个方法可使用,既能够安装npm依赖包,也能够安装bower依赖包,具体请参考上面的代码注释。
综上,一个脚手架最基本的功能就完成了,首先先将这个脚手架连接到本地:
npm link // 若是提示权限问题请使用sudo npm link
此时脚手架已经能够本地使用了,在本地建立一个项目目录,进入该目录,尝试使用该脚手架,好比你的脚手架项目名称为generator-name,命令行中应该去掉脚手架项目的前缀“generator-”来运行:
yo name
根据设定的提示和输入信息,Yeoman会一步一步安装你的项目文件,最终生成你指定的项目结构。
若是你但愿本身的脚手架给更多的人提供方便,能够把它发布到npm上。
首先须要一个npm帐号,若是没有可使用npm adduser建立;
若是有则运行npm login登录,而后到工程根目录下,运行npm publish就能够发布了。
好了,关于Yeoman的用法就介绍到这了,关于yeoman生命周期以及高级功能,可参考Yeoman官网。其实Yeoman生成脚手架不仅限于前端,任何语言的脚手架均可以用Yeoman来实现,具体可参考官网。