2019独角兽企业重金招聘Python工程师标准>>>
由于关于yeoman编写脚手架的文章太少,所以推出此文,注意本文介绍的yoeman generator-generator是基于 0.8.0以前的版本,0.1.0以后的版本将在下一次文章分享。
原理将在系列二中讲述。
脚手架生成器(scaffold generator)能够帮助我们快速建立起一个项目的基础目录结构和构建任务,使得我们开发项目时能够遵循一定的开发规范,工作流方式,帮助我们提高开发和协作效率。脚手架生成器主要的工作包括:
生成规范的项目目录结构
根据用户输入自动生成具体的项目信息,如项目名称,作者等
开发过程中自动编译
less
、coffee
等任务,自动安装项目基本依赖模块统一环境,开启静态server服务,实时预览项目效果
开启
watch
,当文件发生变化时实时更新到页面预览,如接入browserSync
服务(实现类似livereload的功能)质量保障,单元测试
构建和发布
如上面所述,一个脚手架生成器能帮助我们规范项目,保障质量以及提高开发效率。现在社区也有非常多的工具,例如generator-webapp
,但是在实际的环境下我们很多时候需要根据团队需求和业务现况来自定义我们自己的脚手架。编写一个简单的符合我们自己的脚手架生成器其实非常简单,本文将介绍基于yeoman
来实现。因为yeoman
提供了很多常用的工具函数供我们使用,例如处理用户输入,任务队列,文件操作等,并且社区已经具有非常多的工具提供了很多实用的API,借用它正可以让我们站在巨人的肩膀上实现我们小小的梦想。
下面将具体介绍如何利用yeoman
编写一个简单的脚手架工具。
安装依赖
所有的generator
都是基于yo
来执行的,所以我们得先安装好这个全局依赖
npm install -g yo
基本规范约束
命名规范
所有generator
都必须以generator-
为前缀,例如generator-webapp
,而初始化项目时都是通过yo xxx
来执行的,例如使用generator-webapp
来初始一个通用的web项目时是通过执行yo webapp
。
下面以generator-example
为例,其原理就是在generator-example
模块安装时保证是全局的,也即是在linux
或mac
下模块安装在/url/local/lib/node_modules
下,当执行yo example
的时候它就会去这个全局目录下寻找generator-example
模块的入口来执行。
目录规范
目录规范如下,当执行yo example
时会默认进入执行app/index.js
,如果还需要一个sub-generators
则创建generator-exmaple/test/index.js
,并通过yo name:test
来执行
|-generator-example|-package.json|-app/|--index.js
代码规范
package.json
的keywords
字段必须包含yeoman-generator
,且必须依赖yeoman-generator
开始
按上面的目录规范建好项目目录
基本的
package.json
如下{"name": "generator-example","version": "0.1.0","description": "just a simple example generator","files": [ "app"],"keywords": ["yeoman-generator"],"dependencies": {"yeoman-generator": "^0.17.3","chalk": "^1.0.0","yosay": "^1.0.2"}
}npm install
安装依赖进入
app/index.js
,基本代码如下:var generators = require('yeoman-generator'); module.exports = generators.Base.extend({ //必须extend自method1 : function(){ //任务函数,当generator运行时会自动运行这些函数,这些函数有队列之分,下面会解释console.log('hello world...');}
});开发阶段,将模块暴露在全局目录node_modules下
cd generator-example
npm link//将模块link到`/usr/local/lib/node_modules`下并建立链接至此,一个最简单的generator已经完成
mkdir examplecd example
yo example //将会看到控制台输出 'hello world...'增加功能,让它可以自动生成项目规范的目录结构。往
generator-example
增加一些文件,如下:|-generator-example|-package.json|-app/|--index.js|-templates|- _package.json //项目基本信息|- _gulpfile.js //书写构建任务|- _src //项目源码|- less|- index.less|- js|- index.js
也就是需要让生成器能够帮我们将新项目的结构初始化成
templates
下模板文件的结构,让我们编写一下模板文件,如下:-- _package.json
/*默认用Underscore的模板语法来编写,在生成器中会使用用户的输入来填充这些模板内容*/
{"name": "<%&#61; pkgName %>","version": "1.0.0","description": "<%&#61; description %>","main": "index.js","author": "<%&#61; author %>","repository": {"type": "git","url": "<%&#61; repo %>"},"keywords": ["plugin","def"],"license": "<%&#61; license %>","readmeFilename": "README.md","dependencies": {},"devDependencies": {"gulp": "~3.8.11","gulp-less": "~1.2.0","gulp-sourcemaps": "~1.5.1"}
}-- _gulpfile.js
var gulp &#61; require(&#39;gulp&#39;);
var less &#61; require(&#39;gulp-less&#39;);
var sourcemaps &#61; require(&#39;gulp-sourcemaps&#39;);
gulp.task(&#39;less&#39;, function(){ return gulp.src(&#39;src/less/*.less&#39;).pipe(sourcemaps.init()).pipe(less()).pipe(sourcemaps.write(&#39;./maps&#39;)).pipe(gulp.dest(&#39;build&#39;));
});gulp.watch(&#39;src/less/*.less&#39;,[&#39;less&#39;]);gulp.task(&#39;default&#39;, [&#39;less&#39;]);生成器的生命周期
当执行
yo example
启动生成器时&#xff0c;它会沿着它的生命周期执行如下特定名称的函数&#xff0c;这些特定名称的函数会放进一个队列里面按顺序执行&#xff0c;如果功能函数不是特定的函数名称&#xff0c;如上面的method1
&#xff0c;则放进另一个队列default
按顺序执行。这些特定的函数名称有:
initializing : 初始化阶段
prompting : 接受用户输入阶段
configuring : 保存配置信息和文件&#xff0c;如
.editorconfig
default : 非特定的功能函数名称&#xff0c;如上面说到的
method1
writing : 生成项目目录结构阶段
conflicts : 统一处理冲突&#xff0c;如要生成的文件已经存在是否覆盖等处理
install : 安装依赖阶段&#xff0c;如通过
npm
、bower
end : 生成器即将结束
根据上面生成器的生命周期&#xff0c;我们修改app/index.js
来完成生成器的工作&#xff0c;如下:
--app/index.js
&#39;use strict&#39;;
var generators &#61; require(&#39;yeoman-generator&#39;);
var chalk &#61; require(&#39;chalk&#39;);
var yosay &#61; require(&#39;yosay&#39;);
var path &#61; require(&#39;path&#39;);
module.exports &#61; generators.Base.extend({initializing: function () { //初始化准备工作},prompting: function () { //接受用户输入var done &#61; this.async(); //当处理完用户输入需要进入下一个生命周期阶段时必须调用这个方法//yeoman-generator 模块提供了很多内置的方法供我们调用&#xff0c;如下面的this.log , this.prompt , this.template , this.spawnCommand 等// Have Yeoman greet the user.this.log(yosay(&#39;Welcome to the groundbreaking &#39; &#43; chalk.red(&#39;example&#39;) &#43; &#39; generator!&#39;));this.name &#61; path.basename(process.cwd());this.license &#61; &#39;ISC&#39;;this.description &#61; &#39;&#39;;this.author &#61; &#39;&#39;;var prompts &#61; [{type: &#39;input&#39;,name: &#39;name&#39;,message: &#39;name of app:&#39;, default: this.name},{type: &#39;input&#39;,name: &#39;description&#39;,message: &#39;description:&#39;, default: this.description},{type: &#39;list&#39;, // 提供选择的列表name: &#39;kissy&#39;,message: &#39;which version of kissy&#39;,choices: [{name: &#39;KISSY&#64;1.4.x&#39;,value: &#39;1.4.x&#39;},{name: &#39;KISSY&#64;6.0.x&#39;,value: &#39;6.0.x&#39;}]},{type: &#39;input&#39;,name: &#39;repo&#39;,message: &#39;git repository:&#39;, default: this.repo},{type: &#39;input&#39;,name: &#39;license&#39;,message: &#39;license:&#39;, default: this.license},{type: &#39;input&#39;,name: &#39;author&#39;,message: &#39;author:&#39;, default: this.author}];this.prompt(prompts, function (props) {this.name &#61; props.name;this.pkgName &#61; props.name;this.kissy &#61; props.kissy;this.repo &#61; props.repo;this.license &#61; props.license;this.author &#61; props.author;this.description &#61; props.description;done(); //进入下一个生命周期阶段}.bind(this));},writing: { //生成目录结构阶段app: function () { //默认源目录就是生成器的templates目录&#xff0c;目标目录就是执行&#96;yo example&#96;时所处的目录。调用this.template用Underscore模板语法去填充模板文件this.template(&#39;_package.json&#39;, &#39;package.json&#39;); //this.template(&#39;_gulpfile.js&#39;, &#39;gulpfile.js&#39;);this.copy(&#39;_src/less/index.less&#39;, &#39;src/less/index.less&#39;);this.copy(&#39;_src/js/index.js&#39;, &#39;src/js/index.js&#39;);}},install: function () {var done &#61; this.async();this.spawnCommand(&#39;npm&#39;, [&#39;install&#39;]) //安装项目依赖.on(&#39;exit&#39;, function (code) {if (code) {done(new Error(&#39;code:&#39; &#43; code));} else {done();}}).on(&#39;error&#39;, done);},end: function () {var done &#61; this.async();this.spawnCommand(&#39;gulp&#39;) //生成器退出前运行gulp&#xff0c;开启watch任务.on(&#39;exit&#39;, function (code) {if (code) {done(new Error(&#39;code:&#39; &#43; code));} else {done();}}).on(&#39;error&#39;, done);}
});
至此生成器的工作已经完成了&#xff0c;可以通过yo example
验证使用了
最后
可以看到&#xff0c;自定义一个符合自己需求的生成器其实挺简单的。后面还可以做更多的事情&#xff0c;例如在gulpfile.js里面接入browserSync的功能&#xff0c;单元测试&#xff0c;编译打包任务等等。
yeoman还提供了更多的功能以及函数供我们使用&#xff0c;具体还得看yeoman官网