上手 yeoman generator

最近折腾脚手架相关的一些事情。说到脚手架,不得不谈的就是yeoman了。

是什么

yeoman是一个脚手架生成工具。

yeoman generator则是yeoman的精髓所在。

从我的理解来看。yeoman就是一个工具外壳,它定制了如何调用generator,给generator提供了运行环境。yeoman generator则是解耦出来的核心部分,负责完成一个脚手架应该做的事。

线上已经有很多generator,可以满足我们一大波需求。不过要做到真正灵活,完全符合自己的需求、业务中的需要就要自己自定义generator了。

怎么做

yeoman的强大之处在于它提供了一套非常强大的编写自定义generatorAPI,而且上手非常容易。只要按照特定的约束,很快就可以定制一套自己的generator。话不多说,马上一起来看看怎么做。

目录结构

|- app
    |- index.js
    |- template
        |- 模板文件
|- package.json (主入口为app/index.js)

初始化一个npm包,定制目录结构如上,这样就简单完成generator的目录结构啦。当然你可以用generator-generator生成符合规范的generator,这样更加快捷。

index.js结构示例

package.json不多说,注意主入口写好就行。index.js 也是按照具体的约束,一个简单的示例:

var generators = require('yeoman-generator')

module.exports = generators.Base.extend({  
  constructor: function () {
    generators.Base.apply(this, arguments)
  }

  // 方法A

  // 方法B
})

一个 Yeoman Generator 被创建后,会依次调用它原型上的方法,调用的顺序如下:

initializing - 初始化一些状态之类的,通常是和用户输入的 options 或者 arguments 打交道,这个后面说。
prompting - 和用户交互的时候(命令行问答之类的)调用。
configuring - 保存配置文件(如 .babelrc 等)。
default - 其他方法都会在这里按顺序统一调用。
writing - 在这里写一些模板文件。
conflicts - 处理文件冲突,比如当前目录下已经有了同名文件。
install - 开始安装依赖。

也可以自定义方法,比如demo里方法A会先于方法B执行。下面具体介绍下每个方法的一些作用。

prompting

用于做命令行的交互,这个应该是最常用的一个功能。用于在命令行和用户交互,用户提一些问题,我们的generator收集问题的结果。一个简单的例子:

prompting: function () {
        let models = [{
            name: 'eslint',
            checked: true
        }, {
            name: 'sass-lint',
            checked: true
        }];

        const prompts = [{
            type: 'checkbox',
            name: 'enable',
            message: '开启哪些功能?',
            choices: models
        }, {
            type: 'confirm',
            name: 'installDependencies',
            message: '安装相关依赖?',
            when: (props) => {
                return props.enable.length;
            }
        }, {
            type: 'list',
            name: 'tool',
            message: '使用npm/tnpm?',
            choices: [
                CFG.CHOICE.TOOL.NPM,
                CFG.CHOICE.TOOL.TNPM
            ],
            when: (props) => {
                return props.installDependencies;
            }
        }];

        return this.prompt(prompts).then((options) => {
            this.userOptions = options;
        });
    },

这里不对代码细解释,只需要知道这里可以做用户命令行交互,具体每个参数有什么意义,github上搜索一下Inquirer.js就很清晰了。

writing

这里用于文件拷贝,读文件,写文件。一个简单的例子:

writing: function () {
        const options = this.userOptions;
        const imlintrcPath = this.cfg.imPath;
        const imlintrcJson = {
            config: options
        };
        const enableModules = options.enable || [];
        const MODULES = CFG.MODULES;
        const pkgPath = this.cfg.pkgPath;
        let pkgJson;

        /** 1. 复制基础样板文件 */
        this.cfg.files.forEach((item) => {
            this.fs.copy(
                this.templatePath(item),
                this.destinationPath(item)
            );
        });

        /** 2. 创建imlintrc文件 */
        this.fs.writeJSON(imlintrcPath, imlintrcJson);

        /** 3. 修改package.json scripts配置 */
        try {
            pkgJson = this.fs.readJSON(pkgPath);
        } catch (ex) {
            console.log('imlint: package.json不合法');
            process.exit();
        }

        pkgJson.devDependencies = pkgJson.devDependencies || {};
        pkgJson.scripts = pkgJson.scripts || {};
        Object.assign(pkgJson.scripts, this.cfg.scripts || {});

        /** 4. 部署对应模块,包括:1. 迁移文件 2. 修改package.json devDependencies配置 */
        enableModules.forEach((item) => {
            const cur = MODULES[item];

            if (!cur) {
                return;
            }

            Object.assign(pkgJson.devDependencies, cur.pkgs);

            if (cur.files) {
                cur.files.forEach((file) => {
                    this.fs.copy(
                        this.templatePath(file),
                        this.destinationPath(file)
                    );
                });
            }
        });

        /** 5. 写package.json文件 */
        this.fs.writeJSON(pkgPath, pkgJson);
    },

具体文件API的意义可参见mem-fs-editor 这个库

install

用于安装依赖,比如npm install一个lodash

install() {
    this.npmInstall(['lodash'], { 'save-dev': true });
}

发布

index.js写完,一个简单的generator就ok了。上面这个DEMO具体的详细例子,可以看 generator-imlint-init

将上面这个npm包发布后,就可以按如下方法安装使用了~~

npm install -g yo
npm install -g generator-imlint-init

yo imlint-init

小结

例子比较简单,方法也只说了下generator最常用的三个方法。更多的功能参见yeoman官方文档~~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

[WCF权限控制]模拟(Impersonation)与委托(Delegation)[上篇]

由于服务操作是在寄宿进程中执行,在默认的情况下,服务操作是否具有足够的权限访问某个资源(比如文件)决定于执行寄宿进程Windows帐号的权限设置,而与作为客户端...

2676
来自专栏日常学python

python爬虫常用库之urllib详解

这是日常学python的第10篇原创文章 以下为个人在学习过程中做的笔记总结之爬虫常用库urllib urlib库为python3的HTTP内置请求库 uril...

3598
来自专栏芋道源码1024

分布式消息队列 RocketMQ 源码分析 —— Message 拉取与消费(上)

摘要: 原创出处 http://www.iocoder.cn/RocketMQ/message-pull-and-consume-first/ 「芋道源码」欢迎...

3716
来自专栏码农阿宇

asp.net core轻松入门之MVC中Options读取配置文件

接上一篇中讲到利用Bind方法读取配置文件 ASP.NET Core轻松入门Bind读取配置文件到C#实例 那么在这篇文章中,我将在上一篇文章的基础上,利...

2564
来自专栏CDA数据分析师

学会这几招,轻松掌握Python文件管理

一、Python中的文件管理 文件管理是很多应用程序的基本功能和重要组成部分。Python可以使文件管理极其简单,特别是和其它语言相对比。 以下,Peyton ...

3086
来自专栏JavaEdge

操作系统之文件管理

将文件属性从外存拷到内存中打开文件表的一表目中 将其编号返回给用户。 系统可利用该编号到打开文件表中去查找。

36510
来自专栏韩东吉的Unity杂货铺

零基础入门 26:通过代码关闭程序

Hello,各位小伙伴,又是好久没见,因为最近工作变动的原因,停更了快一个月的时间,好多同学都QQ问我什么时候更,会不会不更新了之类的。答案当然是不会的。

821
来自专栏Esofar 开发日记

[译]RabbitMQ教程C#版 - 主题

在 教程[4] 中,我们改进了我们日志系统。我们用direct交换器替换了只能呆滞广播消息的fanout交换器,从而可以有选择性的接收日志。

1053
来自专栏Linyb极客之路

文件监听之WatchService浅析

用watchservice修改配置文件方式仅适合于比较小的项目,例如只有一两台服务器,而且配置文件是可以直接修改的。例如 Spring mvc 以 war 包的...

1332
来自专栏张善友的专栏

.NET 异常处理的动作策略(Action Policy)

SQL Server 2008基于策略的管理,基于策略的管理(Policy Based Management),使DBA们可以制定管理策略,并将这些策略应用到服...

2047

扫码关注云+社区

领取腾讯云代金券