准备工作(一些常用库)
ora
可以用于表示当前模板的状态
const oraIcon = ora().start(`正在下载模板\n`);
oraIcon.fail(`模板下载失败\n`);
oraIcon.succeed("模板下载成功");
figlet
粉笔字体库
const font = figlet.textSync('CLI-TOOL', {
font: 'bulbhead'
})
commander
用于在终端创建脚手架的执行指令
const program = require('commander')
program
.command('init template')
.description('初始化项目模板')
.action(async() => {
}
inquirer
添加选择列表
inquirer.prompt({
type: "list",
message: "请选择创建项目类型",
name: "select",
choices: [{
name: 'Electron项目模板',
value: 'electron'
},
{
name: 'PC项目模板',
value: 'pc'
}]
}).then((data) => {
}
log-symbols
为信息输出添加图标
console.log(logSymbols.error, chalk.green("模板下载成功"))
chalk
添加文字颜色和样式
download-git-repo
下载远程仓库模板,因为我的脚手架中使用的是本地模板,所以没有用这个库,通常需要下载一些github上的公共模板的时候,需要通过这个库来调用
const download = require('download-git-repo')
downloa(repository, ProjectName, options, callback)
repository:远程仓库地址
http://github.com:hyf940760301/cli-template#main (地址需要按照这种格式来定义,代码存放的网站地址:用户名/项目名#分支名)
projectName:存放下载的文件路径,可以直接是文件名,默认是当前目录
options:配置项,如{clone: boolean}
表示用http download还是git clone下载
callback:回调函数,可以在里面对操作过程进行一些设置,比如添加文本颜色,文案内容或增加一些图标等
实现脚手架的搭建
1. 初始化项目
本地创建文件夹后
进入文件夹根目录,npm初始化配置,生成package.json文件
cd ./cli-tool
npm init
2. 创建脚手架入口文件
一般在根目录创建bin文件夹
在bin文件夹中创建index.js
对不不是很复杂的脚手架,index.js文件中就是我们脚手架实现的全部核心代码了
#!/usr/bin/env node
console.log('this is cli-tool')
3. 修改入口指向,创建本地符号链接后测试指令
修改package.json中的内容
{
"name": "cli-tool",
"version": "0.0.1",
"description": "前端脚手架",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "npm run compile -- --watch"
},
"author": "xxx",
"license": "ISC",
"bin": {
"cli-tool": "./bin/index.js"
},
"dependencies": {
"babel-cli": "^6.26.0",
"babel-env": "^2.4.1",
"chalk": "^3.0.0",
"commander": "^5.0.0",
"figlet": "^1.5.2",
"glob": "^8.0.3",
"inquirer": "^7.1.0",
"log-symbols": "^3.0.0",
"ora": "^4.0.3"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.3",
"babel-env": "^2.4.1"
}
}
添加关联本地的符号链接,测试指令运行
(在项目的根目录执行, 然后就可以在我们本地C盘中node文件夹下找到我们新添加的指令了)
本地全局运行指令,查看打印结果
4. 走通整个流程后,我们开始改造入口文件,丰富脚手架的内容
const fs = require('fs')
const ora = require('ora')
const path = require('path')
const glob = require('glob')
const chalk = require('chalk')
const figlet = require('figlet')
const process = require('process')
const program = require('commander')
const inquirer = require('inquirer')
const logSymbols = require('log-symbols')
program
.version('0.1.0')
program
.command('init template')
.description('初始化项目模板')
.action(async() => {
const font = figlet.textSync('CLI-TOOL', {
font: 'bulbhead'
})
inquirer.prompt({
type: "list",
message: "请选择创建项目类型",
name: "select",
choices: [
{
name: 'Electron项目模板',
value: 'electron'
},
{
name: 'PC项目模板',
value: 'pc'
}
]
}).then((data) => {
console.log(`\n`)
if (data.select === 'electron') {
inquirer.prompt(defaultData.project).then((answers) => {
function copyDir(src, dist, callback) {
fs.access(dist, function(err){
if(err){
fs.mkdirSync(dist);
}
_copy(null, src, dist);
});
function _copy(err, src, dist) {
if(err){
callback(err);
} else {
try {
const paths = fs.readdirSync(src)
paths.forEach(function(_path) {
const _src = path.join(src, _path);
const _dist = path.join(dist, _path);
fs.stat(_src, function(err, stat) {
if(err){
callback(err);
} else {
if(stat.isFile()) {
fs.writeFileSync(_dist, fs.readFileSync(_src));
} else if(stat.isDirectory()) {
copyDir(_src, _dist, callback)
}
}
})
})
} catch (err) {
callback(err)
}
}
}
}
const dirPath = process.cwd();
console.log(dirPath)
const fileList = glob.sync('*')
console.log(fs.readdirSync(dirPath))
if (fileList.some((element) => element === answers.name)) {
console.log(logSymbols.error, `当前目录下存在同名文件夹\n`)
console.log(logSymbols.error, `创建失败\n`)
} else {
try {
fs.mkdirSync(`${dirPath}${answers.name}`)
console.log(logSymbols.success, `目录创建成功\n`)
const oraIcon = ora().start(`正在下载模板\n`);
const dirName = __dirname.split('')
dirName.splice(-4, 4)
const resultDirName = dirName.join('')
copyDir(path.join(resultDirName, 'packages'), path.join(dirPath, answers.name), (err) => {
if (err) {
oraIcon.fail(`模板下载失败\n`);
}
})
setTimeout(() => {
oraIcon.succeed("模板下载成功");
console.log(chalk.green(`
======================================================\n
进入项目根目录: cd ./${answers.name}\n
安装项目依赖: yarn install\n
启动项目: yarn run serve 或 npm run serve\n
======================================================
`))}, 2000)
} catch (err) {
console.log(logSymbols.error, chalk.green("目录创建失败"))
}
}
})
}
})
})
if (!process.argv.slice(2).length) {
program.outputHelp()
}
program.parse(process.argv)
5. 我的脚手架实现思考
在实现脚手架的过程中,我个人觉得基本上可以分为两种方式:
- 先下载一个通用的公共模板,在公共模板的基础上,将结合业务的配置以及新增文件添加到公共模板当中,我有大致看过vue-cli的代码结构,很复杂,但是基本的结构也是这种模式,通过在一个基础模板上增加各种配置项来实现定制化的项目结构。
- 第二种方式其实是我自己想到的一种方式,将需要的项目模板整体添加在脚手架工具中(删除不必要的文件项,比如node-modules),通过node的文件系统来实现将项目模板整体复制到本地目标的路径当中。相比于第一种来说,实现成本比较低,我认为比较适合需要快速成型一个直接可用的内部项目结构。
function copyDir(src, dist, callback) {
fs.access(dist, function(err){
if(err){
fs.mkdirSync(dist);
}
_copy(null, src, dist);
});
function _copy(err, src, dist) {
if(err){
callback(err);
} else {
try {
const paths = fs.readdirSync(src)
paths.forEach(function(_path) {
const _src = path.join(src, _path);
const _dist = path.join(dist, _path);
fs.stat(_src, function(err, stat) {
if(err){
callback(err);
} else {
if(stat.isFile()) {
fs.writeFileSync(_dist, fs.readFileSync(_src));
} else if(stat.isDirectory()) {
copyDir(_src, _dist, callback)
}
}
})
})
} catch (err) {
callback(err)
}
}
}
}
const dirPath = process.cwd();
const fileList = glob.sync('*')
if (fileList.some((element) => element === answers.name)) {
console.log(logSymbols.error, `当前目录下存在同名文件夹\n`)
console.log(logSymbols.error, `创建失败\n`)
} else {
try {
fs.mkdirSync(`${dirPath}${answers.name}`)
console.log(logSymbols.success, `目录创建成功\n`)
const oraIcon = ora().start(`正在下载模板\n`);
const dirName = __dirname.split('')
dirName.splice(-4, 4)
const resultDirName = dirName.join('')
copyDir(path.join(resultDirName, 'packages'), path.join(dirPath, answers.name), (err) => {
if (err) {
oraIcon.fail(`模板下载失败\n`);
}
})
setTimeout(() => {
oraIcon.succeed("模板下载成功");
console.log(chalk.green(`
======================================================\n
进入项目根目录: cd ./${answers.name}\n
安装项目依赖: yarn install\n
启动项目: yarn run serve 或 npm run serve\n
======================================================
`))}, 2000)
} catch (err) {
console.log(logSymbols.error, chalk.green("目录创建失败"))
}
}
最后: 其实我的这个方法比较简单粗暴,在没有那么复杂的需求前提下,实现成本更低一些,但是势必性能以及脚手架体积会随着项目模板不断地添加变得更大,而且没有办法优化,这一点比较致命,因为实现的本质就是文件夹的整体复制,所以比较适合小型团体,如果是对于十几种项目类型甚至更多的话,就不太适合了,还是得老老实实的按照第一种通用的方式来进行定制化。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)