脚手架工具
常用脚手架工具
- React项目create-react-app
- Vue项目vue-cli
- Angular项目angular-cli
- Yeoman通用型脚手架工具
- Plop创建一个组件/模块所需要的文件(类似于Yeoman的sub-generator)
脚手架工具的工作原理
脚手架的重要原理就是解析命令行输入的参数(主要解析command 和 options),根据参数来运行node.js代码。解析命令行参数常用的npm包有yargs和commander。
使用node.js来开发一个小型脚手架。
- 创建sample-scaffolding文件夹,使用
npm init
初始化 - 在package.json中添加
”bin“: "cli.js"
用于指定该脚手架的命令行入口文件为cli.js。关于Node命令行参考阮一峰老师的文章"bin": { "sample-scaffolding": "cli.js" }
表示sample-scaffolding是在终端输入的命令,该命令的真实入口文件是当前目录下的cli.js。"bin"字段用来指示命令名与可执行脚本文件的映射关系。当一个npm包的package.json中包含了bin字段时,在安装该包以后,会在项目的node_modules的.bin文件夹下自动生成一个软连接,指向bin字段的可执行脚本(全局安装的情况下,是在全局的node_modules目录下)。
- 创建模板:在项目根路径下创建templates文件夹来存储模板文件,如这里创建了templates/index.html和templates/style.css。
- cli.js的内容:
- Node CLI应用入口文件必须要有
#!/usr/bin/env node
文件头,用于指定使用node解释器来执行该脚本文件,其中user/bin/env
用来在不同主机和系统下找到node可执行文件的位置。 - 如果是Linux或者MacOS系统,则需要修改文件的权限为755(文件拥有者可读可写可执行)
chmod 755 cli.js
- 业务代码
- 获取命令行参数可以使用
process.argv
,使用yargs包可以更加方便简洁npm install yargs
- 通过命令行交互询问用户问题,安装
npm install inquirer
包 - 根据用户回答的结果生成文件,需要安装模板引擎
npm install ejs
来解析模板文件
- 获取命令行参数可以使用
- Node CLI应用入口文件必须要有
// cli.js
const inquirer = require('inquirer');
const ejs = require('ejs');
inquirer.propmt([
{
type: 'input',
name: 'name',
message: 'Project name?'
}
])
.then(answers => {
// 根据用户回答的结果answers结合模板文件,生成新文件
// 模板目录
const tmplDir = path.join(__dirname, 'templates');
// 目标目录, 这里是程序执行的当前路径
const destDir = process.cwd();
// 将模板下的模板文件全部输出到目标目录
fs.readdir(tmplDir, (err, files) => {
if(err) {
throw err;
}
files.forEach(file => {
// 通过模板引擎渲染模板文件,将结果写入新的文件中。
ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
if(err) throw err;
// 将结果写入文件
fs.writeFileSync(path.join(destDir, file), result);
})
})
})
})
- 使用
npm link
全局注册 - 在命令行中使用
sample-scaffolding
命令执行
Yeoman
基本使用
- 全局安装yeoman:
npm install yo -g
- 全局安装generator:安装项目对应的generator包,让yeoman运行generator包来创建项目基本结构。yeoman + generator包 = 脚手架工具。比如安装generator-node来创建一个node项目:
npm install generator-node -g
- 运行脚手架工具:
yo node
,注意要去掉前缀generator
sub-generator
在已有项目中再创建一些项目文件(结构),就是在generator创建的项目中继续generator一些文件,就是sub-generator。
- 使用方式(以上面的node项目为例):
yo node: cli
- 语法:
yo <generator-name>: <sub-generator-name>
- 注意:并非所有的generator包都包含sub-generator(根据generator文档查看),有的时候需要我们自己去定义sub-generator
npm安装二进制文件时的镜像配置
有时候npm安装二进制包时,通过对应包的镜像配置来加速下载。
创建自定义generator
- Generator本质上就是一个NPM模块
- Generator包含两个组成部分
- 组装指令
- 模板文件
- Generator需要特定的文件结构
- app/index.js为生成器组装指令文件
- sub-generator则在该Generator目录内与app目录同级,例如这里的component目录
- generator的名称要求是有generator-前缀
generator-<name>
,否则yeoman是无法找到的。
创建generator
- 新建文件夹作为自定义generator的项目根路径,文件夹命名为generator-<name>
- 使用
npm init
初始化package.json文件,注意需要手动更改一些信息。
- 安装最新版的yeoman-generator作为依赖
npm install yeoman-generator -S
- 在项目文件夹中,创建generator/app/index.js文件,作为generator的核心入口。
- index.js需要导出一个继承自Yeoman Generator的类型
- Yeoman Generator会在工作时自动调用此类型中定义的一些生命周期方法
- 在这些生命周期方法中,通过调用父类的API实现功能,如文件写入等
- 完成generator后,在项目根路径使用
npm link
命令将generator包链接到本地的全局npm包所在地,这样就相当于使用了全局安装。
使用自定义generator
- 新建文件夹作为项目根路径
- 使用
yo <name>
来使用自定义的generator作为脚手架工具来生成项目结构。
使用模板文件来创建自定义generator
- 在app/目录下创建一个templates/目录,用于存放模板文件
- 模板文件使用的是EJS模板引擎的语法规范
- 同时,在index.js中需要使用根据模板方式创建项目的API方法
接收用户输入的数据
有很多时候需要根据用户输入的信息,来决定如何生成项目的结构。比如模板文件中的动态数据、是否安装某项依赖等等
命令行询问
在index.js中,导出的类里添加prompting方法,yeoman在询问用户的阶段会自动调用此方法,在prompting方法中可以调用父类的prompt方法来发出对用户的命令行询问。
module.exports = class extends Generator {
prompting() {
// 返回一个Promise,yeoman可以使用异步流程
// this.prompt()接收一个数组,数组元素为表示要提出的问题对象
//
return this.prompt([
// 以下这个示例表示的是请用户输入项目名称,默认是该项目所在文件夹名称
{
type: 'input', // 问题类型,这里表示使用用户输入的方式提交信息
name: 'name', // 类似于web表单的name属性,也就是结果的键值对中的键
message: 'your project name', // 该问题的描述
default: this.appname // 该问题的默认值,我们这里使用了this.appname表示为项目生成的目录名称
}
])
// answers为当前问题的用户回答
.then(answers => {
// 将用户给与的回答挂载到answers属性上,以便于之后使用该信息。
this.answers = answers;
})
}
}
创建自定义的Vue脚手架
Plop:小而美的脚手架工具
创建特定类型文件的脚手架工具,有点类似yeoman sub-generator。一般不独立使用,而是集成在项目中,自动化地创建同类型文件。
使用
- 安装:
npm install plop -D
- 项目根目录下创建plopfile.js文件作为Plop的入口文件
- 需要导出一个函数
- 该函数接受一个Plop对象,用于创建生成器任务
- 项目根目录下创建plop-templates目录,用于存放Plop生成文件时的模板文件
- 模板文件遵循handlebars的模板语法
- 在命令行中启动Plop:
npx plop <task-name>
,这里的<task-name>就是在plopfile.js中定义的生成器任务名字。启动的方式与gulp在命令行启动任务是一致的。
// plopfile.js
module.exports = plop => {
// 创建生成器任务
// setGenerator第一个参数为生成器名字,第二个参数为配置选项
plop.setGenerator('component', {
// 对于任务的一个描述
description: 'create a component',
// 对于该任务,在命令行中的交互问题
prompts: [
{
type: 'input',
name: 'name',
message: 'component name',
default: 'MyComponent'
}
],
// 完成命令行交互后生成器的动作,多个文件多个模板如下
actions: [
{
type: 'add', // 表示添加新的文件
// 表示添加文件的路径,可以使用{{}}的插值语法,下面的name就是命令行获得的name值
path: 'src/components/{{name}}/{{name}}.js',
// 指定模板文件的路径
templateFile: 'plop-templates/component.hbs'
},
{
type: 'add', // 表示添加新的文件
// 表示添加文件的路径,可以使用{{}}的插值语法,下面的name就是命令行获得的name值
path: 'src/components/{{name}}/{{name}}.css',
// 指定模板文件的路径
templateFile: 'plop-templates/component.css.hbs'
},
{
type: 'add', // 表示添加新的文件
// 表示添加文件的路径,可以使用{{}}的插值语法,下面的name就是命令行获得的name值
path: 'src/components/{{name}}/{{name}}.test.j.s',
// 指定模板文件的路径
templateFile: 'plop-templates/component.test.hbs'
}
]
})
}