利用 yeoman 构建项目 generator

本文作者:ivweb qcyhust

导语

在一个项目的初始化阶段我们一般会做什么呢?如果有一个可参考的项目,是不是会复制这个项目,然后修改成新项目?如果是要在项目中增加一个新页面或是新组件,在开始的时候是不是会复制粘贴先前已存在的页面、组件代码。这些初始化时复制粘贴的操作意味着我们即将着手的项目有大量的结构代码(比如构建脚本,开发脚手架)是存在共性的,在开发过程中,新建一个页面,新开发一个组件,甚至新写一个路由都可能利用一个相同结构的代码来往里面填写新的内容。那么一个能帮助开发者生成自定制结构文件的小工具就会在这中使用场景下派上用场,它能让开发者的工作焦点回到真正的业务逻辑开发上,同时也能为团队开发体统一份统一的代码规范。

简介

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即可,生成的模板可以将注意力放在内容上,提高开发效率。

原文出处:IVWEB社区

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏菩提树下的杨过

ELK日志系统:Filebeat使用及Kibana如何设置登录认证

Filebeat is a lightweight, open source shipper for log file data. As the next-ge...

1091
来自专栏张戈的专栏

Nginx开启fastcgi_cache缓存加速,支持html伪静态页面

张戈博客不久前分享过 Nginx 开启缓存为 WordPress 加速的教程,其中分享了 2 种缓存模式:代理模式和本地模式。我一直以为单个 ngx_cache...

5735
来自专栏张善友的专栏

IIRF(Ionics Isapi Rewrite Filt er)实现在IIS 5/6上重写Url

IIS 7的URL Rewrite功能非常强大,可以通过Microsoft URL Rewrite Module来实现,可参看文章使用Microsoft URL...

1867
来自专栏运维

Nginx1.10.2稳定版本tcp四层负载安装配置过程略解

nginx1.10.2(2016.10.18)是最新稳定版,适合线上运行,最新开发版为1.11.8(2016.12.27)

891
来自专栏服务端技术杂谈

线程池监控

通过扩展线程池进行监控,通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后...

951
来自专栏ytkah

wamp设置本地访问路径为a.com

1759
来自专栏性能与架构

快速认识ELK中的L - Logstash

简介 Logstash 是一个开源的数据采集引擎。 Logstash 就像是一个管子,左面接数据源接收数据,右面接存储目的地,管子中间有过滤器,对接收到的数据进...

3578
来自专栏铭毅天下

干货 | Elasticsearch集群黄色原因的终极探秘

绿色——最健康的状态,代表所有的主分片和副本分片都可用; 黄色——所有的主分片可用,但是部分副本分片不可用; 红色——部分主分片不可用。(此时执行查询部分数...

930
来自专栏linux系统运维

Apache和PHP结合,Apache默认虚拟主机

2106
来自专栏互联网开发者交流社区

PHP配置方法

1492

扫码关注云+社区