广告位联系
返回顶部
分享到

手把手教你从0搭建前端脚手架的全部详细教程

JavaScript 来源:互联网 作者:佚名 发布时间:2023-12-10 21:17:07 人浏览
摘要

先来看一眼实现的效果。 从图上来看这个脚手架的功能非常的简单只有一个创建的命令,其他都是帮助和显示版本号的。 也就是上图这句,创建一个新项目,只需要输入create 项目名便

先来看一眼实现的效果。

请添加图片描述

从图上来看这个脚手架的功能非常的简单只有一个创建的命令,其他都是帮助和显示版本号的。

请添加图片描述

也就是上图这句,创建一个新项目,只需要输入create 项目名便可使用,在创建时执行了一系列的操作,这一块的思路很简单,就是将git仓库中的项目模板拷贝下来再依据使用者的不同操作对复制下来的模板的部分文件进行修改就可以了,大致思路便介绍到这里,接下来我们便来详细的讲讲如何实现,以及会用到的依赖。

脚手架目录结构

请添加图片描述

了解搭建的脚手架

脚手架就是在启动的时候询问一些简单的问题,并且通过用户回答的结果去渲染对应的模板文件,我们接下来的流程亦是如此

脚手架的初始化

由于它是一个npm的包,因此我们需要使用npm的初始化命令,随意新建一个文件夹打开命令行,输入npm init,会出现以下情况。

请添加图片描述

名称 意思 默认值
package name 包的名称 创建文件夹时的名称
version 版本号 1.0.0
description 包的描述 创建文件时的名称
entry point 入口文件 index.js
test command 测试命令
git repository git仓库地址
git仓库地址 关键词,上传到npm官网时在页面中展示的关键词
author 作者信息,对象的形式,里面存储一些邮箱、作者名、url
license 执照 MIT

这就是输入初始化命令时会询问的东西,回答完这些后就会生成一个 package.json 的文件,这个文件就是记录包的信息。

如果想要了解更多,可查看如下地址:
package.json详解

脚手架依赖安装

用到如下依赖请安装。

1

2

3

4

5

6

7

npm i path

npm i chalk@4.1.0

npm i fs-extra

npm i inquirer@8.2.4

npm i commander

npm i axios

npm i download-git-repo

询问用户问题

创建入口文件

在询问问题前我们需要先创建一个入口文件,创建完成后在package.json中添加bin项,并且将入口文件路径写进去

请添加图片描述

填写完入口文件路径后在入口文件内随便输出一句, 但必须在入口文件顶层声明文件执行方式为node。

声明代码:

#! /usr/bin/env node

请添加图片描述

写完后我们需要测试一下我们是否可以正常的访问的我们的脚手架,在本文件夹打开命令行,输入 npm link ,该命令会创建一个全局访问的包的快捷方式,这个是临时的就是本地测试的时候用的,这个在命令行输入你的脚手架的名称可以看到入口文件输出的内容。

请添加图片描述

最基本的交互命令

在完成上一步后我们就要开始与用户进行交互了,这个时候我们就需要用到一个用于自定义命令行指令的依赖 commander。

引入依赖:

1

const program = require('commander')

简单介绍一下commander依赖常用的方法

command

命令。.command()的第一个参数为命令名称。命令参数可以跟在名称后面,也可以用.argument()单独指定。

参数可为必选的(尖括号表示)、可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。

例如:

1

2

// 创建一个create命令

.command('create <app-name>')

parse

解析。.parse()的第一个参数是要解析的字符串数组,也可以省略参数而使用process.argv,这里我们也是用process.argv用来解析node的参数。

例如:

1

2

// 解析用户执行命令传入参数

program.parse(process.argv);

option

选项。option()可以附加选项的简介。第一个参数可以定义一个短选项名称(-后面接单个字符)和一个长选项名称(–后面接一个或多个单词),使用逗号、空格或|分隔。第二个参数为该选项的简介。

例如:

1

.option('-f, --force', '如果存在的话强行覆盖')

action

处理函数。用command创建的自定义命令的处理函数,action携带的实参顺序就是命令上的参数的顺序。

例如:

1

2

3

4

5

6

7

program.command('create <app-name>')

// 这个name 就代表第一个必填参数 options就代表其余, 如果有第二个就在写一个,最后一个永远是剩余参数

.action((name, options) => {

    console.log(name)

    // 打印执行结果

    // require("../lib/create")(name, options)

})

编写交互命令 create

入口文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

#! /usr/bin/env node

const program = require('commander');

const chalk = require('chalk');

 

// 定义命令和参数

// create命令

program

.command('create <app-name>')

.description('create a new project')

// -f or --force 为强制创建,如果创建的目录存在则直接覆盖

.option('-f, --force', 'overwrite target directory if it exist')

.action((name, options) => {

    // 打印执行结果

    console.log('项目名称', name)

})

 

// 解析用户执行命令传入参数

program.parse(process.argv);

这里我们创建了一个叫 create 的自定义指令,这个命令有着必填的项目名、可以选择的强制覆盖的选项 -f,有着处理函数action。

我们在action中接收并打印了用户输入的项目名称。

接下来我们再次运行一下自己的脚手架并带上create命令,我的叫test

1

test-cli create app

出现如下就说明第一个命令创建成功了

这里请注意 解析用户命令参数的操作一定要在最后一行否则什么都不会出现。

1

program.parse(process.argv)

到这里为止我们成功为我们脚手架创建了第一个交互命令,想查看更多关于 commander 的请点击这里commander。

创建第一个模板项目

在创建了一个基本命令 create 后我们就要开始创建一个模板并在用户使用该命令时复制并修改我们所创建的模板。

创建一个模板

我们在复制模板前需要一个模板,现在的我们随便创建一个文件夹并取名为template里面创建一个html。

像这样创建好后,我们就有了一个模板,但我们依然需要让模板有一个可被下载、查询的地方,这里我选择的是使用 git 组织仓库,因为这样可以直接通过git提供的接口进行文件下载,包括选择不同的模板等。

上传模板

我们先去 git 的官网中新建一个存放模板的组织仓库。

点击图中的位置进入组织,并点击下图的创建

会进入到付费的位置,没有大需求就选免费

在这里插入图片描述

填写信息完基本就算创建成功了

在这里插入图片描述

接下来在组织中创建一个储存库

在这里插入图片描述

这里我们暂且选择可见的仓库,千万不要选择私人仓库,否则git接口会找不该仓库

在这里插入图片描述

创建好后的仓库,就直接将模板代码提交至也本次创建的仓库中就可以了,我们在vscode中进行演示。
先点击推送

如果没有推送的仓库则会提示是否添加推送仓库,我们点击推送远程仓库,并从中找到自己的仓库

在这里插入图片描述

择完成后输入仓库名称,然后会报错,报错原因就是因为暂无推送的内容,这个使用,正常的在 vscode 中提交代码就行了,然后查看自己的仓库,会出现上传的内容

在这里插入图片描述

增加一个新的版本标签

跟着下列图操作

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

点击发布发行版后就可以了。

下载模板

我们上传模板后可以通过 git 提供的接口来完成下载模板的功能,首先我们先去询问用户要下载的模板名称然后在用依赖包来进行下载:
https://api.github.com/orgs/geeksTest/repos 获取该组织下的所有模板

create命令后续操作

上传模板后,我们就可以继续完成create命令的后续操作了。

create命令下使用创建函数

1

2

3

4

5

6

7

8

9

program

.command('create <app-name>')

.description(chalk.cyan('create a new project'))

// -f or --force 为强制创建,如果创建的目录存在则直接覆盖

.option('-f, --force', 'overwrite target directory if it exist')

.action((name, options) => {

    // 打印执行结果

    require("../lib/create")(name, options)

})

创建create文件

创建 create 文件用来回应用户的 create 命令。

这里用到的依赖

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

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

// lib/create.js

 

const path = require('path')

// fs-extra 是对 fs 模块的扩展,支持 promise 语法

const fs = require('fs-extra')

// 用于交互式询问用户问题

const inquirer = require('inquirer')

// 导出Generator类

const Generator = require('./Generator')

 

//1. 抛出一个方法用来接收用户要创建的文件夹(项目)名 和 其他参数

module.exports = async function (name, options) {

  // 当前命令行选择的目录

  const cwd  = process.cwd();

  // 需要创建的目录地址

  const targetAir  = path.join(cwd, name)

   

  //2 判断是否存在相同的文件夹(项目)名

  // 目录是否已经存在?

  if (fs.existsSync(targetAir)) {

    // 是否为强制创建?

    if (options.force) {

      await fs.remove(targetAir)

    } else {

      // 询问用户是否确定要覆盖

      let { action } = await inquirer.prompt([

        {

          name: 'action',

          type: 'list',

          message: 'Target directory already exists Pick an action:',

          choices: [

            {

              name: 'Overwrite',

              value: 'overwrite'

            },{

              name: 'Cancel',

              value: false

            }

          ]

        }

      ])

      // 如果用户拒绝覆盖则停止剩余操作

      if (!action) {

        return;

      } else if (action === 'overwrite') {

        // 移除已存在的目录

        console.log(`\r\nRemoving...`)

        await fs.remove(targetAir)

      }

    }

  }

 

  //3 新建generator类

  const generator = new Generator(name, targetAir);

  generator.create();

}

创建generator类

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

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

// lib/Generator.js

 

const { getRepoList, getTagList } = require('./http')

const ora = require('ora')

const inquirer = require('inquirer')

const util = require('util')

const downloadGitRepo = require('download-git-repo') // 不支持 Promise

const chalk = require('chalk')

const path = require('path');

const fs = require("fs-extra");

 

// 添加加载动画

async function wrapLoading(fn, message, ...args) {

  // 使用 ora 初始化,传入提示信息 message

  const spinner = ora(message);

  // 开始加载动画

  spinner.start();

 

  try {

    // 执行传入方法 fn

    const result = await fn(...args);

    // 状态为修改为成功

    spinner.succeed();

    return result;

  } catch (error) {

    // 状态为修改为失败

    spinner.fail('Request failed, refetch ...');

  }

}

 

class Generator {

  constructor (name, targetDir){

    // 目录名称

    this.name = name;

    // 创建位置

    this.targetDir = targetDir;

    // 对 download-git-repo 进行 promise 化改造

    this.downloadGitRepo = util.promisify(downloadGitRepo);

  }

 

  // 获取用户选择的模板

  // 1)从远程拉取模板数据

  // 2)用户选择自己新下载的模板名称

  // 3)return 用户选择的名称

 

  async getRepo() {

    // 1)从远程拉取模板数据

    const repoList = await wrapLoading(getRepoList, 'waiting fetch template');

    if (!repoList) return;

    // 过滤我们需要的模板名称

    const repos = repoList.map(item => item.name);

 

    // 2)用户选择自己新下载的模板名称

    const { repo } = await inquirer.prompt({

      name: 'repo',

      type: 'list',

      choices: repos,

      message: 'Please choose a template to create project'

    })

 

    // 3)return 用户选择的名称

    return repo;

  }

 

  // 获取用户选择的版本

  // 1)基于 repo 结果,远程拉取对应的 tag 列表

  // 2)自动选择最新版的 tag

 

  async getTag(repo) {

    // 1)基于 repo 结果,远程拉取对应的 tag 列表

    const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);

    if (!tags) return;

     

    // 过滤我们需要的 tag 名称

    const tagsList = tags.map(item => item.name);

 

    // 2)return 用户选择的 tag

    return tagsList[0]

  }

 

  // 下载远程模板

  // 1)拼接下载地址

  // 2)调用下载方法

  async download(repo, tag){

    // 1)拼接下载地址

    const requestUrl = `geeksTest/${repo}${tag ? '#'+tag : ''}`;

 

    // 2)调用下载方法

    await wrapLoading(

      this.downloadGitRepo, // 远程下载方法

      'waiting download template', // 加载提示信息

      requestUrl, // 参数1: 下载地址

      path.resolve(process.cwd(), this.targetDir) // 参数2: 创建位置

    )

  }

 

  // 核心创建逻辑

  // 1)获取模板名称

  // 2)获取 tag 名称

  // 3)下载模板到模板目录

  // 4) 对uniapp模板中部分文件进行读写

  // 5) 模板使用提示

  async create(){

 

    // 1)获取模板名称

    const repo = await this.getRepo()

 

    // 2) 获取 tag 名称

    const tag = await this.getTag(repo)

 

    // 3)下载模板到模板目录

    await this.download(repo, tag)

     

    // 5)模板使用提示

    console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)

    console.log(`\r\n  cd ${chalk.cyan(this.name)}`)

    console.log(`\r\n  启动前请务必阅读 ${chalk.cyan("README.md")} 文件`)

   

  }

}

 

module.exports = Generator;

创建http文件

新建一个http.js的文件用来存放要请求的接口,我们用axios去请求.

依赖安装

1

npm i commander

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

// lib/http.js

 

// 通过 axios 处理请求

const axios = require('axios')

 

axios.interceptors.response.use(res => {

  return res.data;

})

 

/**

 * 获取模板列表

 * @returns Promise

 */

async function getRepoList() {

  return axios.get('https://api.github.com/orgs/geeksTest/repos')

}

 

/**

 * 获取版本信息

 * @param {string} repo 模板名称

 * @returns Promise

 */

async function  getTagList(repo) {

  return axios.get(`https://api.github.com/repos/geeksTest/${repo}/tags`)

}

 

module.exports = {

  getRepoList,

  getTagList

}

最后导出了两个方法, 模板列表、模板tag列表。
这个时候的api接口是可以直接在浏览器中访问到的,如果不想被人随意访问读取数据则可以在git中增加双因素验证,然后每次访问api时都会要求带上git的访问token否则会访问不到,查看双因素详情

搭建完成

完成这一步后我们再去进行test-cli create app命令,会看到下图。

在这里插入图片描述

会询问要创建的模板项目,我这里的远程组织模板叫做test,大家选择自己的模板回车,稍等一下就会创建成功,并看到在你使用命令的路径上多出一个项目名的文件夹,就成功了。

在这里插入图片描述

如果有对模板在下载后进行操作的需求可以使用fs依赖进行操作,到这里为止我们已经完成了一个简易的脚手架搭建,感谢大家耐心观看。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。

您可能感兴趣的文章 :

原文链接 :
    Tag :
相关文章
  • javascript入门教程基础篇介绍

    javascript入门教程基础篇介绍
    一、 简介 1、 什么是javascript JavaScript 是网景(Netscape)公司开发的一种基于客户端浏览器、面向(基于)对象、事件驱动式的网页脚本语言
  • 手把手教你从0搭建前端脚手架的全部详细教程

    手把手教你从0搭建前端脚手架的全部详细教程
    先来看一眼实现的效果。 从图上来看这个脚手架的功能非常的简单只有一个创建的命令,其他都是帮助和显示版本号的。 也就是上图这句,
  • 让chatgpt将html中的图片转为base64的方法

    让chatgpt将html中的图片转为base64的方法
    故事要从我们公司的新官网说起,新官网是叫外包做的,前后端没有分离,对,你没听错,都到了 2023 年的今天,新项目依然是前后端混在
  • JavaScript深拷贝方法structuredClone使用介绍
    对于深拷贝,最容易也应该是常见的方法是使用JSON.parse() + JSON.stringify(),还有一个借助第三方脚本库 lodash ,其中方法cloneDeep可以实现深拷
  • requestAnimationFrame使用示例介绍

    requestAnimationFrame使用示例介绍
    requestAnimationFrame--use是什么 告诉浏览器用来执行一个动画,并且在下一次重绘之前调用其指定的回调函数取更新动画,所以该方法的参数就
  • 不可变数据方案之immer.js原理介绍

    不可变数据方案之immer.js原理介绍
    本篇文章是JavaScript 函数式编程 学习系列第三篇 前一篇JavaScript数据类型对函数式编程的影响讲到了不可变数据的重要性,而让数据不可变的
  • WebComponent使用教程介绍

    WebComponent使用教程介绍
    WebComponent 是官方定义的自定义组件实现方式,它可以让开发者不依赖任何第三方框架(如Vue,React)来实现自定义页面组件;达到组件复用
  • element plus的样式修改和扩展实例介绍
    一、用户故事 我们开发了一个业务组件库。业务组件库是需要基于公司内部的一个UI组件库。而公司的UI组件库又出基于element ui的。 公司的
  • element弹窗表格的字体模糊bug解决方法

    element弹窗表格的字体模糊bug解决方法
    有一个BUG,就是在使用element弹窗表格的字体异常的模糊。如下图: 这个问题其实已经存在很久了。客户屡有反馈,但是不多。我们基本自测
  • 如何在JavaScript中使用媒体查询介绍

    如何在JavaScript中使用媒体查询介绍
    说起媒体查询想必大家最先想到的都是CSS中@media,没错,这是我们最常用的媒体查询方法,主要用来为我们的网站做适配处理。 比如: 1
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计