利用yeoman构建项目generator
本文作者:ivweb qcyhust 原文出处:IVWEB社区 未经同意,禁止转载
导语
在一个项目的初始化阶段我们一般会做什么呢?如果有一个可参考的项目,是不是会复制这个项目,然后修改成新项目?如果是要在项目中增加一个新页面或是新组件,在开始的时候是不是会复制粘贴先前已存在的页面、组件代码。这些初始化时复制粘贴的操作意味着我们即将着手的项目有大量的结构代码(比如构建脚本,开发脚手架)是存在共性的,在开发过程中,新建一个页面,新开发一个组件,甚至新写一个路由都可能利用一个相同结构的代码来往里面填写新的内容。那么一个能帮助开发者生成自定制结构文件的小工具就会在这中使用场景下派上用场,它能让开发者的工作焦点回到真正的业务逻辑开发上,同时也能为团队开发体统一份统一的代码规范。
简介
yeoman是一个可以帮助开发者快速开启一个新项目的工具集。yoeman提出一个yeoman工作流的概念,通过脚手架工具(yo),构建工具(grunt gulp等)和包管理器(npm bower等)的配合使用让开发者专注于业务的解决上而不是其他小事情。在yeoman的官网中可以搜索到很多用于初始化项目的generator,可以用于快速开启项目。同时yeoman也提供给开发者如何定义自己的generator,所有我们自己开发的generator都作为一个插件可以通过yo工具创建出我们需要的结构。
自己创建的generator可以是很简单的创建几个模板页面,也可以通过和用户交互构建一套量身定制的项目,取决于项目初始化的策略。可以利用yeoman的generator-generator工具来开始构建自己的generator。
从一个简单的例子开始
先从一个简单的模板页面入手,创建简单的generator。假设我们的需要的demo项目目录结构是这样的:
├───index.html |───styles/ | └───style.css ├───scripts/ └───main.js
之前提到,我们的generator是一个插件,所以首先需要创建成一个node module包,在yeoman中这个包的名字应该是generator开头的,那么我们这个generator就叫做generator-demo。每一个包的keyword中必须包含yeoman-generator
。files属性要指向项目的模板目录。
第一步是通过npm init
或是自己手动创建generator的package.json,项目依赖yeoman-generator。也可以利用generator-generator来初始化。
{ "name": "generator-demo", "version": "0.1.0", "description": "", "files": [ "generators" ], "keywords": [ "yeoman-generator" ], "main": "generators/app/index.js", "dependencies": { "yeoman-generator": "^1.0.0" } }
我们的generator项目目录:
├───package.json └───generators/ └───app/ └───index.js └───templates/ ├───index.html ├───styles/ │ └───style.css └───scripts/ └───main.js
第二步就是往template中填充内容,也就是demo项目的三个基本文件的内容。这里简单提供一个例子: template/index.html
<!DOCTYPE html> <html lang="zh_CN"> <head> <title>generator-demo</title> <link rel="stylesheet" href="styles/style.css"> </head> <body> <h1>helle <%= name %></h1> <script src="scripts/main.js"></script> </body> </html>
yeoman采用ejs模板语法,可以在模板文件中传入参数。
template/styles/style.css
* { margin: 0; padding: 0; }
template/sctipts/main.js
'use strict'; window.onload = function() { console.log('generator success'); };
到这一步后就是扩展generator。yeoman提供了一个基础的generator,它有自己的生命周期和事件,功能强大。可以通过扩展这个基础generator来实现我们项目的初始化需求。于是第三步就是编辑app/index.js来扩展它:
const Generator = require('yeoman-generator'); const path = require('path'); const fs = require('fs'); const mkdirp = require('mkdirp'); const utils = require('../utils'); const log = utils.log; module.exports = class extends Generator { constructor(args, opts) { super(args, opts); this.props = { projectName: 'demo', name: 'world' }; } writing() { const { projectName, name } = this.props; const temps = { 'index.html': { name: this.props.name } }; fs.readdir(this.sourceRoot(), (err, items) => { for(let item of items) { if(temps[item]) { this.fs.copyTpl( this.templatePath(item), this.destinationPath(projectName, item), temps[item] ); } else { this.fs.copy( this.templatePath(item), this.destinationPath(projectName, item) ); } } }); } end() { log.info('generator success'); } };
第四步就是运行generator。yoeaman的henerator是一个全局npm module,我们在本地开发的generator可以通过软连接的方式生成它的全局npm包。在generator-demo的根目录下运行npm link
,它会在本地的全局npm目录下安装我们新建的generator。
在确定本地已经安装yo工具(npm install -g yo
)后,在你需要初始化demo项目的地方运行yo demo
,等命令执行完毕,就可以看到新建的项目了。
run loop
在扩展基础generator时,我们可以给实例添加自定义的方法,每一个添加进去的方法都会在generator调用的时候被调用,而且通常来讲,这些方法是按照顺序调用的。除非是已下划线_开头的私有方法,或是定义在实例上的方法。
module.exports = class extends Generator { constructor(args, opts) { super(args, opts); this.task = () => { this.log('instance task'); } } method1() { this.log('method 1'); } method2() { this.log('method 2'); } _task() { this.log('private task'); } }; // 输出: // 'method 1' // 'method 2'
每一个方法在yeoman中都被认为是一个任务,这些任务都会被run loop调用。yeoman的run loop是一个有优先级的队列系统。采用Grouped-queue来维护yeoman的事件队列。除了自定义的方法外,yeoman有很多特殊的事件方法,按照优先级排序:
- initializing - 初始化开始
- prompting - 调用this.prompt()与用户产生交互
- configuring - 创建配置文件(package.json,config.js等)
- default - 方法都不匹配这些优先级时,就会是default优先级(自定义方法会被划入default)
- writing - 创建项目文件
- conflicts - 文件创建中产生冲突的处理
- install - 调用(npm, bower)包install
end - 结束项目初始化 其他自定义方法在configuring和writing按顺序优先级调用。
更复杂的交互
现在我们来给generator增加用户交互和package.json,让它能构建出一个更复杂的项目。还是修改app/index.js,首先增加prompting:
prompting() { return this.prompt([{ type: 'input', name: 'projectName', message: '请输入项目名字', default: 'default-name' }, { type: 'confirm', name: 'package', message: '需要package.json文件', default: true }, { type: 'input', name: 'name', message: '请输入你的名字', default: 'world' }]).then((answers) => { this.log('create project: ', answers.projectName); this.log('by: ', answers.name); this.props = answers; }); }
增加configuring:
configuring() { const { projectName, name } = this.props; let packageSettings = { name: projectName, version: '0.0.1', description: 'YOUR DESCRIPTION - Generated by generator-demo', main: '', scripts: {}, repository: '', keywords: [], author: name, devDependencies: {}, dependencies: {} }; this.fs.writeJSON(this.destinationPath(projectName, 'package.json'), packageSettings); }
package.json可以直接创建也可以利用模板文件创建或是将其中的属性抽象到配置文件中,这样方便修改。
总结
yeoman genenrator的功能远不只本文演示的这些,它还支持异步事件(prompting本身就返回一个promise对象)、install依赖包等等。
一个genenrator也不只是创建一个模板,它同时支持多种模板的需要,比如我们有个复杂的项目,files里面可以添加多个generator,主generator负责初始化项目的时候创建项目的主要文件并安装好各种依赖,在项目的开发中,我们需要增加一个container或是router的话,调用对应的genenrator即可,生成的模板可以将注意力放在内容上,提高开发效率。
腾讯NOW直播前端工程化解决方案feflow正式开源啦~: https://github.com/feflow/feflow 感兴趣的可以star一下哈~