脚手架搭建及发布npm

Posted 面条请不要欺负汉堡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了脚手架搭建及发布npm相关的知识,希望对你有一定的参考价值。

一.为什么需要脚手架?

  • 减少重复性的工作,不再从零创建一个项目,或者复制粘贴另一个项目的代码 。
  • 根据动态交互生成项目结构和配置文件,具备更高的灵活性和人性化定制的能力 。
  • 有利于多人开发协作,避免了人工传递文件的繁琐。
  • 可以集成多套开发模板,根据项目需要选择合适的模板。

二.第三方库的支持

实现一个脚手架,通常需要以下工具

  • commander: 命令行工具
  • download-git-repo: 来通过git下载项目模板的插件
  • inquirer: 用于命令行交互问询等
  • ora: 用于实现node命令环境的loading效果,并显示各种状态的图标,显示 loading 动画
  • chalk: chalk是一个颜色的插件。可以通过chalk.green(‘success’)来改变颜色。修改控制台输出内容样式
  • log-symbols: 用于在打印输出的内容中加入icon更友好(显示出 √ 或 × 等的图标)
  • handlebars.js: 模板引擎工具,可用于修改package.json中相关字段
  • child_process:

三.手脚架的项目结构

├── bin  
    ├── www                            脚手架的入口文件
├── commands                           定义命令
    ├── add.js                         定义新增项目模板命令 add
    ├── delete.js                      定义删除项目模板命令 delete
    ├── init.js                        初始化项目init
    ├── list.js                        目前存在的模板列表
├── node_modules                       npm install 生成
├── templates                          存放模板静态资源
├── util
    ├──showTable.js                     控制台显示的table布局
├── .npmignore                          忽略需要发布的文件
├── package.json                        package.json
└── template.json                      存放模板列表的json数据

四.创建

1. 新建ydframe-cli文件

(ydframe-cli 脚手架的名称)
在该目录下执行 npm init -y 进行初始化,生产 package.json 文件

2. 安装第三方工具库

npm install chalk commander download-git-repo inquirer ora log-symbols

或是 执行修改package.json中把相应的开发依赖写入

"dependencies": {
    "child_process": "^1.0.2",
    "chalk": "^4.1.2",
    "cli-table": "^0.3.6",
    "commander": "^4.1.1",
    "inquirer": "^7.0.4",
    "download-git-repo": "^1.1.0",
    "log-symbols": "^4.0.0",
    "ora": "^3.2.0"
},

然后再执行npm i

3. 在根目录下新建bin/www【整个脚手架的入口文件】

#!/usr/bin/env node
console.log('hello world')

执行 node ./bin/www ,在控制台就会打印出 hello world。

4. package.json 进行命令配置

{
  ...
  "bin": {
 	"per": "bin/www"
	}
  ....
}

执行 npm link将命令挂载到全局,然后再输入 per 就可以到达刚才node ./bin/www 的效果了

5. 定义多个命令

再 bin 下面的 cm 文件夹来定义多个命令,此时就用到 commander 了。首先我们来看一下 commander 的用法

usage(): 设置 usage 值
command(): 定义一个命令名字
description(): 设置 description 值
option(): 定义参数,需要设置“关键字”和“描述”,关键字包括“简写”和“全写”两部分,以”,”,”|”,”空格”做分隔。
parse(): 解析命令行参数 argv
action(): 注册一个 callback 函数
version() : 终端输出版本号

根据日常开发需要,我们创建以下几个脚手架命令:

  • add 新增一个项目模板
  • delete 删除一个项目模板
  • list 列举所以项目模板
  • init 初始化一个项目模板

编写一下 www 文件

#!/usr/bin/env node
// 声明全局对象 program 变量(Commander 提供了一个全局对象)
const program = require('commander')

// 定义脚手架的用法,在program.help方法中会显示
program.usage('<command>')

// 获取package.json中的version来做为项目的版本号 
program.version(require('../package').version)

// 新增一个项目模板
program
	.command('add')
	.description('add a new template')
	.action(() => {
		console.log('新增项目模板业务')
		require('../commands/add')
	})
// 删除项目模板
program
	.command('delete')
	.description('delete a template')
	.action(() => {
		console.log('删除项目模板业务')
		require('../commands/delete')
	})
//  list 列举所以项目模板
program
	.command('list')
	.description('List the templateList')
	.action(() => {
		console.log('列表项目模板')
		require('../commands/list')
	})
// init 初始化一个项目模板
program
	.command('init')
	.description('init a project')
	.action(() => {
		console.log('项目初始化')
		require('../commands/init')
	})

// 处理参数,没有被使用的选项会被存放在program.args数组中
program.parse(process.argv)


// 如果有选项被放在program.args,即没有被program.parse处理,则默认使用program.help()将npm包可以执行的命令打印出来
// 可以通过program.on('--help', function(){})来自定义help
if (program.args.length) {
    program.help()
}

/*
command为执行的命令
description为命令的描述
alias为简写
action为命令相应的操作

*/

6. 创建模板command,编写指令

(1)编写指令- inquirer

在这里会用到 inquirer 进行命令行交互,先来看下 inquirer 的用法,它有以下参数可以配置

type:表示提问的类型,包括:input, confirm, list, rawlist, expand, checkbox, password, editor;
name: 存储当前问题回答的变量;
message:问题的描述;
default:默认值;
choices:列表选项,在某些type下可用,并且包含一个分隔符(separator);
validate:对用户的回答进行校验;
filter:对用户的回答进行过滤处理,返回处理后的值;
transformer:对用户回答的显示效果进行处理(如:修改回答的字体或背景颜色),但不会影响最终的答案的内容;
when:根据前面问题的回答,判断当前问题是否需要被回答;
pageSize:修改某些type类型下的渲染行数;
prefix:修改message默认前缀;
suffix:修改message默认后缀。

语法结构如下:

const inquirer = require('inquirer')

const question = [
  // 具体交互内容
]

inquirer.prompt(question).then((answers) => {
  console.log(answers) // 返回的结果
})

(2)创建模板
根目录下建立\\command文件夹【专门用来存放命令处理文件】
A 添加模板
进入\\command并新建add.js

  • 通过命令行交互,让用户输入模板名称和模板的地址
  • 将用户输入的模板信息新增写入到template.json文件中
  • 打印出所有的项目模板
  • 从git 下载到本地
#!/usr/bin/env node

const inquirer = require('inquirer')
const fs = require('fs')
const path = require("path");
const templateList = require(`${__dirname}/../template`)
const { showTable } = require(`${__dirname}/../util/showTable`)
const symbols = require('log-symbols')
const { exec } = require('child_process')
const chalk = require('chalk')
chalk.level = 3

let question = [
    {
        name: 'name',// 问题对应的属性
        type: 'input',// 问题类型为填空题
        message: '请输入模板名称', // 问题描述
        validate(val) {
            if (!val) {
                return chalk.red('name is required!')
            } else if (templateList[val]) {
                return 'Template has already existed!'
            } else {
                return true
            }
        }
    },
    {
        name: 'url',
        type: 'input',
        message: '请输入模板地址',
        validate(val) {
            if (!val) {
                return chalk.red('The url is required!')
            }
            return true
        }
    }
]
inquirer.prompt(question).then((answers) => {
    let { name, url } = answers;

    // 打印再控制台上
    templateList[name] = url.replace(/[\\u0000-\\u0019]/g, '') // 过滤 unicode 字符
    fs.writeFile(`${__dirname}/../template.json`, JSON.stringify(templateList), 'utf-8', err => {
        if (err) console.log(chalk.red(symbols.error), chalk.red(err))
        console.log('\\n')
        console.log(chalk.green(symbols.success), chalk.green('Add a template successfully!\\n'))
        console.log(chalk.green('The latest templateList is: \\n'))
        showTable(templateList)
    })
    //从git下载到本地指定路径
    exec(`git clone ${url} ` + path.resolve(__dirname, `../templates/${name}`), (error, stdout, stderr) => {
        if (error) { // 当有错误时打印出错误并退出操作
            console.log(chalk.red(error))
            process.exit()
        }
        console.log(chalk.green('初始化完成'))
        process.exit() // 退出这次命令行操作
    })
})
在这里还用到以下两个第三方库,原来美化相互效果:
  • chalk:用来修改控制台输出内容样式的,比如颜色 log-symbols:
  • 显示出 √ 或 × 等的图标

此时,执行 per add ,并输入项目模板名称和地址,就能看到以下效果了

本地的文件

B 删除模板
进入\\command并新建delete.js

  • 通过命令行交互,让用户输入要删除的项目模板名称
  • 删除用户输入的模板数据,然后再将更新的数据写入到template.json文件中
  • 打印出所有的项目模板
  • 删除本地文件

此时,执行 per delete ,并输入项目模板名称,就能看到以下效果了
C 项目列表
进入\\command并新建list.js
列举所有的项目模板,这个就更简单了,直接上代码

#!/usr/bin/env node
const { showTable } = require(`${__dirname}/../util/showTable`)
const templateList = require(`${__dirname}/../template`)

showTable(templateList)


D 初始化安装项目模板
初始化一个项目模板,这是最重要的一部分,步骤如下

  • 通过命令行交互,让用户模板的名称和项目的名称
  • 校验模板是否存在,项目名称是否填写 开始下载模板,
  • 显示加载图标 完成模板下载,隐藏加载图标
    用到download-git-repo 下载远程模板,它的使用方法如下
const download = require('download-git-repo')
download(repository, destination, options, callback)
/*
repository 是远程仓库地址
destination 是存放下载的文件路径,也可以直接写文件名,默认就是当前目录
options 是一些选项,比如 { clone:boolean } 表示用 http download 还是 git clone 的形式下载。
callback 是回调函数
*/

init.js

#!/usr/bin/env node
const inquirer = require('inquirer')
const fs = require('fs')
const ora = require('ora')
const path = require('path');
const download = require('download-git-repo')
const templateList = require(`${__dirname}/../template`)
const symbols = require('log-symbols')
const chalk = require('chalk')
const spinner = ora(chalk.green('下载中...'));
chalk.level = 3
const cwd = process.cwd();
// 获取项目模板list
let list = [];
for (let key in templateList) {
    list.push(key)
}
let question = [
    {
        name: "projectTemplate",
        message: "请选择项目模板?",
        type: 'list',
        choices: list,
        validate(val) {

        }
    },
    {
        type: 'input',
        name: 'projectName',
        message: '请填写项目名称',
        validate(val) {
            if (!val) {
                return chalk.red('projectName is required!')
            } else {
                return true
            }
        }
    }
]

inquirer.prompt(question).then((answers) => {
    let { name, url } = answers;
    // 获取当前路径
    const targetDir = path.resolve(cwd, answers.projectName || '.');
    // 判断当前路径下是否有这个文件夹
    if (!fs.existsSync(targetDir)) {
        downloadGitRepo(answers);
    } else {
        console.log(chalk.bgRed('当前路径已存在同名目录,请确定后再试'));
    }
})
/**
 * 下载 git 仓库
 * @param {项目名} projectName
 */
function downloadGitRepo(answers) {
    const { projectTemplate, projectName } = answers;
    console.log(chalk.blue('正在拉取' + projectTemplate + '项目模板'));
    // 出现加载图标
    spinner.start();
    //git 上的地址
    let url = templateList[projectTemplate]
    download(
        `direct:${url}`,
        `./${projectName}`,
        { clone: true },
        function (err) {
            if (err) {
                // 下载失败
                spinner.fail(chalk.bgRed('下载失败'));
            } else {
                // 下载成功
                // 获取当前路径
                // 结束加载图标
                spinner.succeed(chalk.bgGreen('下载成功'));
                console.log('\\n To get started')
                console.log(`\\n    cd ${projectName} \\n`)
            }
        }
    );
   
}

运行per init

五.发布到 npm

(1)发布流程

发布执行npm login , npm publish
由于发布名字重复了,所以改了文件名

六 使用

npm i scanper-cli -g 全局安装脚手架

执行scanper -h,就说明脚手架已经发布并安装成功了

七 .参考材料

从零搭建一个前端cli脚手架并发布到npm
教你从零开始搭建一款前端脚手架工具
前端工程化 搭建自己的脚手架工具详细步骤
搭建一个自己的前端脚手架(一)
inquirer.js —— 一个用户与命令行交互的工具

以上是关于脚手架搭建及发布npm的主要内容,如果未能解决你的问题,请参考以下文章

React脚手架搭建及创建项目

用脚手架 create-react-app 搭建 react 项目及各种组件库的安装

VUE脚手架搭建运行及部署

vue搭建项目的步骤及详解

最详细安装Vue脚手架及快速图形可视化搭建Vue项目

最详细安装Vue脚手架及快速图形可视化搭建Vue项目