脚手架这个词估计做前端的都很熟悉。在没有实现前端工程化的年代,前端代码的组织都是纯手工维护的。比如我要做一个网站页面,那么我需要手动创建一个文件夹来存放代码文件,我把它命名为demo。然后在demo目录下创建src文件夹,在src文件夹内创建css文件夹、js文件夹、image文件夹、lib文件夹等等…一切都是手工维护。自从node.js出现后,前端开发才慢慢开始告别刀耕火种,越来越多的自动化工具充斥我们的眼球。模板生成、代码压缩、构建打包、自动部署…这些已经成为构建前端工程项目的标配。那么,一个模板生成的命令行工具的原理是什么?怎样开发一个属于自己的命令行脚手架工具?希望我写的这篇小文章会给大家带来一点启发。
原理
生成模板文件的方式可以是本地新建空白文件,然后进行文件内容读写;又或者是把本地已有的模板进行配置信息填充。然而我们知道,IO读写的速度非常慢,性能消耗大。但是一个模板生成器(Generator)如果是基于已有的模板文件进行配置填充,然后在copy到项目目录对应的位置,那会比直接读写磁盘效率更高。所以一般来说,模板生成器会采用第二种工作原理。
Yeoman-generator
模板生成器的脚手架有很多,前端领域每天都会有很多类似的轮子源源不断地从开源社区流出。这里我用来开发自己的generator的工具是Yeoman。Yeoman的Logo是一个戴着红帽子的大胡子,它是一个通用的脚手架搭建系统,可以创建任何的类型的app。同时它又是”语言无感知”的,支持创建任何类型开发语言的项目,Web, Java, Python, C# 等等。Yeoman的通用性在于,它本身不做任何决定,所有的操作都是通过Yeoman环境里面的各种generator实现的。通过自定义generator,我们可以创建任何格式的项目目录。这是Yeoman的最大魅力之处。另外,Yeoman通过提供promting这个方法实现输入式命令行交互,可以让用户自由填写配置信息,交互体验也非常棒。下面说说怎样基于Yeoman开发一个简单的generator:
Simple-dir
simple-dir是我自己捣鼓的一个很简单的Yeoman-generator,在这里我拿它来作为讲解示例,大家也可以打开详细代码来看,欢迎star,也欢迎提issue。
第一步,package.json
开发一个Yeoman-generator,我们要做的第一步就是配置package.json。有几个关键的地方,一个是,name的值的格式必须是”generator-“前缀 + Yeoman-generators官方源列表上的唯一值(如果你要共享你的generator到官方generator源的话);第二个就是,keywords属性必须包括”yeoman-generator”这个值;第三,files属性是命令自定义文件,app是默认的命令;第四,必须要安装最新版本的yeoman-generator依赖,可以直接运行:npm install –save yeoman-generator 获取最新的版本号。详细的package.json可以看下面这份:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
{ "name": "generator-simple-dir", "version": "0.0.1", "description": "A very simple template generator", "files": [ "generators/app", "generators/comp", "generators/page" ], "author": "橙乡果汁", "license": "MIT", "keywords": [ "yeoman-generator" ], "repository": { "type": "git", "url": "git@github.com:hugzh/generator-simple-dir.git" }, "bugs": { "url": "https://github.com/hugzh/generator-simple-dir/issues" }, "dependencies": { "glob": "^7.1.0", "mkdirp": "^0.5.1", "yeoman-generator": "^0.24.1" } } |
对应的src目录格式应该是这样的:
├───package.json └───generators/ ├───app/ │ └───index.js ├───comp/ │ └───index.js └───page/ └───index.js
你也可以直接把files属性直接写成:
1 2 3 4 5 |
"files": [ "app", "comp", "page" ] |
但是这样的话,你的代码根目录就必须直接包含app,comp和page文件夹。
第二步,拓展generator
这里我们有三个generator——app,comp和page。以page为例,我们来实现一个generator。
首先,需要继承Yeoman提供的generator基类:
1 2 |
var generators = require('yeoman-generator'); module.exports = generators.Base.extend(); |
然后我们就可以在基类内部重写generator的方法了。Yeoman提供了一系列的基类方法:
initializing – 初始化 (检查当前项目状态、获取配置文件内容等等) prompting – 获取用户输入,实现与用户的交互 (通过this.prompt()调用) configuring – 保存配置并配置整个项目 (比如创建 .editorconfig 文件和其他媒介文件) default – 当定义的方法没有匹配任何基类方法的时候用到 writing – 根据自定义的规则写入具体的generator文件 (routes, controllers, etc) conflicts – 内部冲突处理 install – 安装npm、bower等依赖的地方 end – 在最后调用, 实现cleanup, say good bye等功能。
在示例generator-simple-dir里,page这个generator的作用是创建页面,需要生成html/css/js文件。在generators.Base.extend函数内部,page实现了 initializing、prompting、writing、end这几个方法。对于prompting这样的异步方法,需要在交互结束的时候调用this.async()来结束异步任务。Yeoman实现用户交互的核心方法是prompting,它是一个异步的方法,并且返回一个promise。prompting方法通过一个数组参数,可以实现链式的用户输入。其中input类型的是用户输入自定义内容,confirm类型是作为True/False判断的prompt,输入Y/N。官方的示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
module.exports = generators.Base.extend({ prompting: function () { return 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?' }]).then(function (answers) { this.log('app name', answers.name); this.log('cool feature', answers.cool); }.bind(this)); } }) |
如果你想要记住用户输入的一个内容,用来做后面输入的默认值的话,还可以通过增加store:true配置来实现。 在generator-simple-dir里面,page这个generator包含4个执行步骤:初始化、获取用户输入、根据用户输入生产模板文件、结束返回,实现的代码如下:
脚手架这个词估计做前端的都很熟悉。在没有实现前端工程化的年代,前端代码的组织都是纯手工维护的。比如我要做一个网站页面,那么我需要手动创建一个文件夹来存放代码文件,我把它命名为demo。然后在demo目录下创建src文件夹,在src文件夹内创建css文件夹、js文件夹、image文件夹、lib文件夹等等…一切都是手工维护。自从node.js出现后,前端开发才慢慢开始告别刀耕火种,越来越多的自动化工具充斥我们的眼球。模板生成、代码压缩、构建打包、自动部署…这些已经成为构建前端工程项目的标配。那么,一个模板生成的命令行工具的原理是什么?怎样开发一个属于自己的命令行脚手架工具?希望我写的这篇小文章会给大家带来一点启发。
原理
生成模板文件的方式可以是本地新建空白文件,然后进行文件内容读写;又或者是把本地已有的模板进行配置信息填充。然而我们知道,IO读写的速度非常慢,性能消耗大。但是一个模板生成器(Generator)如果是基于已有的模板文件进行配置填充,然后在copy到项目目录对应的位置,那会比直接读写磁盘效率更高。所以一般来说,模板生成器会采用第二种工作原理。
Yeoman-generator
模板生成器的脚手架有很多,前端领域每天都会有很多类似的轮子源源不断地从开源社区流出。这里我用来开发自己的generator的工具是Yeoman。Yeoman的Logo是一个戴着红帽子的大胡子,它是一个通用的脚手架搭建系统,可以创建任何的类型的app。同时它又是”语言无感知”的,支持创建任何类型开发语言的项目,Web, Java, Python, C# 等等。Yeoman的通用性在于,它本身不做任何决定,所有的操作都是通过Yeoman环境里面的各种generator实现的。通过自定义generator,我们可以创建任何格式的项目目录。这是Yeoman的最大魅力之处。另外,Yeoman通过提供promting这个方法实现输入式命令行交互,可以让用户自由填写配置信息,交互体验也非常棒。下面说说怎样基于Yeoman开发一个简单的generator:
Simple-dir
simple-dir是我自己捣鼓的一个很简单的Yeoman-generator,在这里我拿它来作为讲解示例,大家也可以打开详细代码来看,欢迎star,也欢迎提issue。
第一步,package.json
开发一个Yeoman-generator,我们要做的第一步就是配置package.json。有几个关键的地方,一个是,name的值的格式必须是”generator-“前缀 + Yeoman-generators官方源列表上的唯一值(如果你要共享你的generator到官方generator源的话);第二个就是,keywords属性必须包括”yeoman-generator”这个值;第三,files属性是命令自定义文件,app是默认的命令;第四,必须要安装最新版本的yeoman-generator依赖,可以直接运行:npm install –save yeoman-generator 获取最新的版本号。详细的package.json可以看下面这份:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
{ "name": "generator-simple-dir", "version": "0.0.1", "description": "A very simple template generator", "files": [ "generators/app", "generators/comp", "generators/page" ], "author": "橙乡果汁", "license": "MIT", "keywords": [ "yeoman-generator" ], "repository": { "type": "git", "url": "git@github.com:hugzh/generator-simple-dir.git" }, "bugs": { "url": "https://github.com/hugzh/generator-simple-dir/issues" }, "dependencies": { "glob": "^7.1.0", "mkdirp": "^0.5.1", "yeoman-generator": "^0.24.1" } } |
对应的src目录格式应该是这样的:
├───package.json └───generators/ ├───app/ │ └───index.js ├───comp/ │ └───index.js └───page/ └───index.js
你也可以直接把files属性直接写成:
1 2 3 4 5 |
"files": [ "app", "comp", "page" ] |
但是这样的话,你的代码根目录就必须直接包含app,comp和page文件夹。
第二步,拓展generator
这里我们有三个generator——app,comp和page。以page为例,我们来实现一个generator。
首先,需要继承Yeoman提供的generator基类:
1 2 |
var generators = require('yeoman-generator'); module.exports = generators.Base.extend(); |
然后我们就可以在基类内部重写generator的方法了。Yeoman提供了一系列的基类方法:
initializing – 初始化 (检查当前项目状态、获取配置文件内容等等) prompting – 获取用户输入,实现与用户的交互 (通过this.prompt()调用) configuring – 保存配置并配置整个项目 (比如创建 .editorconfig 文件和其他媒介文件) default – 当定义的方法没有匹配任何基类方法的时候用到 writing – 根据自定义的规则写入具体的generator文件 (routes, controllers, etc) conflicts – 内部冲突处理 install – 安装npm、bower等依赖的地方 end – 在最后调用, 实现cleanup, say good bye等功能。
在示例generator-simple-dir里,page这个generator的作用是创建页面,需要生成html/css/js文件。在generators.Base.extend函数内部,page实现了 initializing、prompting、writing、end这几个方法。对于prompting这样的异步方法,需要在交互结束的时候调用this.async()来结束异步任务。Yeoman实现用户交互的核心方法是prompting,它是一个异步的方法,并且返回一个promise。prompting方法通过一个数组参数,可以实现链式的用户输入。其中input类型的是用户输入自定义内容,confirm类型是作为True/False判断的prompt,输入Y/N。官方的示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
module.exports = generators.Base.extend({ prompting: function () { return 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?' }]).then(function (answers) { this.log('app name', answers.name); this.log |