利用 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 条评论
登录 后参与评论

相关文章

来自专栏ionic3+

Cordova插件须知

ionic可以很方便的安装cordova插件,最新ionic-cli要求cordova插件里面有package.json和plugin.xml才能安装成功,如果...

813
来自专栏魏琼东

基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET平台开发指南 - 对象设计器使用帮助

介绍        AgileEAS.NET平台做为一个快速应用开发平台,其目的为是为了提高应用软件的生产效率,如何软件开发的生产效率,方法是多种多样的;使用工...

1795
来自专栏十月梦想

node读取html文件

node和Apache是没有web容器的,node的目录下的同级文件是无法使用/filename进行访问的,因为node没有根目录门也没用web容器!

712
来自专栏编程微刊

基于promise用于浏览器和node.js的http客户端的axios

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:

892
来自专栏极乐技术社区

微信小程序:新功能WXS解读

? 注意(来自官方文档) 1、wxs 不依赖于运行时的基础库版本,可以在所有版本的小程序中运行。 2、wxs 与 javascript 是不同的语言,有自己的...

2656
来自专栏北京马哥教育

Linux老司机带你学Zabbix从入门到精通(万字长文)

zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案,在企业中有非常全面的应用,本文是Linux运维老司机撰写,一站式帮...

59511
来自专栏阿杜的世界

了解Spring Boot的自动配置

Spring Boot的自动配置给开发者带来了很大的便利,当开发人员在pom文件中添加starter依赖后,maven或者gradle会自动下载很多jar包到c...

662
来自专栏云计算教程系列

如何在Ubuntu 14.04上将解析应用程序迁移到解析服务器

Parse是一个移动后端即服务平台,自2013年起由Facebook拥有。2016年1月,Parse宣布其托管服务将于2017年1月28日完全关闭。

220
来自专栏Android相关

Android Profile--Memory Monitor

在Android Studio3.0 Preview版本中,Android Profiler中提供了Memory Monitor工具来监控选中App内存变化,可...

1293
来自专栏还债之路

Nginx

直接去官网(www.nginx.org)查找源码包,直接复制链接地址下载,我们一般选择stable(稳定版),不选择最新版本

35120

扫码关注云+社区