本文先介绍一款通用脚手架(yeoman)的基本使用,然后再利用node.js来开发一个自己的脚手架工具。yeoman脚手架的本质也是通过node.js实现的

什么是脚手架

相信大家应该都用过脚手架,像create-react-app, vue-cli angular-cli等。应该也大概能知道脚手架具体是干嘛的,用来做什么。
脚手架本质就是用来快速生成我们所需要的项目结构,但他最重要的意义却在于,提供了统一的规范约定。有助于我们团队项目的开发

常用的脚手架工具

像现在比较流行得 create-react-app, vue-cli, angular-cli。这些都是为特定类型得项目使用得。市面上还有一些比较灵活得脚手架工具,如:yeoman, plop等,这些脚手架本质都是通过node.js来实现得工具。今天我们主要来看看yeoman

初识yeoman

yeoman是一款通用型得脚手架工具,它可以根据相应得模板来生成一套对应得项目结构。使用起来比较灵活。
yeoman与其他常用脚手架不同的是,他更像是一个脚手架运行平台,他提供一定的cli指令,然后搭配任意类型的generator模块,来生成任意类型的项目结构。也就是说他的本质是利用generator模板来生成项目结构的,而不同类型的generator模块,可以让我们生成不同类型的项目结构。而yeoman主要通过yo命令来调用这个generator模块从而实现创建我们所需的项目结构

generator模块

generator模块主要以 generator-(name) 这种形式定义,通过yo调用时,只需要调用后面的name即可,如:generator-sample 模块,我们只需要运行yo sample即可

那么yeoman是如何使用的,我们来看看。
1、首先,全局安装yeoman

yarn global add yo
或者
npm install yeoman -g

安装完成后,如果无法运行,那么请手动配置环境变量,配置完后,请手动重启vscode

ios脚手架 脚手架设计软件_工程化


ios脚手架 脚手架设计软件_ios脚手架_02


yeoman安装完成后,我们需要选用特定的generator模块来生成对应的项目结构,这里,我们随便选一个generator模块做示例: 如generator-node模块

2、首先,需要先安装此模块

yarn global add generator-node

3、安装完成后,直接使用yo node(使用时不需要加generator)来运行generator文件,生成项目结构目录

ios脚手架 脚手架设计软件_node.js_03

生成的目录结构如下:

ios脚手架 脚手架设计软件_yeoman_04


4、sub Generator(Generator模块下的子generator),有的generator模块存在子generator,但有的不存在子generator。当存在子generator时,可通过yo generator模块:子generator来执行。如generator-node下存在一个cli子generator。那么可以通过yo node:cli 来执行

ios脚手架 脚手架设计软件_node.js_05


ios脚手架 脚手架设计软件_ios脚手架_06

执行完后,新增了一个cli.js。说明这个子generator所做的事情就是新增一个这样的文件。

yeoman总结

yeoman的基本使用我们已经看完了。yeoman本质就是利用不同的generator模块去生成不同的项目结构。那么如果我们想自己生成一套自己的项目结构代码,那么我们是不是就要自己去写一套generator模块啊。没错,下面,我们来写一套自己的generator模块,来快速生成一套自己的项目模板

自定义generator模块

1、新建generator模块文件夹(generator模块必须以generator开头)

mkdir generator-myVue
cd generator-myVue

下面,我们先来看下一个generator模块的基本结构

ios脚手架 脚手架设计软件_ios脚手架_07


其实,app是默认的生成器目录,下面的index.js为主要的功能实现文件,如果需要定义其他子生成器时(sub generator),只需要在app同级新建一个新的文件夹就行。下面我们继续来晚上generator

2、初始化package.json文件

yarn init

3、新建生成器目录generators并且新建默认的生成器目录以及默认生成器实现文件

mkdir generators // 创建文件夹
cd generators // 进入文件夹
mkdir app // 创建文件夹
cd app // 进入文件夹
ls > index.js // 创建index.js文件
cd .. // 回到上级目录
cd ..

其实,index.js文件为我们Generator模块的核心入口文件
此文件需要导出一个继承自yeoman-generator的类
yeoman-generator提供了generator的一个基础类,这个基础类中提供了一系列的生成器函数,让我们自定义generator时更便捷

4、安装yeoman-generator,此模块可以提供一个generator的基础类

yarn add yeoman-generator

5、准备模板文件
在app文件夹下新建templates目录

cd generators
cd app
mkdir templates

将目标文件拷贝到templates目录下

6、在index.js中编写generator核心逻辑

/**
 * @description 此文件为Generator的核心入口
 * 需要导出一个继承自Yeoman Generator的类型
 * Yeman Generator在工作时会自动调用我们在此类型中定义的一些生命周期方法
 * 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
 */

const Generator = require('yeoman-generator')

module.exports = class extends Generator {
    prompting () {
        return this.prompt([
            {
                type: 'input',
                name: 'name',
                message: 'Your project name',
                default: 'app'
            }
        ])
        .then(answers => {
            this.answers = answers
        })
    },

    writing () {
        // templates数组中是每一个模板文件的路径
        const templates = [
            '.browserslistrc',
            '.editorconfig',
            '.eslintrc.js',
            '.gitignore',
            'babel.config.js',
            'package.json',
            'README.md',
            'vue.config.js',
            'yarn.lock',
            'public/favicon.ico',
            'public/index.html',
            'src/assets/logo.png',
            'src/components/loading/Loading.vue',
            'src/components/nodata/NoData.vue',
            'src/containers/Full.vue',
            'src/containers/header/Header.vue',
            'src/containers/sidebar/Sidebar.vue',
            'src/lang/en.js',
            'src/lang/index.js',
            'src/lang/zh.js',
            'src/router/index.js',
            'src/router/router.config.js',
            'src/store/modules/app.js',
            'src/store/modules/user.js',
            'src/getter.js',
            'src/index.js',
            'src/styles/variables.scss',
            'src/utils/axios.js',
            'src/utils/crypto.js',
            'src/utils/localStorage.js',
            'src/utils/utils.js',
            'src/views/actions/Action.vue',
            'src/views/dashboard/Create.vue',
            'src/views/instruments/index.vue',
            'src/views/shopFloor/ShopFloor.vue',
            'src/views/users/forgotPass.vue',
            'src/views/users/Login.vue',
            'src/views/404.vue',
            'src/App.vue',
            'src/main.js'
        ]
        
        templates.forEach(item => {
            // item是每一个文件的路径
            // copyTpl方法3个参数分别是模板路径,写入目标路径,命令行交互获取到额值
            this.fs.copyTpl(
                this.templatePath(item),
                this.destinationPath(item),
                this.answers
            )
        })
    }
}

7、修改templates目录下的模板文件中需要根据用户输入而变动的部分

ios脚手架 脚手架设计软件_yeoman_08


ios脚手架 脚手架设计软件_工程化_09


ios脚手架 脚手架设计软件_ios脚手架_10


8、将此generator模板关联到全局

yarn link

9、测试
新建一个文件夹,进入到该文件夹内 运行 yo myVue。看是否能在新建的文件夹中为什么新建一个新的项目结构

cd ..
mkdir test
cd test
yo myVue

以上就是yeoman的用法

之前我们也说过,yeoman的本质就是通过node.js来实现的一个工具。那么我们是不是也可以用node.js自己来实现一个脚手架呢。当然是可以的。下面我们就来看看该怎么自己实现一个简单的脚手架工具

实现自己的脚手架工具

目标:定义一个小型的脚手架工具,根据模板生成自己的项目目录

思路:
1、建立自己的脚手架目录 my-scaffolding

2、初始化package.json文件

3、在package.json中添加bin字段,指定cli应用入口文件(cli应用入口文件为实际实现我们脚手架功能的文件)

4、新建cli.js文件,并添加文件头 #!/usr/bin/env node
Node Cli应用文件必须要有这样的文件头
如果是Linux 或者 macOS系统下还需要修改此文件的读写权限为755
通过chmod 755 cli.js 实现修改

5、新增templates目录,并将模板添加到该目录下,以便到时候读取该目录下的文件

ios脚手架 脚手架设计软件_ios脚手架_11

6、开始写脚手架工具实现(开始编写cli.js),此时,需要明确我们需要做些什么

6.1、首先,毫无疑问,我们要先发起一个命令行交互去询问用户相关问题,此时我们需要用到inquirer模块来发起命令行交互
6.2、我们需要读取template模板中的文件内容,然后将模板文件的内容利用ejs模板引擎结合命令行交互得到的用户输入结果来渲染模板中的内容。这期间需要用到path模块来读取路径,以及ejs模板引擎来渲染模板
6.3、然后我们需要将渲染之后的内容写入到我们的目标目录中
6.4、此期间,读取template模板目录以及写入目标文件我们都需要用到node.js核心模块fs

7、明确了我们需要用到的模块后,我们需要先安装相应模块(inquirer, ejs),并引入相应模块(inquirer,ejs, path, fs)
path和fs是node.js内置模块,无需安装

const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')

8、开始写脚手架实现逻辑
8.1、inquirer发起命令行交互

const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')
// 发起命令行交互询问
inquirer.prompt([
  {
    type: 'input',
    name: 'name',
    message: 'Project name'
  }
]).then((answers) => { console.log(answers) } // 得到用户输入的结果

8.2、获取模板所在目录,以及需要写入的目标目录。并读取templates下的模板

// 获取模板目录
const tempDir = path.join(__dirname, 'templates')
// 文件输出目录
const outDir = process.cwd()
fs.readdir(tempDir, (err, files) => { // 读取模板目录,files是从目录中读取到的文件路径
  if (err) throw err
  files.forEach((file) => {

  })
})

8.3、将读取到的模板内容利用ejs进行渲染(结合命令行交互时用户的输入结果)

ejs.renderFile(path.join(tempDir, file), answers, (err, result) => {
  if (err) throw err
  // file 是files循环后得到的每个文件的路径
  // result是通过ejs解析后的每个文件的内容
})

8、4 将渲染后的结果写入到目标目录中
// 将结果写入到目标目录

ejs.renderFile(path.join(tempDir, file), answers, (err, result) => {
  if (err) throw err
  // file 是files循环后得到的每个文件的路径
  // result是通过ejs解析后的每个文件的内容
  
  // 将结果写入到目标目录
  fs.writeFileSync(path.join(outDir, file), result)
})

下面我们看一下完整代码

#!/usr/bin/env node

const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer')
const ejs = require('ejs')

// 利用inquirer模块发起命令行交互询问
inquirer.prompt([
  {
    type: 'input',
    name: 'name',
    message: 'Project name'
  }
]).then((answers) => {
  // 获取模板目录
  const tempDir = path.join(__dirname, 'templates')

  // 文件输出目录
  const outDir = process.cwd()

  // 将模板目录下的模板转换为目标文件
  fs.readdir(tempDir, (err, files) => {
    if (err) throw err
    files.forEach((file) => {
      // 利用模板引擎渲染文件
      ejs.renderFile(path.join(tempDir, file), answers, (err, result) => {
        if (err) throw err
        // file 是files循环后得到的每个文件的路径
  		// result是通过ejs解析后的每个文件的内容
  
        // 将结果写入到目标目录
        fs.writeFileSync(path.join(outDir, file), result)
      })
    })
  })
})

9、实现逻辑写完后,我们需要通过yarn link 或者 npm link将其关联到全局(关联到全局后,可以在任何地方去使用此脚手架)

10、测试脚手架情况

mkdir test

cd test

执行命令 my-scaffolding

查看test目录下是否有生成我们需要的项目结构

ios脚手架 脚手架设计软件_ios脚手架_12


说明此脚手架已经搭建完成,并可正常使用了

提交npm

正所谓做事做全,现在我们来将我们做好得脚手架提交到npm上,以后后续可以直接通过npm 或者yarn下载后使用(yarn 和 npm)包管理库是共享得,所以用npm或者yarn提交都行

1、准备工作:

要提交一个模块到npm上,首先,我们必须将这个模块上传至开源库中,如 github

如何提交github这个我就不多说了 相信用过git得都会

ios脚手架 脚手架设计软件_yeoman_13


我们这里已经将该模块提交到github上了,提交完成后

直接在本地仓库中打开gitbash

2、输入npm login --registry=https://registry.npmjs.org
此命令是用于登录npm
故前提,需要大家先到npm官网上去注册一个账号,且必须进行邮箱验证,如果没有进行邮箱验证,也是提交不上去得,会报错

403 403 Forbidden - PUT https://registry.npmjs.org/my-scaffolding - Forbidden

输入npm login --registry=https://registry.npmjs.org 后
会让你输入npm的用户名密码,输入完成后,登录成功

3、直接npm publish 或者 yarn publish

有时会提示你输入一些版本之类的,输入完后就提交完成了(首次提交不需要输入)

ios脚手架 脚手架设计软件_node.js_14

4、测试
此时,我们再重新通过yarn或npm下载这个包试试

mkdir test // 新建一个新的文件夹
cd test // 进入文件夹
// 打开命令行工具
yarn add my-scaffolding

ios脚手架 脚手架设计软件_node.js_15


安装完成后

ios脚手架 脚手架设计软件_工程化_16


此时,再执行命令下载的包的命令 my-scaffolding,即可生成我们的模板

ios脚手架 脚手架设计软件_工程化_17


ios脚手架 脚手架设计软件_ios脚手架_18


我们之前自己写的generator-myVue 模块,也可以以同样的方式发布到npm中。

这个我们就不继续演示了,大家可以自己试试

总结

今天主要介绍了通用脚手架yeoman的使用,相信大家看完应该知道他的相关用法了,
yeoman主要是利用不同generator模块来完成他生成不同项目结构的功能。故如果想实现自己的脚手架时,可以自己写一个generator模块,然后用yeoman去使用自己写的这个generator模块即可
另外介绍了一下如何使用node.js去写自己的脚手架工具,大家也可以不使用yeoman,而使用node.js自己去写一个自己的脚手架,从而去实现生成自己想要的项目结构