Yeoman 脚手架工具

文章目录

    • 安装Yeoman
    • 安装生成器( Generators)
      • 查看生成器帮助
      • 访问生成器主页
      • 子生成器
      • 其它的命令
    • 生成器
      • 生成器目录
      • 扩展生成器
        • 覆写构造函数
      • 添加自己的功能
      • 运行生成器
      • 寻找项目根目录
    • 编写生成器
      • 私有方法
    • 运行循环(The run loop)
      • 异步任务
      • 用户交互
        • 提示
      • 使用用户的答案
      • 记住用户首选项
      • 参数
      • 选项
      • 输出信息
    • 组合使用
      • this.composeWith()
      • dependencies or peerDependencies
    • 管理依赖
      • npm
        • 编程方式管理依赖
      • Yarn
      • Bower
      • 组合使用
      • 使用其它工具
    • 与文件系统的交互
      • 目标上下文(Destination context)
      • 模板上下文(Template context)
      • 内存中的文件系统
        • 文件工具
        • 拷贝模板文件
        • 通过输出流转换输出文件
      • 更新现有的文件
    • 管理配置
      • 方法
      • `.yo-rc.json` 结构

Yeoman 脚手架应用工具

  Yeoman 是一个通用的脚手架系统,可以用来创建任何类型的应用程序。Yeoman和语言无关所以可以用来生成任何语言的项目。

  Yeoman 本身没有任何流程控制,所有流程都是由Yeoman的插件生成器( Generators)来控制的。

安装Yeoman

npm install -g yo

安装生成器( Generators)

只是单独安装Yeoman,是没有用的,需要同时安装需要的生成器( Generators)。生成器都是命名为generator-XYZ的npm包,都可以通过npm来安装。
例如

npm install -g generator-webapp

安装之后需要搭建新项目,就可以运行对应的命令调用指定的生成器即可。

yo webapp

查看生成器帮助

大多数生成器,都会询问一系列的问题用来配置工程,如果想要查看有哪些选项,可以使用help命令。

yo webapp --help

访问生成器主页

大多数生成器依赖构建系统(Grunt 或者Gulp)或者包管理器(npm),如果需要了解如何使用这个生成器,可以使用home 命令访问生成器的主页。

npm home generator-webapp

子生成器

一部分复杂的生成器可能会提供额外的生成器用来生成项目的较小的部分。这些生成器通常被称为子生成器,可以通过generator:sub-generator来访问。
例如:

yo angular:controller MyNewController

其它的命令

  • yo --help 访问帮助信息
  • yo --generators 列出已经安装的生成器
  • yo --version 查看版本
  • yo doctor 命令可以诊断并提供解决常见问题的步骤

生成器

生成器是Yeoman生态系统的基石。它们是用来最终为用户生成文件的插件。

生成器是一个npm包,所以和创建普通的npm一样,不过,生成器的名字格式要设置为generator-xxxx的形式。Yeoman依靠这个在文件系统中查找可用的生成器。

{
  "name": "generator-name",
  "version": "0.1.0",
  "description": "",
  "files": [
    "generators"
  ],
  "keywords": ["yeoman-generator"],
  "dependencies": {
    "yeoman-generator": "^1.0.0"
  }
}

生成器目录

Yeoman的功能取决于如何和构建目录树,每个子生成器都包含在自己的文件夹中。我们使用yo name 调用的默认生成器在app文件夹中。子生成器则存储在与sub命令相同的文件夹中。
例如:

├───package.json
└───generators/
    ├───app/
    │   └───index.js
    └───router/
        └───index.js

这个生成器,将会暴露 yo nameyo name:router 两个命令。
Yeoman 也支持两个不同的文件夹结构。它将会搜索 ./generators/ 两个文件夹下的内容。上面的例子也可以写作:

├───package.json
├───app/
│   └───index.js
└───router/
    └───index.js

如果使用了第二种目录结构,需要确保 package.json 中的 files 属性指向对应的文件夹

{
  "files": [
    "app",
    "router"
  ]
}

扩展生成器

Yeoman 提供了一个基础的生成器,我们可以扩展它来实现我们的功能,这个生成器将会简化大多数任务,

// generator/app/index.js
var Generator = require('yeoman-generator');

module.exports = class extends Generator {};

覆写构造函数

某些生成器方法只能在构造函数内部使用,这些方法可能会执行例如设置状态之类的操作,并且在构造函数之外无法运行。
可以通过下面的方法来覆盖构造函数。

module.exports = class extends Generator {
  // The name `constructor` is important here
  constructor(args, opts) {
    // Calling the super constructor is important so our generator is correctly set up
    super(args, opts);

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

添加自己的功能

添加到原型中的每个方法都会在调用生成器后运行,并且通常是按顺序运行的。但是,正如我们将在下一节中看到的,一些特殊的方法名称将触发特定的运行顺序。

module.exports = class extends Generator {
  method1() {
    this.log('method 1 just ran');
  }

  method2() {
    this.log('method 2 just ran');
  }
};

运行生成器

这个时候我们就有了一个生成器,如果是本地开发的生成器,还没有发布为npm模块,我们可以使用npm创建全局模块并使用npm link 链接到本地,(pnpm 的多包管理也不错)

npm link

这个命令将会安装,当前的项目到全局模块,然后就可以 使用yo name来调用了。

寻找项目根目录

Yeoman 将会沿着目录树向上搜索, 如果找到.yo-rc.json文件,它将会把这个文件所在的目录作项目根目录。Yeoman将会切换工作目录到yo-rc.json文件所在的目录中。

编写生成器

直接附加到生成器原型的方法都被视为一个任务,有Yeoman按顺序循环运行,简单来说Object.getPrototypeOf(Generator)返回的每一个函数都会自动运行。

私有方法

想要让Yeoman不把原型上的方法视为任务,可以通过下面三个方法

  1. 用下划线做方法名前缀
     class extends Generator {
         method1() {
           console.log('hey 1');
         }
    
         _private_method() {
           console.log('private hey');
         }
       }
       ```
    
  2. 实例方法
      class extends Generator {
         constructor(args, opts) {
           // Calling the super constructor is important so our generator is correctly set up
           super(args, opts)
    
           this.helperMethod = function () {
             console.log('won\'t be called automatically');
           };
         }
       }
    
  3. 扩展父生成器
     class MyBase extends Generator {
     helper() {
       console.log('methods on the parent generator won\'t be called automatically');
     }
    }
    
    module.exports = class extends MyBase {
     exec() {
       this.helper();
     }
    };
    

运行循环(The run loop)

如果只有一个生成器,那么按照顺序运行任务时可以的。但是一旦开始组合使用生成器就不行了,这就是Yeoman使用循环的原因。

Yeoman 运行循环(The run loop) 是具有优先级支持的系统,使用分组队列来处理循环。

优先级为在代码中定义为特殊的原型方法的名称、当方法名与优先级名称相同的时候,运行循环会将该方法推送到特殊队列中,如果不匹配则会推送到default中

class extends Generator {
  priorityName() {}
}

也可在同一个队列中添加多个方法:

Generator.extend({
  priorityName: {
    method() {},
    method2() {}
  }
});

注意:这个写法不支持js 的class语法

可用的优先级(按运行顺序):

  1. initializing - 初始化方法(检查当前项目状态,获取配置等)
  2. prompting - 提示用户输入选项的位置(调用的位置this.prompt())
  3. configuring - 保存配置和配置项目(创建文件和其他元数据文件).editorconfig
  4. default- 如果方法名称与优先级不匹配,则会将其推送到此组。
  5. writing- 写入生成器特定文件(路由、控制器等)的位置
  6. conflicts- 处理冲突的地方(内部使用)
  7. install- 运行安装的地方(npm,bower)
  8. end- 叫最后,清理,说再见等

遵循这些优先级指南,您的生成器将与其他生成器配合得很好。

异步任务

有多种方法暂停 运行循环 直到异步任务完成。最简单的方法是,返回一个Promise 。循环将会一直等到Promise 返回。

如果依赖的API不支持Promise,也可以使用this.async() 方法。

asyncTask() {
  var done = this.async();

  getUserEmail(function (err, name) {
    done(err);
  });
}

如果 done 方法的入参是一个Error 循环将会终止并挂起。

用户交互

提示

提示是生成器和用户的主要交互方式,由Inquirer.js 提供。

prompt 是一个异步的方法返回一个Promise,所以你需要返回一个promise 等待对应的操作完成。然后再继续。

module.exports = class extends Generator {
  async prompting() {
    const answers = await this.prompt([
      {
        type: "input",
        name: "name",
        message: "Your project name",
        default: this.appname // Default to current folder name
      },
      {
        type: "confirm",
        name: "cool",
        message: "Would you like to enable the Cool feature?"
      }
    ]);

    this.log("app name", answers.name);
    this.log("cool feature", answers.cool);
  }
};

使用用户的答案

module.exports = class extends Generator {
  async prompting() {
    this.answers = await this.prompt([
      {
        type: "confirm",
        name: "cool",
        message: "Would you like to enable the Cool feature?"
      }
    ]);
  }

  writing() {
    this.log("cool feature", this.answers.cool); // user answer `cool` used
  }
};

记住用户首选项

用户在使用生成器的时候对于某些问题总是提供相同的输入,对于这些问题,我们可以记住用户得选择,并将该答案作为新的默认值。

Yeoman 通过扩展 Inquirer.js 的API 给question 对象新增 store 属性。 此属性允许您指定将来应将用户提供的答案用作默认答案。这可以按如下方式完成

this.prompt({
  type: "input",
  name: "username",
  message: "What's your GitHub username",
  store: true
});

注意:提供默认值,将会阻止用户返回空回答

参数

参数可以直接通过 命令行传递:

yo webapp my-project

为了通知系统我们期望一个参数,我们使用this.argument()方法。此方法接受name参数 (string) 或者一个对象。

name 参数可以通过 this.options[name] 访问到:

这个选项接受的对象的属性有

  • desc 参数描述
  • required Boolean 是否必填
  • type String, Number, Array 类型,也可以是一个自定义函数来解析
  • default 参数默认值

this.argument()这个方法,只能在 构造函数constructor 中调用。否则当用户使用help 的时候,Yeoman将无法输出帮助信息。

module.exports = class extends Generator {
  // note: arguments and options should be defined in the constructor.
  constructor(args, opts) {
    super(args, opts);

    // This makes `appname` a required argument.
    this.argument("appname", { type: String, required: true });

    // And you can then access it later; e.g.
    this.log(this.options.appname);
  }
};

选项

选项和参数有点类似,但是它们是作为命令行标志编写的

yo webapp --coffee

为了让系统知道我们接受一个选项,可以使用this.option()方法。这个方法接受一个 name(String) 参数,

name 的值可以通过 this.options[name]来访问,

选项可以有第二个参数,

  • desc 选项描述
  • alias 选项别名
  • type 类型 Boolean, String or Number 也可以自定义函数
  • default 默认值
  • hide Boolean 是否在help信息中隐藏
module.exports = class extends Generator {
  // note: arguments and options should be defined in the constructor.
  constructor(args, opts) {
    super(args, opts);

    // This method adds support for a `--coffee` flag
    this.option("coffee");

    // And you can then access it later; e.g.
    this.scriptSuffix = this.options.coffee ? ".coffee" : ".js";
  }
};

输出信息

Yeoman 输出信息通过 this.log模块.

你可以直接使用this.log。功能基本和console.log相同。

module.exports = class extends Generator {
  myAction() {
    this.log("Something has gone wrong!");
  }
};

完整API文档

组合使用

将较小的部分组合成为一个大的东西。

this.composeWith()

该方法允许生成器与另一个生成器(或子生成器)并行运行。这样,它就可以使用其他生成器的功能,而不必自己完成所有操作。

  1. generatorPath 想要组合的生成器路径(通常使用 require.resolve())
  2. options 要传给生成器的选项

例如:

this.composeWith(require.resolve('generator-bootstrap/generators/app'), {preprocessor: 'sass'});

require.resolve()返回 Node.js 将加载提供的模块的路径。

尽管不鼓励这样做,但您也可以将生成器命名空间传递给 。在这种情况下,Yeoman 将尝试查找作为peerDependencies 或全局安装在用户系统上的生成器

this.composeWith('backbone:route', {rjs: true});

第一个参数也可以是一个对象,对象应该包含下面的属性:

  1. Generator - 生成器的calss
  2. path - 生成器文件地址
// Import generator-node's main generator
const NodeGenerator = require('generator-node/generators/app/index.js');

// Compose with it
this.composeWith({
  Generator: NodeGenerator,
  path: require.resolve('generator-node/generators/app')
});

例子:

// In my-generator/generators/turbo/index.js
module.exports = class extends Generator {
  prompting() {
    this.log('prompting - turbo');
  }

  writing() {
    this.log('writing - turbo');
  }
};

// In my-generator/generators/electric/index.js
module.exports = class extends Generator {
  prompting() {
    this.log('prompting - zap');
  }

  writing() {
    this.log('writing - zap');
  }
};

// In my-generator/generators/app/index.js
module.exports = class extends Generator {
  initializing() {
    this.composeWith(require.resolve('../turbo'));
    this.composeWith(require.resolve('../electric'));
  }
};

执行结果

prompting - turbo
prompting - zap
writing - turbo
writing - zap

dependencies or peerDependencies

npm允许三种类型的依赖

  • dependencies 将生成器安装到当前项目
  • peerDependencies 作为兄弟依赖安装
     ├───generator-backbone/
     └───generator-gruntfile/
    
  • devDependencies 用于测试和开发过程,这里不需要

使用peerDependencies的时候不要指定特定版本,或者范围很窄的版本。

{
  "peerDependencies": {
    "generator-gruntfile": "*",
    "generator-bootstrap": ">=1.0.0"
  }
}

管理依赖

Yeoman 提供安装助手用于安装依赖。

npm

this.npmInstall() 方法用来安装npm 依赖

class extends Generator {
  installingLodash() {
    this.npmInstall(['lodash'], { 'save-dev': true });
  }
}

相当于调用 npm install lodash --save-dev

编程方式管理依赖

class extends Generator {
  writing() {
    const pkgJson = {
      devDependencies: {
        eslint: '^3.15.0'
      },
      dependencies: {
        react: '^16.2.0'
      }
    };

    // Extend or create package.json file in destination path
    this.fs.extendJSON(this.destinationPath('package.json'), pkgJson);
  }

  install() {
    this.npmInstall();
  }
};

Yarn

this.yarnInstall()

Bower

this.bowerInstall()

组合使用

this.installDependencies(),默认执行 npm 和bower

generators.Base.extend({
  install: function () {
    this.installDependencies({
      npm: false,
      bower: true,
      yarn: true
    });
  }
});

使用其它工具

Yeoman 允许用户使用 spawn 来执行任意的命令行命令。

class extends Generator {
  install() {
    this.spawnCommand('composer', ['install']);
  }
}

与文件系统的交互

目标上下文(Destination context)

目标上下文是Yeoman将在其中搭建新的应用程序的文件夹,当前工作目录或者最近的包含.yo-rc.json的父级目录。.yo-rc.json 定义了Yeoman工程的根目录。

可以通过this.destinationRoot() 方法获取目标地址。或者通过this.destinationPath('sub/path').来获取地址链接。

// Given destination root is ~/projects
class extends Generator {
  paths() {
    this.destinationRoot();
    // returns '~/projects'

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

可以使用this.destinationRoot('new/path')来设置默认的根目录,但是为了保持一致,你不应该修改这个默认的目标目录。如果你想要知道用户是在哪里运行yo 命令,可以使用this.contextRoot来获取对应的路径,

模板上下文(Template context)

模板上下文是指存储模板文件的地址,通常会从其中读取和赋值文件,模板上下文默认为./templates/,你可以通过this.sourceRoot('new/template/path')来修改它,this.sourceRoot()可以用来获取这个路径,或者this.templatePath('app/index.js')来拼接路径。

class extends Generator {
  paths() {
    this.sourceRoot();
    // returns './templates'

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

内存中的文件系统

由于写入文件

异步写入API很难用,Yeoman提供了一个同步的文件写入系统API,将每一个文件写入 内存中的文件系统,在Yeoman运行完成的时候一次性写入硬盘。内存文件系统在所有的生成器之间共享。

文件工具

生成器通过内存文件系统编辑器的实例thsi.fs暴露出所有的文件操作方法。内存文件编辑器文档.

拷贝模板文件

<html>
  <head>
    <title><%= title %>title>
  head>
html>

copyTpl 使用 EJS模板语法

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

例子:接受用户参数并输出到模板

class extends Generator {
  async prompting() {
    this.answers = await this.prompt([{
      type    : 'input',
      name    : 'title',
      message : 'Your project title',
    }]);
  }

  writing() {
    this.fs.copyTpl(
      this.templatePath('index.html'),
      this.destinationPath('public/index.html'),
      { title: this.answers.title } // user answer `title` used
    );
  }
}

通过输出流转换输出文件

Yeoman 的生成器系统允许我们自定义过滤器,在文件写入的时候对文件进行格式化等操作,
一旦Yeoman处理结束,Yeoman将会把所有修改的文件写入硬盘。这个过程将会通过 vinyl 对象流,任何生成器作者都可以注册一个transformStream 方法来修改,文件的路径或者内容。

var beautify = require("gulp-beautify");
this.registerTransformStream(beautify({ indent_size: 2 }));

注意 任何类型的文件都将通过此流传递,所以需要使用gul-if 或者gulp=filter 这类的工具有助于过滤无效的文件。

更新现有的文件

更新现有的文件,是最可靠的方法是通过解析文件的AST。

  • Cheerio 用于解析HTML
  • Esprima 解析JavaScript
  • JSON JSON对象方法即可操作
  • 等等
    不要随意使用正则表达式。

管理配置

存储用户配置并在子生成器之间共享是一个比较常见的任务。这些配置可以通过Yeoman存储 在.yo-rc.json 文件之中。可以通过generator.config对象暴露API来访问。

方法

this.config.save()
这个方法将会把配置写入.yo-rc.json。如果不存在将会自动创建。.yo-rc.json文件也决定了工程的根目录,save方法将会在你每次调用set方法之后自动调用,所以通常并不需要显式调用。

this.config.set()
set 方法接受 key 和一个关联的值,或者一个对象,值必须是JSON序列化的。

this.config.get()

get 接受一个String参数返回关联的值。

this.config.getAll()
返回所有的可用配置,返回的对象是值传递。
this.config.delete()
删除数据
this.config.defaults()
设置默认值,如果该值已存在则保持不变,如果不存在则添加。

.yo-rc.json 结构

{
  "generator-backbone": {
    "requirejs": true,
    "coffee": true
  },
  "generator-gruntfile": {
    "compass": false
  }

你可能感兴趣的:(tool,npm,javascript,前端)