从零开始搭建前端脚手架(一)

252 阅读5分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

一、前言

本篇主要介绍如何搭建前端脚手架,一步一步地实现通过搭建的脚手架下载对应的项目模板。经过本人的一步一步的实践,终于做成了这第一步的目标。

本篇目标:通过自己搭建的脚手架,让用户选择下载对应的模板。

什么是脚手架?

简单来说,「前端脚手架」就是指通过选择几个选项快速搭建项目基础代码的工具。前端脚手架可以有效避免我们 ctrl + C 和 ctrl + V 相同的代码框架和基础配置。

前情提要

本篇文章node环境版本为v16.15.1!这个很重要!

image.png

二、实现步骤

初始化项目

首先我们创建一个文件夹first-engineering-1(文件夹名字自定义哦),之后执行命令

npm init -y

这一步是初始化一个package.json,然后我们在文件夹下创建一个bin文件夹,在bin文件夹下面在创建一个index.js文件,如图所示

image.png

工程的创建

之后让我们进行相关的配置和测试,在index.js第一行加上这句话#! /usr/bin/env node,如下所示

// first-engineering-1/bin/index.js

#! /usr/bin/env node

console.log('这是我的第一个脚手架项目')

然后在package.json文件中添加命令配置,这里咱们的命令叫做air,这个是可以自定义的。

"bin": {
   "air": "./bin/index.js"
}

如图所示

image.png

之后我们在项目内执行npm link测试下命令,如图所示代表成功了。

npm link ,link将一个任意位置的npm包链接到全局执行环境,从而在任意位置使用命令行都可以直接运行该npm包。 npm link命令通过链接目录和可执行文件,实现npm包命令的全局可执行。点击查看官方文档

image.png

之后我们在命令行输入下我们的命令air,执行结果如图

image.png

其实这里就相当于是执行了 node ./bin/index.js

这样我们就完成了第一步,接下来我们就要继续丰富我们的脚手架,来实现我们的目标。

实现读取用户命令行信息

这里我们要用到commander,这个是用来解析读取用户命令行中输入内容的工具。commander中文文档

以下是部分commander的API

  • Command#command(): 定义一个命令名字

  • Command#action(): 注册一个callback函数

  • Command#parse(): 解析命令行参数argv

安装

npm install commander

注意:为了避免node版本带来的插件引入方式问题,我们将package.json中添加"type": "module",如图

image.png

之后我们开始编写读取用户输入命令的逻辑

// first-engineering-1/bin/index.js
import { program } from 'commander'

program.version(packageJsonData.version, '-v, --version')
    .command('create <projectName>')
    .action((projectName) => { // 当用户输入create命令时走这里
      // 这里我们就可以对用户输入的命令进行进行操作
      console.log(`用户想创建一个名为${projectName}的项目`)
    })
program.parse(process.argv);

之后我们可以在命令行输入如下命令,就可以得到如图所示结果。我们的npm link是一直在的,除非你把它unlink

image.png

这一步读取用户命令我们就实现了。

拉取模版代码

这里我们就用到了另一个插件download-git-repo点击查看官方文档

安装

npm install download-git-repo

这里咱们使用direct:url的方式

  1. 如果使用 direct,并且没有 clone配置项, 你必须传入完整的zip文件地址, 包括分支(如果需要的话)。

  2. 如果使用 direct 并带有 clone配置项, 你必须传入完整的 git repo url , 你可以通过 direct:url#my-branch指定分支。

加上拉取代码模版后,我们的代码就变成这样了

#! /usr/bin/env node
import { program } from 'commander'
import download from 'download-git-repo'

program.version('1.0.0', '-v, --version')
    .command('create <projectName>')
    .action((projectName) => { // 当用户输入create命令时走这里
      // 这里我们就可以对用户输入的命令进行进行操作
      console.log(`用户想创建一个名为${projectName}的项目`)
      download(`direct:https://gitee.com/airdark/nuxt-website.git`, `./${projectName}`, {clone: true}, (err) =>{
        if (err) {
          console.log('获取模版失败')
        } else {
          console.log('Success!')
        }
      })
    })
program.parse(process.argv);

之后我们在命令行执行并看下结果,如图所示

image.png

image.png

这个项目地址是我自己在码云上创建的,大家使用github或者gitlab的话,建议使用download-git-repo插件的对应方式。

这一步我们就是实现了通过命令行拉取项目模板了。

实现增加选择项,与用户交互

假如我们有多个模板的话,我们可以让用户选择下载哪种模版。要实现这个,我们需要用到inquirer这个插件。点击查看官方文档

用户与命令行交互的工具---inquirer

安装

npm install inquirer

接下来我以备注的形式为大家说明,增加用户选择后的总体代码如下

#! /usr/bin/env node
import { program } from 'commander'
import download from 'download-git-repo'
import inquirer from 'inquirer'

// 设置让用户选择模版的问题项
const question = [
  {
    name: "features", // 选项名称
    message: "请选择要创建的项目模板", // 选项提示语
    type: "list", // 选项类型 另外还有 confirm check 等
    choices: [ // 具体的选项
        {
          name: "electron", // 选项展示的名称
          value: "electron1", // 用户最终选择的值
          description: "electron+vue2(桌面端项目)", // 自定义的说明
          link: 'https://gitee.com/airdark/first-electron-1.git' // 自定义的模版项目地址
        },
        {
          name: "web",
          value: "web",
          description: "vue2+webpack+iview(浏览器项目)",
          link: 'https://gitee.com/airdark/vue2-demo.git'
        },
        {
          name: "h5",
          value: "h5",
          description: "vue2+vant(移动端项目)",
          link: 'https://gitee.com/airdark/nuxt-website'
        }
    ]
  }
]

program.version('1.0.0', '-v, --version')
    .command('create <projectName>')
    .action((projectName) => { // 当用户输入create命令时走这里
      // 这里我们就可以对用户输入的命令进行进行操作
      console.log(`用户想创建一个名为${projectName}的项目`)
      // 交互输入的参数为上面的question,res是得到答案
      inquirer.prompt(question).then(res => {
        console.log('用户选择的答案', res)
        // 获取到第一项中,用户选择的值,这里偷了个懒,大家可以根据答案和问题获取对应下载链接。
        let info = question[0].choices.find(item => item.value === res.features)
        download(`direct:${info.link}`, `./${projectName}`, {clone: true}, (err) =>{
          if (err) {
            console.log('获取模版失败')
          } else {
            console.log('Success!')
          }
        })
      })
    })
program.parse(process.argv);

之后我们执行代码得到如图所示结果

image.png

这样我们就完成了我们的初步目标!

美化一下(字体颜色、图标)

这里我们安装两个插件orachalk。其中ora是设置图标的,chalk是设置字体颜色的。

安装

npm i ora chalk

之后完整代码如下所示

#! /usr/bin/env node

import { program } from 'commander'
import download from 'download-git-repo'
import inquirer from 'inquirer'
import ora from 'ora'
import chalk from 'chalk'

/*** 初始化loading图标文字 start */
const spinner = ora('模版下载中 ...')
/*** 初始化loading图标文字 end */

// 设置让用户选择模版的问题项
const question = [
  {
    name: "features", // 选项名称
    message: "请选择要创建的项目模板", // 选项提示语
    type: "list", // 选项类型 另外还有 confirm check 等
    choices: [ // 具体的选项
        {
          name: "electron", // 选项展示的名称
          value: "electron1", // 用户最终选择的值
          description: "electron+vue2(桌面端项目)", // 自定义的说明
          link: 'https://gitee.com/airdark/first-electron-1.git' // 自定义的模版项目地址
        },
        {
          name: "web",
          value: "web",
          description: "vue2+webpack+iview(浏览器项目)",
          link: 'https://gitee.com/airdark/vue2-demo.git'
        },
        {
          name: "h5",
          value: "h5",
          description: "vue2+vant(移动端项目)",
          link: 'https://gitee.com/airdark/nuxt-website'
        }
    ]
  }
]

program.version('1.0.0', '-v, --version')
    .command('create <projectName>')
    .action((projectName) => { // 当用户输入create命令时走这里
      // 这里我们就可以对用户输入的命令进行进行操作
      console.log(`用户想创建一个名为${projectName}的项目`)
      // 交互输入的参数为上面的question,res是得到答案
      inquirer.prompt(question).then(res => {
        console.log('用户选择的答案', res)
        spinner.start()
        // 获取到第一项中,用户选择的值,这里偷了个懒,大家可以根据答案和问题获取对应下载链接。
        let info = question[0].choices.find(item => item.value === res.features)
        download(`direct:${info.link}`, `./${projectName}`, {clone: true}, (err) =>{
          if (err) {
            spinner.fail()
            console.log(chalk.red('获取模版失败'))
          } else {
            spinner.succeed()
            console.log(chalk.green('Success!'))
          }
          spinner.stop()
        })
      })
    })
program.parse(process.argv);

运行命令结果如图所示

image.png

三、后记

以上我们就完成了我们开头处立下的目标!细心的同学可能发现了,咱们的版本号是写死的。这里咱们再说下如何动态获取版本号,即package.json中的版本号。因为node版本的问题,我只能这么来取了

/*** 处理根路径获取,为了得到package.json中的数据  start */
import fs from 'fs'

import path from 'path'
import { fileURLToPath } from 'url'
const __filenameNew = fileURLToPath(import.meta.url)

const __dirnameNew = path.dirname(__filenameNew)

let rootPath = __dirnameNew.slice(0, __dirnameNew.length - 3)

const packageJsonData = JSON.parse(fs.readFileSync(rootPath + 'package.json', 'utf8'))

// console.log(packageJsonData.version) 
// 替换到对应位置即可
/*** 处理根路径获取,为了得到package.json中的数据   end */

另外还有很多东西没做,比如说文件夹重名、根据用户指令给模版加配置等等,后续咱们继续说。

本篇完结!撒花!感谢观看! 希望能帮助到你!