yeoman构建项目generator(yeoman-generator)

前言:在开始一个新项目得时候,如果我们已经有存在得项目,大多数都会借鉴这个项目,包括他的基础配置,以及一些公共的组件。而往往采用的方式都是复制粘贴,如果项目比较复杂,这样做有几个很明显的弊端,一是比较耗时,二是容易出错。即使我们重新构建一个新的脚手架,也需要根据项目的需求做不少的添加更改等等,其实经过简单的分析,在我们的许多项目当中都有很多共性的东西,哪我们有没有一个方法,在每次需开发新项目的时候,迅速生成这些基础的配置,就像我们下载一个npm包一样简单。答案当然是可以的,接下来我们我们就介绍这样一个脚手架yeoman-generator。

简介

yeoman是一个可以帮助开发者快速开启一个新项目的工具集。yoeman提出一个yeoman工作流的概念,通过脚手架工具(yo),构建工具(grunt gulp等)和包管理器(npm bower等)的配合使用让开发者专注于业务的逻辑。在yeoman的官网中可以搜索到用于初始化项目的generator,可以用于快速开启项目。同时yeoman也提供给开发者如何定义自己的generator,所有我们自己开发的generator都作为一个插件可以通过yo工具创建出我们需要的结构。

自己创建的generator可以是很简单的创建几个模板页面,也可以通过和用户交互构建一套量身定制的项目,取决于项目初始化的策略。可以利用yeoman的generator-generator工具来开始构建自己的generator。

一.准备:

  • npmjs 账号,用于publish到npm。
  • Github 账号,npm中显示你的源码(也是发布npm包所需要),同时方便Yeoman官网中能搜到你的generator。

下载安装yo和generator-generator

注:在已经有装好node和npm的前提下,需要全局安装yogenerator-generator

npm install -g yo
npm install -g generator-generator

之后运行generator-generator来创建我们自己需要的generator的基础框架

yo generator

在一系列设置问题之后

最终得到的generator的目录:

.
├── generators/
│   └── app/
│       ├── index.js
│       └── templates/
│           └── dummyfile.txt
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .eslintrc
├── .travis.yml
├── .yo-rc.json
├── package.json
├── gulpfile.js
├── README.md
├── LICENSE
└── test/
    └── app.js

二.从一个简单的例子开始

我们的generator是一个插件,所以首先需要创建成一个node module包,在yeoman中这个包的名字必须是generator开头的,那么我们这个generator就叫做generator-xxx。每一个package.json的keyword中必须包含yeoman-generator。files属性要指向项目的模板目录。

三.通过npm init或是自己手动创建generator的package.json,项目依赖yeoman-generator。建议用yo  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"
    }
}

四.往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的generator是一个全局npm module,我们在本地开发的generator可以通过软连接的方式在本地生成它的全局npm包。在工程的根目录下运行npm link,它会在本地的全局npm目录下安装我们新建的generator。

在确定本地已经安装yo工具(npm install -g yo)后,在你需要初始化项目的地方运行yo demo,等命令执行完毕,就可以看到新建的项目了。

 

七.在扩展基础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有很多特殊的事件方法,按照优先级排序:

  1. initializing - 初始化开始
  2. prompting - 调用this.prompt()与用户产生交互
  3. configuring - 创建配置文件(package.json,config.js等)
  4. default - 方法都不匹配这些优先级时,就会是default优先级(自定义方法会被划入default)
  5. writing - 创建项目文件
  6. conflicts - 文件创建中产生冲突的处理
  7. install - 调用(npm, bower)包install
  8. 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);
}

九. 重写constructor方法

有些generator方法只有定义在构造方法内才能被调用到.这些特殊的方法可以做的一些重要的操作等,而这些操作可能在构造之外无法正常运行。

module.exports = generators.Base.extend({
      // The name `constructor` is important here
      constructor: function () {
        // Calling the super constructor is important so our generator is correctly set up
        generators.Base.apply(this, arguments);

        // Next, add your custom code
        this.option('coffee'); // This method adds support for a `--coffee` flag
      }
    });

 

十.找到工程根目录

当使用yo命令来运行generator的生活,yeoman会把 .yo-rc.json文件所在的目录作为工程的根目录,之后Yeoman将当前文件目录跳转到根目录下运行请求的生成器。当我们使用this.config.save()的时候,storage模块会创建它。如果.yo-rc.json 不在当前的工作目录,请确保他也不在其他的项目目录里。

 

十一. 运行上下文

在generator内,所有的静态方法都会被作为action而自定执行,当然generator也提供了可以声明不自动执行的辅助函数,generator提供了三种可以创建辅助函数的方法.

通过下划线开头定义函数,如:CopyFiles 2 使用实例函数声明:

generators.Base.extend({
        constructor: function () {
          this.helperMethod = function () {
            console.log('won\'t be called automatically');
          };
        }
      });

继承一个父级generator:

var MyBase = generators.Base.extend({
        helper: function () {
          console.log('methods on the parent generator won\'t be called automatically');
        }
      });

      module.exports = MyBase.extend({
        exec: function () {
          this.helper();
        }
      });

 

十二.generator Arguments

Arguments是在命令行中直接传递的。 如:yo webapp my-project,接受键值对的条件。

desc:描述argument

required:定义是否必须

optional:是否可选择的

type:参数类型,支持的类型有String Number Array Object

defaults: argument默认值

banner:字符串显示的使用说明(这是默认提供)

注意:参数必须的定义在construct函数内,否则当你使用generator调用命令(如:yo webapp --help)的时候,不能够输出相关的帮助信息。

示例:

var _ = require('lodash');

    module.exports = generators.Base.extend({
      // note: arguments and options should be defined in the constructor.
      constructor: function () {
        generators.Base.apply(this, arguments);

        // This makes `appname` a required argument.
        this.argument('appname', { type: String, required: true });
        // And you can then access it later on this way; e.g. CamelCased
        this.appname = _.camelCase(this.appname);
      }
    });

十三. Options

option和argument很相似,但是option是作为命令行标识使用的,如yo webapp --coffee。

我们可可以通过generator.option()添加option。

示例:

module.exports = generators.Base.extend({
   // note: arguments and options should be defined in the constructor.
   constructor: function () {
     generators.Base.apply(this, arguments);

     // This method adds support for a `--coffee` flag
     this.option('coffee');
     // And you can then access it later on this way; e.g.
     this.scriptSuffix = (this.options.coffee ? ".coffee": ".js");
   }
 });

十四. 输出消息

输出消息是通过generator.log模块来处理实现的。不建议使用console.log输出命令。

示例:

module.exports = generators.Base.extend({
      myAction: function () {
        this.log('Something has gone wrong!');
      }
    });

十五. 处理依赖关系

一般当你运行你的generator的时候,你经常需要通过 npm 和 Bower来安装一些generator用到的依赖模块。而这些任务是非常繁琐的,为了方便,yeoman将这部分任务抽离了出来。

npm

你只需要调用generator.npmInstall() 命令就可以执行npm安装命令,yeoman确保了npm install只执行了一次,即使他被多个generator调用。

例如你想安装lodash作为dev dependency:

generators.Base.extend({
     installingLodash: function() {
       this.npmInstall(['lodash'], { 'saveDev': true });
     }
   });

上面代码等同于调用了npm install lodash --save-dev命令。

Bower

你只需要调用generator.bowerInstall()即可启动安装命令。yeoman确保了bower install只执行了一次,即使他被多个generator调用。

npm && Bower

调用enerator.installDependencies()即可同时运行npm 和 bower。

其他tools

yeoman抽离了spawn命令,这个抽离保证了我们可以在Linux ,mac 以及windows系统上可以很好的运行。

假如你是一个PHP狂热爱好者,你想运行composer命令,你可以这样做:

generators.Base.extend({
      install: function () {
        this.spawnCommand('composer', ['install']);
      }
    });

请确保面spawn命令在install队列里。因为您的用户不愿意等待在那儿直到安装命令完成。

十六. 上下文路径

为了方便文件流的输入输出,Yeoman使用两种位置环境。

1. 目标上下文

目标上下文定义为当前工作目录或含.yo-rc.json文件最接近的父文件夹。该.yo-rc.json文件定义了一个generator项目的根目录。该文件允许用户在子目录中运行命令,并让他们在项目中可以运行。这确保了用户行为的一致。

你可以通过generator.destinationRoot()命令获取目标路径,也可以通过generator.destinationPath('sub/path')来拼一个路径:

// Given destination root is ~/projects
    generators.Base.extend({
      paths: function () {
        this.destinationRoot();
        // returns '~/projects'

        this.destinationPath('index.js');
        // returns '~/projects/index.js'
      }
    });

2. 模板上下文

模板上下文是你保存模板文件的目录,他一般是你要读取和复制的目录。模板上下文一般是默认是定义在./templates/目录的.你可以通过generator.sourceRoot('new/template/path')命令来重写。你可以通过generator.sourceRoot()或者generator.templatePath('app/index.js').来获取路径。

generators.Base.extend({
      paths: function () {
        this.sourceRoot();
        // returns './templates'

        this.templatePath('index.js');
        // returns './templates/index.js'
      }
});

3. “内存”文件系统

当涉及到覆盖用户的文件的时候,yeoman非常的谨慎,基本上,每一个write动作都是一个为已经存在的文件解决冲突的过程。帮助用户严重需要覆盖的内容。

4. 文件工具

generator的this.fs暴露出所有的文件方法,通过mem-fs editor .

十七.完整示例

'use strict';

    var generators = require('yeoman-generator');
    var mkdirp = require('mkdirp');
    var yosay = require('yosay');
    var chalk = require('chalk');
    module.exports = generators.Base.extend({

        constructor: function() {
            generators.Base.apply(this, arguments);
            this.option('coffee');
            this.scriptSuffix = (this.options.coffee ? ".coffee": ".js");
        },

        initializing: function() {
            var message = chalk.bgBlack.bold('\nWelcome to webApp\n') + chalk.underline('webApp.github.io\n');
            this.log(yosay(message));
        },

        prompting: function() {
             var prompts = [{
                type:'input',
                name: 'appName',
                message: 'input app name .',
                default: 'webApp'
            }];
            this.prompt(prompts, function (answers) {
                this.log(answers);
            }.bind(this));
        },

        configuring: function() {
            this.config.save();
        },

        selfFunction: function () {
            this.log("执行了自定义方法");
        },

        writing: function() {
           this.fs.copyTpl(
             this.templatePath('index.html'),
             this.destinationPath('public/index.html'),
             { title: 'Templating with Yeoman' }
           );
        },
   
    });

实列:

注:使用_.template(lodash的template功能)和this.fs.write将模版中的关键字替换为用户的输入项。

this.fs.readJSONthis.fs.writeJSON,则是将package.json模版中的数据读取出来,作出一定修改写成新的文件。

最后使用mkdirpthis.fs.copy构建工程目录结构和将一些不要修改的配置文件copy到指定目录。

发布

首先npm官网注册一个npm账号,如果有则运行npm login登陆。然后到工程根目录下,运行npm publish进行发布。

官网api:https://yeoman.github.io/generator/Generator.html#destinationRoot

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值