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

相关文章

来自专栏Linux运维学习之路

day4、Linux基础题目

第一题 我想在/data/da 目录下面创建 一个 da.txt 文件  [root@ll ~]# cd /data/oldboyedu -bash: cd: ...

1968
来自专栏一个会写诗的程序员的博客

《Kotin 极简教程》第16章 使用 Kotlin Native第16章 使用 Kotlin Native《Kotlin极简教程》正式上架:

不得不说 JetBrains 是一家务实的公司,各种IDE让人赞不绝口,用起来也是相当溜。同样的,诞生自 JetBrains 的 Kotlin 也是一门务实的编...

633
来自专栏Java技术栈

Git 12 岁了,送给你 12 个 Git 使用技巧!

1926
来自专栏从零开始学自动化测试

appium+python自动化53-adb logcat查看日志

做app测试,遇到异常情况,查看日志是必不可少的,日志如何输出到手机sdcard和电脑的目录呢?这就需要用logcat输出日志了 以下操作是基于windows平...

912
来自专栏deepcc

gulp插件

3195
来自专栏liulun

学习WPF——使用Font-Awesome图标字体

图标字体介绍 在介绍图标字体之前,不得不介绍图标格式ICON ICON是一种图标格式,我们操作系统中各种应用程序都包含一个图标 比如QQ程序的图标是一个可...

2105
来自专栏月色的自留地

Python和C++的混合编程(使用Boost编写Python的扩展包)

  想要享受更轻松愉悦的编程,脚本语言是首选。想要更敏捷高效,c++则高山仰止。所以我一直试图在各种通用或者专用的脚本语言中将c++的优势融入其中。原来贡献...

582
来自专栏前端杂谈

广告等第三方应用嵌入到web页面方案 之 使用js片段

26911
来自专栏张戈的专栏

巧用echo命令解决Samba批量添加用户难题

最近实在太忙,没时间研究和折腾,所以也没有什么可以分享到博客的。果然,个人博客坚持原创太不不容易了。张戈博客上线 2 年多,从 1 天多更,到一天 1 更、一周...

3559
来自专栏技术小黑屋

Android支持动态申请权限么

作为Android开发者,为程序增加权限是在正常不过的事情了,做法必然是在mainifest中,写入类似这样<uses-permission android:n...

743

扫码关注云+社区