【学习笔记】Part2·前端工程化实战--开发脚手架及封装自动化构建工作流(二、脚手架工具)

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(一、工程化概述)

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(二、脚手架工具)

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(三、自动化构建 – 主Grunt)

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(三、自动化构建 – 主Gulp)

脚手架工具概要

在对前端工程化的整体有了初步的认识之后,顺着一个项目的开发过程,先从脚手架开始去探讨前端工程化在创建环节中的表现。

脚手架的本质作用:创建项目基础结构、提供项目规范和约定

通常开发相同类型的项目时都会有一些相同的地方:

  • 相同的组织结构
  • 相同的开发范式
  • 相同的模块依赖
  • 相同的工具配置
  • 相同的基础代码

这样一来呢,就会出现在搭建新项目时有大量的重复工作来做,脚手架工具就是来解决这样的一个问题的。

我们可以使用脚手架工具快速搭建属于特定类型的项目骨架,然后基于这个骨架进行后续的开发工作。

比如一些 IDE 创建项目的过程实际上就是一个脚手架工作的流程。

而前端项目创建过程当中,由于前端计数选型比较多样,又没有一个统一的标准,所以前端方向的脚手架一般不会集成到某一个 IDE 当中,都是以一个独立的工具存在,而且相对会复杂一些,但是本质上脚手架的目标都是一样的。它们都是为了解决我们在创建项目过程中那些复杂的工作。

接下来:
在这里插入图片描述

常用的脚手架工具

目前市面上有很多成熟的脚手架工具,但是大都是为了特定的项目类型服务的,例如:React 项目中的 create-react-app ,Vue.js 项目中的 vue-cli,Angular 项目中的 angular-cli 等等

它们的实现方式都大同小异,无外乎:根据提供的信息创建对应的项目基础结构。不过它们一般只适用于它们自身所服务的那个框架的项目。

还有一类是以 Yeoman 这样的工具为代表的通用型项目脚手架工具,它们可以根据一套模板,生成一套对应的项目结构,这种类型的脚手架一般都很灵活,而且容易扩展。

除了以上这种在创建项目时很有用,还有一种脚手架也非常有用,代表性的是 Plop,它们用来在项目开发过程当中用于创建一些特定类型的文件。例如创建一个 组件 / 模块所需的文件 ,这些模块和组件一般是由特定的几个文件组成的,而且有特定的代码结构,相对于手动一个个去创建的话,脚手架会提供更为便捷更为稳定的一种操作方式。

这里重点关注几个有代表性的工具去做探究。

Yeoman 简介

时至当下,react.js vue.js angular.js 大行其道,这些框架的官方都提供了更为集成的脚手架工具链,所以说大家在谈论到脚手架工具的时候往往最先想到的都是像 angular-cli vue-cli 这样的工具,对于这一类的工具,因为它过于针对某一个框架,而且在使用上也非常的普及, 这里不做过多的介绍,这里着重的探讨 Yeoman 这样一款工具,因为它作为最老牌,最强大,最通用的脚手架工具,它有更多值得我们借鉴和学习的地方。

Yeoman 官方的定义是它是一款用于创造现代化 web 应用的脚手架工具。

不同于 vue-cli 这样的工具,Yeoman 更像是一个脚手架的运行平台,我们可以通过 yeoman 搭配不同的 Generator 去创建任何类型的项目。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
也就是说我们可以通过创建自己的 generator 从而去定制属于我们自己的前端脚手架。

yeoman 的优点同样也是它的缺点,在很多专注基于框架开发的人眼中,yeoman 过于通用,不够专注。所以他们更愿意使用类似于 vue-cli 这样的脚手架。这也是像 vue-cli 这样的工具它为什么现在变得这么成功。但是这并不妨碍我们学习 Yeoman 。

Yeoman 基础使用

我们之前都知道了 Yeoman 是基于 nodejs 开发的一款工具模块,使用它的第一步自然是通过 npm 在全局范围去安装它。

使用 npm 的前提是正常的在机器上安装 node 环境。
在这里插入图片描述
这里使用 yarn 代替 npm 来做后续的 npm 操作
在这里插入图片描述
先安装一下 yeoman ,它的模块名字叫 yo

yarn global add yo

在这里插入图片描述
yo 安装之后我们就可以使用 yeoman 去创建我们的项目了。

但是通过之前的介绍我们应该知道,单有 yo 是不够的,因为它是搭配不同的 generator 才能去使用,我们要想使用 yeoman 创建项目的话就必须要找到对应的 generator 去使用。

例如我们想生成一个 node module 的项目,也就是一个 node 的模块,那我们可以使用 generator-node 的这样一个模块。

使用方式也是先全局安装的方式安装到我们的本地:

yarn global add generator-node

在这里插入图片描述
这里安装报了这样一个错误:An unexpected error occurred: "https://registry.npm.taobao.org/tslib/download …

这是因为之前我们安装的模块都是从淘宝镜像安装的,在这里不可用,所以我们先还原一下再重新安装 generator-node:

yarn config set registry https://resgistry.npmjs.org --global

在这里插入图片描述
有了这样两款本地安装的模块之后,我们就可以使用 yarn 去运行我们刚刚安装的 generator-node 的生成器(generator),自动的帮我们创建要给全新的 Node module 。

先定位到我们想创建项目的目录并创建一个新的文件夹 叫 my-module。这个 my-module 就作为一个我们新项目的根目录。
在这里插入图片描述
在这个目录下面,我们可以通过 yeoman 提供的 yo 命令去运行我们刚刚所安装的 generator-node 的生成器,运行特定的 generator 就是把 generator-node 这样的包的 generator- 前缀去掉

yo node

我们这里提示:无法将“yo”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

这里是用 yarn 安装的,我们来配置一下 yarn 的 bin 目录放到环境变量里边,我的目录是:

C:\Users\10145\AppData\Local\Yarn\bin

看你们自己的是哪儿,自己配置一下就行,重新运行:yo node
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,这就是自动生成的项目目录,该脚手架的整个工作流程工作就结束了

脚手架的目的呢就是让我们得到这样一个项目的基础结构以及一些基础的项目代码

总结一下:

  1. 在全局安装 yo :
npm install yo --global  # or yarn global add yo
  1. 安装对应的 generator
npm install generator-node --global # or yarn global add generator-node
  1. 通过 yo 运行 generator
cd path/to/project-dir
mkdir my-module
yo node
Sub Generator

有时候我们并不需要创建完整的项目结构,可能我们只是需要在已有的项目之上去创建一些特定类型的文件,例如我们给一个已经存在的项目创建 README.md ,又或者我们在原有的项目之上去添加某些特定类型的文件 比如 eslint 或者 babel 的配置文件 这些文件呢一般都有一些基础的代码,自己手动去写的话很容易配置错误,我们可以通过生成器自动生成,这样会提高效率。

如果你需要这样的一些需求的话可以通过使用 Yeoman 所提供的 SubGenerator 这样的特性来实现,具体是在项目目录下运行特定的 subgenerator 的命令生成对应的文件,这里还是以 generator-node 为例:

这里可以使用 generator-node 所提供的一个子级的生成器叫做 CLI 的生成器去帮我们生成一个 cli 应用所需要的文件,让我们的模块变成一个 cli 应用。

运行 sub-generator 的方式就是原有的 generator 后面冒号跟上 sub-generator 的名字:

yo node:cli

在这里插入图片描述
这里会提示是否覆盖 package.json 因为我们去添加 cli 支持的时候会添加一些新的模块和配置,这里选择 yes
在这里插入图片描述
在 package.json 最下方出现了新的配置和依赖,都是在 cli 应用当中所需要的
在这里插入图片描述
并且在 cli 里边出现了一些基础代码,有了这些呢,我们就可以将我们的模块作为一个全局的命令行模块去使用了。

yarn 运行安装一下相关依赖,再 link 映射到全局范围

yarn

yarn link

在这里插入图片描述
看看 link 之后的结果
在这里插入图片描述
从图上可以看出,cli 应用可以正常工作了。

这就是 generator 子级的 generator 的特性,需要注意的是并不是每一个 generator 都提供生成器,所以我们在使用之前,需要通过你使用的 generator 官方文档,来去明确当前 generator 下面有没有一个子级的 generator ,例如 这里使用的 generator-node
在这里插入图片描述
它就提供了这样一些 sub generators

Yeoman 使用步骤总结

yeoman 是一款通用型脚手架工具,所以说我们几乎可以用它去创建任何类型的项目,这里总结一下使用 yeoman 一般遵循几个步骤:

  1. 明确需求
  2. 找到合适的 generator
  3. 全局范围安装找到的 generator
  4. 通过 yo 运行对应的 generator
  5. 通过命令行交互填写选项
  6. 生成你所需要的项目结构

在这里插入图片描述
我这儿本地加载了好多次,加载不出来,看来国外的站点还需要搭梯子啊,先不看了

例如我门要去创建一个网页应用,我们首先应该通过 yeoman 的官网找到对应的 generator ,这里所使用的 generator 叫 webapp
在这里插入图片描述
首先通过命令行安装一下:

yarn global add generator-webapp

在这里插入图片描述
然后通过 yo 命令运行这个模块

yo webapp

在这里插入图片描述
需要注意的是在这个模块当中会依赖一些 C++ 的模块,这些模块需要在安装的过程当中去下载一些二进制的文件,而这些文件并不能通过 npm 镜像去加速,所以速度相对慢一些。

如果说你对镜像加速有一些心得的话,应该知道是可以通过配置对应的镜像去提高这些二进制下载的速度,比如 node-sass 可以通过淘宝镜像源做一些加速,这里呈现以下加速的配置:
在这里插入图片描述
在这里插入图片描述
最终,我们就可以在项目的根目录下得到一个 web 应用的基础结构。

自定义 Generator

通过前面对 yeoman 的使用介绍,我们发现,不同的 generator 用来生成不同的项目,也就是说我们可以通过创造自己的 generator 来帮我们生成自定义的项目结构,即便是市面上已经有了很多的 generator ,我们还是有创建自己的 generator 的必要,因为市面上的 generator 都是通用的,而我们在开发过程当中呢,会出现一部分基础代码甚至业务代码,在相同的类型项目内,还是重复的,我们这时候就可以把公共的部分都放到脚手架当中去生成,让脚手架工具发挥更大的价值。

例如我们创建 vuejs 的时候,官方默认只会创建一些最基础的模块,但是并不包含我们经常用到的模块,例如:axios vue-router vuex 等等,咱们需要创建完项目过后再去手动的引入这些模块,并且去编写一些基础的使用代码。

试想一下,如果我们把这些也放到脚手架当中,就不会存在这样的问题了。

接下来我们就去自定义一个带有一定基础代码的 vuejs 项目脚手架。

创建 Generator 模块

创建 generator 实质上就是创建一个 npm 模块,但是 generator 有特定结构。
在这里插入图片描述
除了特定的结构,还有一个与普通 npm 模块的区别就是 yeoman 的 generator 名称必须是 generator-<name> 这种格式,如果说在具体开发的时候没有使用这样一个格式的名字,那 yeoman 在后续工作的时候就没有办法找到你所提供的这个生成器模块。

接下来做一个具体的演示:
首先创建一个 generator-sample 的文件夹作为生成器模块的目录:

mkdir generator-sample

然后进入目录,在下方通过 yarn init 的方式初始化一个 package.json 文件

再然后去安装一个 yeoman-generator 的模块,这个模块提供了我们生成器的一个基类,这个基类当中提供了一些工具函数,让我们去创建生成器的时候更加便捷

yarn add yeoman-generator

安装过后我们可以使用编辑器打开当前项目,然后按照 generator 的目录结构去创建对应的 文件/文件夹
使用 VSCode 打开当前文件夹代码的命令:code .
在这里插入图片描述

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

// 首先载入 yeoman-generator
const Generator = require('yeoman-generator')

// 导出一个类型,让这个类型继承自 Generator
module.exports = class extends Generator{
    
    
    // 在文件当中定义一个 writing 方法,Yeoman 自动生成文件阶段自动调用此方法
    writing () {
    
    
        // 这里尝试往项目目录中写入文件,这儿通过父类当中的 fs 模块去写入文件到我们的生成目录
        // 需要注意的是这里的 fs 模块,与 node 的 fs 模块不同,这个是一个高度封装的 fs 模块,相对于原生的 fs 模块,功能更加强大一些
        // 两个参数 一个 filepath 一个 contents 路径和内容
        this.fs.write(
            this.destinationPath('temp.txt'),
            Math.random().toString()
        )
    }
}

简单的 generator 就完成了,回到命令行,通过 link 的方式,把这个模块链接到全局范围,使之成为一个全局模块包,这样的话 yeoman 在工作的时候就能找到我们自己写的 generator-sample

yarn link

在这里插入图片描述
准备就绪,可以通过 yeoman 去运行这样一个生成器,具体运行方式使通过 yo sample 去运行
在这里插入图片描述
在这里插入图片描述
这就是我们一个最基本的生成器的一个开发过程

根据模板创建文件

很多时候我们需要自动去创建的文件有很多,而且文件的内容相对复杂,像这样的一种情况下,我们就可以使用模板去创建文件,因为这样可以更加便捷一些,具体来看就是:

  • 首先先在生成器目录下添加一个 templates 目录
  • 将需要生成的文件都放入 templates 目录下
    在这里插入图片描述
    在这里插入图片描述
这是模板
内部使完全遵循 EJS 模板引擎的语法
例如 <%= title %>
    <% if(success){ %>
        哈哈哈
    <% } %>
// index.js
// 此文件作为 Generator 的核心入口
// 需要导出一个继承自 Yeoman Generator 的类型
// Yeoman Generator 在工作的时候会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法,实现一些功能,比如:文件写入

// 首先载入 yeoman-generator
const Generator = require('yeoman-generator')

// 导出一个类型,让这个类型继承自 Generator
module.exports = class extends Generator{
    
    
    // 在文件当中定义一个 writing 方法,Yeoman 自动生成文件阶段自动调用此方法
    writing () {
    
    
        // 这里尝试往项目目录中写入文件,这儿通过父类当中的 fs 模块去写入文件到我们的生成目录
        // 需要注意的是这里的 fs 模块,与 node 的 fs 模块不同,这个是一个高度封装的 fs 模块,相对于原生的 fs 模块,功能更加强大一些
        // 两个参数 一个 filepath 一个 contents 路径和内容
        // 简单的 generator 就完成了
        // this.fs.write(
        //     this.destinationPath('temp.txt'),
        //     Math.random().toString()
        // )
        // 有了模板过后,就不用借助于 this.fs.write 去写入文件,而是借助于 fs 当中一个专门使用模板引擎的方法叫做 copyTpl 的方式
        // 三个参数:模板文件的路径,输出文件的路径,模板数据的上下文
        // 获取模板文件路径
        const tmpl = this.templatePath('foo.txt')
        // 输出路径
        const output = this.destinationPath('foo.txt')
        // 模板数据上下文
        const context = {
    
     title: 'Hello foo.txt', success:false }
        // 通过 copyTpl 传入,会自动的把我们的文件映射到生成的输出文件上
        this.fs.copyTpl(tmpl, output, context)
    }
}

切换到命令行再次使用 sample 生成器:yo sample
在这里插入图片描述
在这里插入图片描述
相对于手动去创建每一个文件,模板的方式大大提高了效率,特别是在文件比较多,比较复杂的情况下

接收用户输入

对于模板当中的一些动态数据,例如项目的标题,名称等等,这样的数据呢一般通过命令行交互的方式去询问我们的使用者,从而得到。

在 generator 当中,想要去实现,可以通过 generator 提供的 prompting 方法

新建一个模板:index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title><%= name %></title>
</head>
<body>
  
</body>
</html>

更改 index.js

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

// 首先载入 yeoman-generator
const Generator = require('yeoman-generator')

// 导出一个类型,让这个类型继承自 Generator
module.exports = class extends Generator{
    
    
    prompting () {
    
    
        // Yeoman 在询问用户环节会自动调用此方法
        // 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问,这个方法返回一个 promise ,我们需要去 return ,这样的话 Yeoman 可以更好的进行流程控制
        return this.prompt([
            {
    
    
                type: 'input',
                name: 'name',
                message: `Your project name (${
      
      this.appname})`,
                default: this.appname // appname 默认为生成当前项目目录的名字
            }
        ])
        .then(answers => {
    
    
            // answers 就是用户输出的结果,{name: 'user input value'}
            this.answers = answers // 挂载到当前 this 对象上,便于在后边 writing 的时候使用
        })
    }
    // 在文件当中定义一个 writing 方法,Yeoman 自动生成文件阶段自动调用此方法
    writing () {
    
    
        // 这里尝试往项目目录中写入文件,这儿通过父类当中的 fs 模块去写入文件到我们的生成目录
        // 需要注意的是这里的 fs 模块,与 node 的 fs 模块不同,这个是一个高度封装的 fs 模块,相对于原生的 fs 模块,功能更加强大一些
        // 两个参数 一个 filepath 一个 contents 路径和内容
        // 简单的 generator 就完成了
        // this.fs.write(
        //     this.destinationPath('temp.txt'),
        //     Math.random().toString()
        // )
        // 有了模板过后,就不用借助于 this.fs.write 去写入文件,而是借助于 fs 当中一个专门使用模板引擎的方法叫做 copyTpl 的方式
        // 三个参数:模板文件的路径,输出文件的路径,模板数据的上下文
        // 获取模板文件路径
        // const tmpl = this.templatePath('foo.txt')
        const tmpl = this.templatePath('index.html')
        // 输出路径
        // const output = this.destinationPath('foo.txt')
        const output = this.destinationPath('index.html')
        // 模板数据上下文
        // const context = { title: 'Hello foo.txt', success:false }
        const context = this.answers // 使用当前 this 对象的 answers 
        // 通过 copyTpl 传入,会自动的把我们的文件映射到生成的输出文件上
        this.fs.copyTpl(tmpl, output, context)
    }
}

模板里边咱们这么使用:
在这里插入图片描述
保存以下去命令行再次使用 yo sample :
在这里插入图片描述
看生成的模板文件:
在这里插入图片描述
多个参数:
在这里插入图片描述
在这里插入图片描述
生成之后:
在这里插入图片描述
这就是在 yeoman 当中如何动态接收用户输入数据的实现方式

Vue Generator 案例

这里开始自定义一个带有基础代码的 vuejs 脚手架:

首先按照原始的方式去创建一个理想的项目结构,这个项目当中把你需要重复使用的代码全部包含在里边,然后我们再去封装一个全新的 generator 用于去生成这样一个理想状态下的项目结构:
在这里插入图片描述
首先创建一个全新的 generator 目录 mkdir generator-zgp-vue
在这里插入图片描述
进入到这个目录并初始化一个 package.json

cd generator-zgp-vue
yarn init

在这里插入图片描述
安装 yeoman 的依赖:yarn add yeoman-generator

安装完成之后,使用编辑器打开

新建 generator 对应结构 generators/app/index.js 主入口文件:
在这里插入图片描述
文件当中还是和之前一样,但是 writing 方法不再像之前只是去写入单个文件,它需要把我们刚才所准备好的那样一个 vue 的结构去批量生成,所以需要一个 templates 目录,把项目结构拷贝到 templates 当中作为模板:
在这里插入图片描述
有了模板过后,我们需要把项目结构里边一些可能会发生变化的地方,通过 ejs 模板引擎的语法去挖坑,这里在 prompting 当中接收的只有一个 name ,所以这里只把所有 name 的地方全部替换成 ejs 的语法 <%= name %>
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
替换过后,再回到 index.js 当中

const Generator = require('yeoman-generator')

module.exports = class extends Generator {
    
    
    prompting() {
    
    
        return this.prompt([
            {
    
    
                type: 'input',
                name: 'name',
                message: `Your project name (${
      
      this.appname})`,
                default: this.appname
            }
        ])
            .then(answers => {
    
    
                this.answers = answers
            })
    }
    // 这时候 writing 方法不再像之前只是去写入单个文件,它需要把我们刚才所准备好的那样一个 vue 的结构去批量生成,所以需要一个 templates 目录,把项目结构拷贝到 templates 当中作为模板
    writing() {
    
    
        // 正常是 一个一个生成每一个文件,把它生成到对应的目标路径,这里通过数组循环的方式,去批量的生成每一个文件
        const templates = [
            '.browserslistrc',
            '.editorconfig',
            '.env.development',
            '.env.production',
            '.eslintrc.js',
            '.gitignore',
            'babel.config.js',
            'package.json',
            'postcss.config.js',
            'README.md',
            'public/favicon.ico',
            'public/index.html',
            'src/App.vue',
            'src/main.js',
            'src/router.js',
            'src/assets/logo.png',
            'src/components/HelloWorld.vue',
            'src/store/actions.js',
            'src/store/getters.js',
            'src/store/index.js',
            'src/store/mutations.js',
            'src/store/state.js',
            'src/utils/request.js',
            'src/views/About.vue',
            'src/views/Home.vue'
        ]
        templates.forEach(item => {
    
    
            this.fs.copyTpl(
                this.templatePath(item),
                this.destinationPath(item),
                this.answers
            )
        })
    }
}

先 link 当前模块到全局,然后定位到项目所在位置:

yarn link
mkdir zgp-vue-test
cd zgp-vue-test

在这里插入图片描述
使用 yo zgp-vue
在这里插入图片描述
这个错误是说在 generator-zgp-vue\generators\app\templates\public\index.html 文件当中有一个模板标记:<%= BASE_URL %> BASE_URL 没定义数据

但是我们这里要输出的是这个模板标记的本身,并不是把它当作一个模板标记,需要原封不动的输出的,在 EJS 模板标记中原封不动的输出模板标记语法是:<%% title %> 在前边的 % 后边再加一个 % ,类似于 js 的转义。
在这里插入图片描述
我们再次运行 yo zgp-vue
在这里插入图片描述
我们看生成的结果:
在这里插入图片描述
就是我们需要的那个对应的模板,这样我们的一个原始的项目结构就可以被复用起来了,后续去做的时候可以加上更多的选项,让我们的项目脚手架可以更加灵活,更加通用。

发布 Generator

因为 generator 实际上就是一个 npm 的模块,所以发布 generator 实际上就是去发布一个 generator 模块,我们只需要将我们写好的一个 generator 通过 npm publish 这样一个命令去发布成一个公开的模块就可以了。

具体的在做之前,我们一般会将项目的源代码托管到一个公开的源代码仓库上面。

初始化一个空仓库,并进行一个本地的提交:

先创建一个 .gitignore 去忽略 node_modules 这样的目录

git init
echo node_modules > .gitignore
git status
git add .
git commit -m "feat: initial commit"

在这里插入图片描述
在这里插入图片描述
接下来我们需要一个远端的仓库,将本地的提交同步到远端,打开 github 创建一个新的仓库
在这里插入图片描述
在这里插入图片描述
通过 git remote add origin [email protected]:zhangpupu42/generator-zgp-vue.git 为我们本地仓库指定一个远端仓库地址

# 推送到远端
git push -u origin master

在这里插入图片描述
在这里插入图片描述
推送成功之后就可以发布了 npm publish 这里使用的 yarn 跟 npm 一样的

yarn publish

如果是第一次使用,会让输入用户名,邮箱,密码
在这里插入图片描述
在这里插入图片描述
这里报了一个错,原因就是国内的开发者习惯用淘宝的镜像源去取待npm官方的镜像,这时候往 npm 上发布代码的时候就会出现问题,因为淘宝的镜像是一个只读镜像 …

这时候可以

  • 先修改以下本地的 镜像 配置
  • publish 的时候跟上发布地址
# 跟上一个 registry 等于官方的镜像地址 https://registry.npmjs.org/,这里使用的 yarn 所以跟上的是 yarn 的地址
yarn publish --registry=https://registry.yarnpkg.com

又报了一个错:Forbidden
error Couldn’t publish package: “https://registry.yarnpkg.com/generator-zgp-vue: Forbidden”
在这里插入图片描述
这是因为注册 npm 之后没有去邮箱验证
在这里插入图片描述
这里去邮箱验证一下:
在这里插入图片描述
在这里插入图片描述
没有未验证的那个黄条了,再次 publish
在这里插入图片描述
OK 发布成功了

yarn 的官方镜像是和 npm 的是保持同步的,我们发布到了 yarn 也就代表着我们发布成功了,我们去 npm 官网上去看一下:
在这里插入图片描述
好了 npm 官网上也有了这样的一个包,有了这样一个模块,我们就可以通过 npm 或者 yarn 的方式去全局范围去安装,然后通过 yeoman 的方式去使用它了。

这里还有一个需要注意的是:如果你想你的这个 generator 在 yeoman 官方的列表也出现的话,你可以给你的项目添加一个关键词叫 yeoman-generator 的关键词,这时候 yeoman 官方就会发现你的这个项目。

这里只是测试,就不做操作了。

Plop 简介

除了像 yeoman 这样大型的脚手架工具,还有一些小型的脚手架工具也非常的出色,这里介绍一款小型的脚手架工具,叫做 plop 。

Plop 是一款主要用于去创建项目中特定类型文件的一个小工具,有点类似于 yeoman 中的 sub-generator ,不过一般不会独立去使用,一般都会把 plop 集成到项目中,用来自动化的去创建同类型的项目文件。

接下来我们通过两个案例的对比去体会它的作用以及它的优势:

这里使用两个相同的 react 项目来做举例说明:
在这里插入图片描述
不同的是右侧的项目中集成了 plop 工具。具体的差异需要从日常开发中面临的一个问题说起:

开发过程中,经常需要重复创建相同类型的文件,例如这个例子当中,每一个页面的文件都会由3个文件组成,分别是 js css test.js 的文件,如果我们需要去创建新得一个组件,我们就需要创建3个这样的文件,而且,每一个文件当中都含有基础的一些代码,整个过程非常的繁琐,而且很难统一每一个文件当中那些基础的代码。

相对于左侧的项目,右侧的项目中使用了 plop ,面对相同的问题,使用 plop 就会方便很多,我们只需要在命令行当中去运行 plop ,命令行就会根据我们之前的一个配置,自动的去询问我们一些信息,然后根据我们所输入的结果,自动的帮我们创建对应的这些文件,这样就确保了我们每次创建的文件是统一的,而且是自动化的。
在这里插入图片描述
在这里插入图片描述
这样就大大提高了我们在开发过程当中每次去创建重复文件时的效率。

Plop 的基本使用

接下来我们将在一个 react 项目中加入 plop 的集成,了解一下 plop 该如何使用:

使用 plop 的第一件事儿,就是将 plop 以一个开发依赖的模块形式,安装到我们的项目当中

yarn add plop --dev

在这里插入图片描述
安装完成过后,我们需要在项目的根目录新建一个叫 plopfile.js 的一个文件

// Plop 入口文件 ,需要导出一个函数
// 此函数接收一个叫 plop 对象,这个对象里边提供了一系列的工具函数,用于帮我们创建生成器的任务

module.exports = plop => {
    
    
    // 第一个参数:生成器的名字,第二个参数:生成器的一些配置选项
    // 指定一下生成器的描述:description
    // 还可以指定这个 generator 在工作时候命令行当中发出的一些指定问题,prompts:[]
    // 有了命令行交互过后,还可以指定一个 actions ,顾名思义就是完成命令行交互过后,需要执行的一些动作
    plop.setGenerator('component', {
    
    
        description: 'create a component',
        prompts: [
            {
    
    
                type: 'input',
                name: 'name',
                message: 'component name',
                default: 'MyComponent'
            }
        ],
        actions: [
            {
    
    
                type: 'add', // 代表添加一个全新的文件
                path: 'src/components/{
    
    {name}}/{
    
    {name}}.js', // 被添加的文件会被添加到哪个具体的路径,可以使用 {
    
    {}} 这种方式去插入刚刚在命令行当中得到的数据
                templateFile: 'plop-templates/component.hbs', // 指定本次要添加的文件对应的模板是哪个模板文件,一般我们会将项目对应的模板文件放在项目根目录所在的一个叫 plop-templates 的文件夹下,在这个目录下,我们可以通过 handlebars 模板引擎的方式去创建一些模板文件,都遵循 handlebars 语法
            }
        ]
    })
}

在这里插入图片描述

import React from 'react';

export default () => {
    <div className="{
     
     {name}}">
        <h1>{
   
   {name}} Component</h1>
    </div>
}

准备完成之后,我们就可以会到命令行,因为安装 plop 的时候就为我们提供了一套 plop 的 cli 命令行程序:
在这里插入图片描述
我们可以使用 yarn 去启动这个 cli 程序 (npm 也可以)
具体的使用方式就是 : yarn + plop + 生成器名字

yarn plop component

在这里插入图片描述
创建之前:
在这里插入图片描述
创建之后:
在这里插入图片描述
我们可以看到,这样的一个文件就会根据我们的模板创建成功。

有了这样一个体验过后,我们就可以为生成器添加多个模板。

因为在 react 当中,一个组件是由多个文件组成的,所以这里为 css 文件和 test 文件去添加相应的模板

component.css.hbs
在这里插入图片描述
component.test.hbs
在这里插入图片描述
有了模板之后,我们就可以回到 plopfile 中去添加多个 action ,这里只是新增文件,所以这里的 type 都是 add ,可以自行去 plop 的官网查看更多的 type 类型

// Plop 入口文件 ,需要导出一个函数
// 此函数接收一个叫 plop 对象,这个对象里边提供了一系列的工具函数,用于帮我们创建生成器的任务

module.exports = plop => {
    
    
    // 第一个参数:生成器的名字,第二个参数:生成器的一些配置选项
    // 指定一下生成器的描述:description
    // 还可以指定这个 generator 在工作时候命令行当中发出的一些指定问题,prompts:[]
    // 有了命令行交互过后,还可以指定一个 actions ,顾名思义就是完成命令行交互过后,需要执行的一些动作
    plop.setGenerator('component', {
    
    
        description: 'create a component',
        prompts: [
            {
    
    
                type: 'input',
                name: 'name',
                message: 'component name',
                default: 'MyComponent'
            }
        ],
        actions: [
            {
    
    
                type: 'add', // 代表添加一个全新的文件
                path: 'src/components/{
    
    {name}}/{
    
    {name}}.js', // 被添加的文件会被添加到哪个具体的路径,可以使用 {
    
    {}} 这种方式去插入刚刚在命令行当中得到的数据
                templateFile: 'plop-templates/component.hbs', // 指定本次要添加的文件对应的模板是哪个模板文件,一般我们会将项目对应的模板文件放在项目根目录所在的一个叫 plop-templates 的文件夹下,在这个目录下,我们可以通过 handlebars 模板引擎的方式去创建一些模板文件,都遵循 handlebars 语法
            },
            {
    
    
                type: 'add', // 代表添加一个全新的文件
                path: 'src/components/{
    
    {name}}/{
    
    {name}}.css', // 被添加的文件会被添加到哪个具体的路径,可以使用 {
    
    {}} 这种方式去插入刚刚在命令行当中得到的数据
                templateFile: 'plop-templates/component.css.hbs', // 指定本次要添加的文件对应的模板是哪个模板文件,一般我们会将项目对应的模板文件放在项目根目录所在的一个叫 plop-templates 的文件夹下,在这个目录下,我们可以通过 handlebars 模板引擎的方式去创建一些模板文件,都遵循 handlebars 语法
            },
            {
    
    
                type: 'add', // 代表添加一个全新的文件
                path: 'src/components/{
    
    {name}}/{
    
    {name}}.test.js', // 被添加的文件会被添加到哪个具体的路径,可以使用 {
    
    {}} 这种方式去插入刚刚在命令行当中得到的数据
                templateFile: 'plop-templates/component.test.hbs', // 指定本次要添加的文件对应的模板是哪个模板文件,一般我们会将项目对应的模板文件放在项目根目录所在的一个叫 plop-templates 的文件夹下,在这个目录下,我们可以通过 handlebars 模板引擎的方式去创建一些模板文件,都遵循 handlebars 语法
            }
        ]
    })
}

这里重新运行 plop yarn plop component
在这里插入图片描述
在这里插入图片描述
可以看到,生成了 MyComponent 组件和对应的三个文件。

这就是 plop 的基本的使用,可以看到,plop 去创建项目中同类型的文件还是非常方便的。

总结一下,我们在项目当中具体使用 plop 需要这么几个步骤:

  • 将 plop 模块作为项目开发依赖安装
  • 在项目根目录下创建一个 plopfile.js 文件
  • 在 plopfile.js 文件中定义脚手架任务
  • 编写用于生成特定类型文件的模板
  • 通过 Plop 提供的 cli 运行脚手架任务
脚手架的工作原理

通过前面脚手架工具的介绍,不难发现,大部分脚手架工具的工作原理都比较简单,无外乎就是在你启动过后,自动的问一些预设的问题,然后将回答的结果结合一些模板文件生成对应的项目结构。

接下来就使用 node 去开发一个小型的脚手架工具,去深入的体会一下脚手架工具的工作过程。

我们都知道脚手架工具实际上就是一个 node cli 应用,去创建脚手架工具就是去创建一个 cli 应用。

mkdir sample-scaffolding
cd sample-scaffolding
yarn init --yes
# 使用编辑器打开当前目录,这里是 vsCode
code .

在 package.json 文件当中指定一个 bin 字段,指定当前 cli 应用的入口文件

{
    
    
  "name": "sample-scaffolding",
  "version": "1.0.0",
  "main": "index.js",
  "bin": "cli.js",
  "license": "MIT"
}

创建 cli.js 文件
在这里插入图片描述
cli.js 内容:

#!/usr/bin/env node

// Node Cli 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 还需要修改此文件读写权限为 755
// chmod 755 cli.js

console.log('先简单的 console 一句话:cli 起作用了')

然后去命令行 link 一下,全局就能使用 sample-scaffolding 指令了

yarn link
sample-scaffolding

在这里插入图片描述
console.log 正常执行,意味着这个 cli 应用的基础也就 OK 了

接下来实现以下脚手架的具体业务(工作过程):

  • 通过命令行交互的方式询问用户的信息
  • 根据用户反馈回来的结果生成文件

在 node 当中去发起命令行交互使用的是 inquirer 这样一个模块,我们需要安装一下:
https://www.npmjs.com/package/inquirer#documentation

yarn add inquirer

在这里插入图片描述

#!/usr/bin/env node

// Node Cli 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 还需要修改此文件读写权限为 755
// chmod 755 cli.js

// 脚手架工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件

const inquirer = require('inquirer')

// 发起命令行询问,参数是数组,每个成员就是发起的每个提问
inquirer.prompt([
    {
    
    
        type: 'input',
        name: 'name',
        message: 'Project name?'
    }
])
.then(answers => {
    
    
    console.log(answers)
})

在这里插入图片描述
可以看到,inquirer 确实可以帮我们发起用户询问

那有了 answers 过后接下来我们就要考虑的是动态的生成我们的项目文件,生成项目文件一般会根据项目模板去生成,我们在项目根目录下新建一个 templates 目录,在这个目录下新建一些模板,因为我们这里重点关注脚手架工作过程,所以不需要关心模板里边有什么太多的东西,简单的弄一两个文件就好。

并且在模板中我们可以使用 <%= name %> 这种语法去绑定动态数据(询问环节得到的答案)。

在这里插入图片描述
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= name %></title>
</head>
<body>
    <h1>Welcome <%= name %> !</h1>
</body>
</html>
*{
    
    
    font-size: 16px;
    color: #333333;
    margin: 0;
    padding: 0;
}
body {
    
    
    background: #f7f7f7;
}

有了模板过后,回到 cli.js 文件,就可以根据用户的回答去生成文件了。

#!/usr/bin/env node

// Node Cli 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 还需要修改此文件读写权限为 755
// chmod 755 cli.js

// 脚手架工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件

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

// 发起命令行询问,参数是数组,每个成员就是发起的每个提问
inquirer.prompt([
    {
    
    
        type: 'input',
        name: 'name',
        message: 'Project name?'
    }
])
.then(answers => {
    
    
    // 2. 根据用户回答的结果生成文件
    // 明确模板目录
    const tmplDir = path.join(__dirname, 'templates')
    // 输出的目标目录:命令行在哪执行就是哪个路径
    const destDir = process.cwd() // process.cwd 方法得到当前命令行所在路径

    // 通过 fs 模块读取模板目录下所有文件,全部输出到目标目录去
    fs.readdir(tmplDir, (err, files) => {
    
    
        if(err) throw err

        files.forEach(file => {
    
     // file 是每个模板相对于 templates 目录的相对路径
            console.log(file)
        })
    })
})

在这里插入图片描述
拿到了模板所在路径就好办了,我们需要通过模板引擎去渲染这个路径所对应的文件,这里用 ejs 模板引擎,需要安装一下:

yarn add ejs

在这里插入图片描述
安装过后就可以使用了:

#!/usr/bin/env node

// Node Cli 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 还需要修改此文件读写权限为 755
// chmod 755 cli.js

// 脚手架工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件

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 => {
    
    
        // 2. 根据用户回答的结果生成文件
        // 明确模板目录
        const tmplDir = path.join(__dirname, 'templates')
        // 输出的目标目录:命令行在哪执行就是哪个路径
        const destDir = process.cwd() // process.cwd 方法得到当前命令行所在路径

        // 通过 fs 模块读取模板目录下所有文件,全部输出到目标目录去
        fs.readdir(tmplDir, (err, files) => {
    
    
            if (err) throw err

            files.forEach(file => {
    
     // file 是每个模板相对于 templates 目录的相对路径
                // 通过模板引擎去渲染路径所对应的文件
                ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
    
    
                    if(err) throw err
                    console.log(result)
                }) // renderFile 渲染
            })
        })
    })

再次运行:
在这里插入图片描述
可以看到,输出的结果是经过模板引擎渲染过后的结果,我们只需要将这个结果通过写入的方式,写入到目标目录就可以了:

#!/usr/bin/env node

// Node Cli 应用入口文件必须要有这样的文件头
// 如果是 Linux 或者 macOS 还需要修改此文件读写权限为 755
// chmod 755 cli.js

// 脚手架工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件

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 => {
    
    
        // 2. 根据用户回答的结果生成文件
        // 明确模板目录
        const tmplDir = path.join(__dirname, 'templates')
        // 输出的目标目录:命令行在哪执行就是哪个路径
        const destDir = process.cwd() // process.cwd 方法得到当前命令行所在路径

        // 通过 fs 模块读取模板目录下所有文件,全部输出到目标目录去
        fs.readdir(tmplDir, (err, files) => {
    
    
            if (err) throw err

            files.forEach(file => {
    
     // file 是每个模板相对于 templates 目录的相对路径
                // 通过模板引擎去渲染路径所对应的文件
                ejs.renderFile(path.join(tmplDir, file), answers, (err, result) => {
    
    
                    if(err) throw err
                    // 将渲染过后的结果写入到目标目录
                    fs.writeFileSync(path.join(destDir, file), result)
                }) // renderFile 渲染
            })
        })
    })

代码写完之后,回到命令行当中,定位到一个全新的目录,尝试使用这个脚手架工具:
在这里插入图片描述
在这里插入图片描述
和生成前做个对比:
在这里插入图片描述
包括用户输入的内容都在,至此,我们就完成了一个非常简单,非常小型的脚手架应用。

脚手架的工作原理并不复杂,但是它的意义确是很大的,因为它确实在创建项目环节,大大提高了我们的效率。

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(一、工程化概述)

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(二、脚手架工具)

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(三、自动化构建 – 主Grunt)

【学习笔记】Part2·前端工程化实战–开发脚手架及封装自动化构建工作流(三、自动化构建 – 主Gulp)

猜你喜欢

转载自blog.csdn.net/qq_38652871/article/details/109596987