0%

前端脚手架工具构建

脚手架在建筑学中用于保障施工时能稳定进行的平台,建造者会在施工前根据不同的建筑物搭建不同的脚手架以供施工时的重复使用。

前端工程中的脚手架也是一样的道理,很多前端项目都有一些基础文件或通用模块作为项目运行的基本结构和运行工具,前端脚手架能为我们快速开启一个前端工程而准备好基本的目录结构和基础文件

像常见的Vue-cliReact-cli就是专门针对各自框架使用的脚手架工具。本文就另一款通用的脚手架工具yeoman进行简单的使用说明。

Yeoman的简单使用

简介

官网定位的yeoman为用于现代WEB应用程序的WEB脚手架工具。yeoman在使用时需要选择不同的生成器模块generator进行不同项目的脚手架搭建,比如官网提供了可用于AngularBackboneReactPolymer和超过5600多个其他项目的脚手架生成器模块。

当然我们也可以利用官方提供的api定义自己的脚手架生成器。

Yeoman与语言无关。它可以使用任何语言(Web,Java,Python,C#等)生成项目。

Yeoman本身不做任何决定。每个决定都是由生成器决定的,生成器基本上是Yeoman环境中的插件。有很多公开可用的生成器,它很容易创建新的生成器以匹配任何工作流程。约曼始终是满足您脚手架需求的正确选择。

脚手架的搭建的一般过程为:

  1. 明确你的项目需求:需要什么样的脚手架
  2. 找到合适的generator
  3. 全局安装需要的generator
  4. 通过yo模块运行使用的generator
  5. 填写generator安装时需要的信息
  6. 生成项目基本结构

简单使用

更具上面的脚手架搭建过程,我们一步一步来。

  1. 全局安装yeoman模块包yo

    1
    npm insatll yo -g
  2. 全局安装需要的生成器模块:比如node环境通用的生成器generator-node

    1
    npm install generator-node -g
  3. 在需要创建脚手架的目录执行安装命令:(注:省略前缀generator-)

    1
    yo node
  4. 安装的过程中生成器可能需要读取用户的一些基本信息:根据你的需求来填写即可

  5. 生成对应的项目目录结构:

这样我们就可以在这个基础文件结构下快速开始我们的项目啦。

更多细节可参考官方使用文档

sub-generator的使用

有时候使用到的生成器无法满足我们的需求,这个时候还可以使用该生成器提供的子生成器sub-generator进行特定的文件或模块补充。该生成器具体支持哪些子生成器需要查看官方说明,比如generator-node支持的子生成器有:

  • node:boilerplate
  • node:cli
  • node:editorconfig
  • node:eslint
  • node:git
  • node:readme

比如我们需要把刚才的node项目编程cli项目,就可以安装node:cli子生成器来构建一些基本文件:

1
yo node:cli

自定义generator

如果官方提供的生成器无法满足项目的需要,我们就可以自定义一个专门为自己项目服务的生成器对象。

生成器的本质就是一个node-module

  1. 命名:首先,为你即将写的 generator 创建一个文件夹。这个文件夹必须命名为 generator-name (这里的 name 就是你 generator 的名字)。这是很重要的,作为 Yeoman 的依赖文件系统来查找可用的 generators。比如,我们创建一个名为generator-sample的生成器。

  2. 初始化:进入创建好的生成器文件目录,进行npm初始化

    1
    npm init -y

    你应该确保你设置了最新版本的 yeoman-generator 作为一个依赖。你可以去运行:npm install --save yeoman-generatorpackage.json文件的“file” 属性必须是由你的 generator 使用的文件排列和目录。根据需要加入其他的 package.json 属性

  3. 文件结构:在一个示例项目中,目录树可能看起来像这样:

    1
    2
    3
    4
    5
    ├───package.json
    ├───app/
    │ └───index.js
    └───router/
    └───index.js

    当你使用 yo name 为这个 app 的 generator时,这个默认的 generator 将会被使用。这必须包含在 app/ 目录中。

    当你使用 yo name:subcommand 时 Sub-generators 将被使用,存储在文件夹中,名字完全像一个子命令。

    如果不喜欢把文件都放在根目录,也可以这样:

    1
    2
    3
    4
    5
    6
    ├───package.json
    └───generators/
    ├───app/
    │ └───index.js
    └───router/
    └───index.js

    file属性需要做对应调整:(目前版本4.12.0不写也能自动读取到)

    1
    2
    3
    4
    5
    6
    {
    "files": [
    "generators/app",
    "generators/router"
    ]
    }
  4. 编写生成器逻辑代码。当文件目录结构确定下来以后,你就可以编写生成器需要实现的功能代码,yeoman提供了一个基本的generator,完成了大部分基本的通用任务,你可在此基础上扩展自己的需求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const generators = require('yeoman-generator'); // yeoman提供的生成器基类:能帮我们完成大部分基本工作
    module.exports = generators.Base.extend({
    method1: function(){
    console.log('method1 just ran.');
    },
    method2: function(){
    console.log('method2 just ran.');
    }
    });

    或者使用一个类进行继承:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const generators = require('yeoman-generator');

    module.exports = class extends generators {
    // yeoman-generator 在生成文件阶段会自动调用,这里尝试写入一些东西
    writing() {
    this.fs.write(
    this.destinationPath('readme.txt'),
    'this is a generator-sample demo'
    );
    }
    }

  5. 运行:当导出了一个基本的模块,其实最基本的一个生成器已经实现了,现在我们来运行(使用)我们自己定义的生成器模块:

    首先使用npm link将该模块链接到全局,以便能直接使用。

    然后在使用目录下执行:yo sample即可。如果,控制台打印了我们扩展的两个函数的信息,说明执行成功了。

更多使用细节请参考官方文档

自定义生成器:创建模板文件

有时候我们需要生成多个文件,或者需要根据一些变量来动态创建文件,这个时候就需要使用到模板文件,generator里的模板文件使用的是ejs的模板文件语法。模板文件默认放到./templates目录下。

模板上下文默认被定义为./templates/。你可以使用generator.sourceRoot('new/template/path')去重写这个默认值。

首先,我们随便创建一个模板文件,如readme.txt:

1
2
3
4
5
6
7
这是一个模板文件
内部可以使用EJS模板标记语法:
<%= title %>
或者使用EJS语法:
<% if(success) { %>
成功输出!
<% } %>

然后我们就可以使用模板文件来动态创建文件了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const generators = require('yeoman-generator');

module.exports = class extends generators {
writing() {
// 模板文件路径
const temp = this.templatePath('readme.txt');
// 输出路径
const output = this.destinationPath('readme.txt');
// 模板上下文数据
const data = {
title: 'follow your heart.',
success: true
}
// 复制并渲染模板文件
this.fs.copyTpl(temp, output, data);
}
}

自定义生成器:与用户交互

在上面的模板中,一般需要动态太如的都是来自用户输入的数据,所以有必要接受用户输入数据来动态创建文件。

要提示用户输入并获取数据,我们需要实现prompting接口:

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
28
29
30
const generators = require('yeoman-generator');

module.exports = class extends generators {
prompting(){
// 定义用户输入数据的问题和默认值
return this.prompt([{
type: 'input',
name: 'name',
message : 'Your project name',
default : this.appname // Default to current folder name
}]).then(answers => {
this.answers = answers; // 用户输入数据的键值对
});
}

writing() {
// 模板文件路径
const temp = this.templatePath('readme.txt');
// 输出路径
const output = this.destinationPath('readme.txt');
// 模板上下文数据
const answers = this.answers; // 使用用户输入数据
const data = {
title: answers['name'],
success: true
}
// 复制并渲染模板文件
this.fs.copyTpl(temp, output, data);
}
}

发布自定义generator

为了方便后续更方便地使用我们创建好的generator,可以将生成器模块公开发布到npm上。这里的发布流程与普通的npm模块发布无异。

  1. 创建本地git仓库,并将代码推送到github远程仓库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #忽略node_modules文件夹
    echo node_modules > .gitignore
    #git初始化
    git init
    #提交本地记录
    git add.
    git commit -m "generator init"
    #在github上创建仓库并完成绑定
    git remote add origin https://github.com/fongzhizhi/generator-sample.git
    #提交到master分支
    git push -u origin master
  2. 将模块发布到npm

    1
    2
    #如果你的npm仓库绑定了淘宝镜像等仓库地址,需要在发布时指定官方仓库
    npm publish --registry=ttps://registry.npmjs.org/
    1
    2
    3
    4
    5
    6
       
    3. 然后就能在npm官网搜索到你发布的模块了

    ```shell
    #这里为了测试,所以我们不是真正发布一个模块,测试成功后移除即可
    npm unpublish

Plop的简单使用

一款小而美的脚手架工具,github地址。我们可以使用Plop通过模板文件为我们批量生成文件,就像yeoman那样。

脚手架的工作原理

脚手架本质上就是一个创建文件的node-cli应用,至于需要创建什么样的文件,就取决于我们的项目性质是什么,通过脚手架创建的基础文件就能快速开始我们的项目,而无需重复性地创建文件或导入一些通过模块。

首先,我们新建一个文件夹,创建一个node模块:

1
2
mkdir scaffloding-sample
npm init -y

作为Node CLI应用,我们使用cli.js作为模块执行的入口文件:

1
2
3
4
#!/usr/bin/env node

// Node CLI 应用入口文件必须要有这样的文件头
// 在Linux 或 macOS系统中还需要将此文件的读写权限修改为755 : chmod 755 cli.js

然后,修改package.json文件的bin字段为cli.js

现在,我们就来编写cli.js逻辑了,一般来说,脚手架最基本的两个功能:

  • 用户交互:我们使用inquirer模块进行实现。
  • 生成模板文件:我们使用ejs模板引擎进行渲染。

现在我们随便创建一些模板文件,比如templates/index.htmltemplates/main.css

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title><%= name %></title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' type='text/css' media='screen' href='./main.css'>
</head>
<body>
<h2><%= name %></h2>
</body>
</html>
1
2
3
4
body {
padding: 0;
background: #cecece;
}

这里主要是为了测试,就不写太多东西。

那么cli.js的逻辑就可以写成这样:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env node

// Node CLI 应用入口文件必须要有这样的文件头
// 在Linux 或 macOS系统中还需要将此文件的读写权限修改为755 : chmod 755 cli.js

/**
* [脚手架的一般功能]
* 1.用户交互:根据使用者的信息来生成不同的文件配置 => inquire模块来实现
* 2.使用模板引擎生成模板文件
*/
const fs = require('fs');
const path = require('path');
const inuqire = require('inquirer'); // 用户交互模块
const ejs = require('ejs');
const { connect } = require('http2');

inuqire.prompt([{
type: 'input',
name: 'name',
message: 'the project name:',
default: 'my-project',
}]).then(anwser => {
// 根据用户信息生成文件
// 模板目录
const tempDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd();
// 读取模板文件目录下的所有文件
fs.readdir(tempDir, (err, files) => {
if (err) throw err;
files.forEach(file => {
// 使用模板引擎读取文件
ejs.renderFile(path.join(tempDir, file), anwser, (err, content) => {
if(err) throw err;
// 写入文件
fs.writeFileSync(path.join(destDir, file), content);
})
});
});
});

现在,我们使用npm link将模块链接到全局,就可以通过命令行使用这个cli模块了:

1
scaffloding-sample
谢谢你请我吃糖!