市面上已经有很多很成熟很好用的脚手架,如vue-cli,为什么还会想到自己写自定义脚手架呢?

因为可按公司的需求,编写适合公司开发的脚手架,统一规范项目结构,方便维护,优化避免互相拷贝代码,导致bug,增加不必要的开发时间,扩散减少重复代码,降低门槛,更方便快速开启新的项目。

开始编写前,需要提前,按照自己需求先定义,项目模板,并上传到github上

步骤一.创建工程化

1.创建目录并初始化获取package.json

npm init -y

2.在package.json 中设置在命令下执行‘构建指令:如 test-cli’ 时调用bin目录下的test-cli.js文件

//package.json
{...,"bin": {"test-cli": "./bin/test-cli.js"},
}

3.test-cli.js文件中使用作为入口文件,并以node环境执行次文件

#! /usr/bin/env node
require('../src/main.js')

4.建立软链接,全局使用

npm link

二.配置指令命令

自定义脚手架需要用到一下几个模块

commander          参数解析 --help其实就是借助了它

inquirer                 交互式命令行工具,有它就可以实现命令行的选择功能

ora                        加载loading

axios                     获取仓库模板信息

download-git-repo 在git中下载模板

metalsmith            读取所有文件,实现模板渲染

consolidate           统一模板引擎

ejs                        ejs模板,生成package

可以依次安装这些模块

1.写入指令配置表mapAcitions和入口文件main.js配置指令命令

main.js(./src/main.js)

//引入commander
const program = require('commander');
//引入path
const path = require('path');
//引入配置命令表
const mapAcitions = require('../config/mapAcitions');//使用Reflect,好处是可以遍历Symbol
Reflect.ownKeys(mapAcitions).forEach((action) => {const comm = mapAcitions[action];program.command(action)//命令指令 .alias(comm.alias) //命令别名.description(comm.description) //备注.action(() => {//自定义处理逻辑if (action === '*') { console.log('*',comm.description)} else {//截取命令action,//写入参数process.argv.slice(3)//执行命令对应文件require(path.resolve(__dirname,action))(...process.argv.slice(3))}})});program.on('--help',()=>{console.log('\r\nExamples:');Reflect.ownKeys(mapAcitions).forEach((action)=>{const {examples} = mapAcitions[action];examples.forEach(example=>{console.log(example)})})
});//获取package.js基本信息
const pak = require('../package.json');
//动态写入基本信息
program.name(pak.name).usage(`<command> [option]` ).version(pak.version).parse(process.argv);

注意:program.parse(process.argv),是必写,还要在最后写入,它注册它前面的内容

mapAcitions(./congif/mapAcitions)


module.exports = {create:{alias:'c',//别名description:'create a porject (创建一个项目)',//备注examples:[//指令'test-cli create <porject-name>']},config:{alias:'conf',description:'config project variable (项目变量配置)',examples:['test-cli config set<k><v>','test-cli config get<k>']},'*':{alias:'',description:'commander not founf (没有该指令)',examples:[]  }
}

3.编写create 指令

//创建create
const path = require('path');
//引入axios
const axios = require('axios');
//引入inquirer //node使用8.0.0旧版本用require引入
const inquirer = require('inquirer');
//引入ora //node使用5.0.0旧版本用require引入
const ora = require('ora');//包装一下downloadGitRepo
//promisify //包装成为es6的promise方法
const {promisify} = require('util')
let downloadGitRepo = require('download-git-repo');
downloadGitRepo = promisify(downloadGitRepo);
//downloadDirextory
const downloadDirextory = `${process.env[process.platform === 'win32'?'HOME':'USERPROFILE']}./template`;//ncp 拷贝模板
let ncp = require('ncp');
ncp = promisify(ncp);const fs = require('fs');
//引入metalsmith 遍历所有文件目录配合json渲染,只要模板渲染都需要
const MetalSmith = require('metalsmith');
const { down } = require('inquirer/lib/utils/readline');
//引入consolidate 返回渲染函数render 统一所有模板引擎
let  {render} = require('consolidate').ejs
render = promisify(render)//拉取仓库模板信息
const fetchRepoList = async ()=>{const {data} = await axios.get("仓库模板地址/repos");return data
}
//获取仓库版本信息
const fetchTagList = async (repo)=>{const {data} = await axios.get(`仓库模板地址/${repo}/tags`);return data
}
//临时存放目录
const download = async(repo,tag) => {let api = `test-cli/${repo}`;//下载项目地址if(tag){api += `${tag}`;}const dest =`${downloadDirextory}/${repo}`;await downloadGitRepo(api,dest) //将模板下载对应的目录中
}
//封装loading
const waitFnLoading = (fn,message) => async (...args)=>{//添加loadingconst spinner = ora(message);spinner.start();//loading开始const repos = await fn(...args);spinner.succeed();//loading结束return repos
}
module.exports= async(projectName)=>{//拉取模板信息let repos = await waitFnLoading(fetchRepoList,'正在获取模板信息.....')();//选择模板repos = repos.map(item=>item.name);const {repo} = await inquirer.prompt({name:'repo',//获取选择后的结果type:'list',//什么方式显示在命令行message:'please chiose a template to create project(请选择模板创建项目)',//提示信息choices: repos,//选择的数据 })//抓取tag列表let tags = await waitFnLoading(fetchTagList,'正在获取版本信息.....')(repo);//选择模板tags = tags.map(item=>item.name);const {tag} = await inquirer.prompt({name:'tag',//获取选择后的结果type:'list',//什么方式显示在命令行message:'please chiose a template to create project(请选择创建对应版本)',//提示信息choices: tags,//选择的数据 })//下载目录 返回一个临时存放目录const result = await waitFnLoading(download,'正在下载模板')(repo,tag);console.log(repo,tags)//判断复杂的模板if(!fs.existsSync(path.join(result,'ask.js'))){//简单模板//将下载的文件拷贝到当前执行命令的目录下await ncp(result,path.join(path.resolve(),projectName));}else{//复杂的模板需要渲染后再拷贝//需要用户选择,选择后编译模板//1.让用户填写信息await new Promise((resolve,reject)=>{MetalSmith(__dirname)//如果传入路径,会遍历当前文件的src文件.source(result) // 遍历result文件.destination(path.resolve(projectName))//编辑要去的地方.use(async (files,metal,done)=>{//files 就是现在所有的文件//拿到提前配置好的信息 传下去渲染const args = require(path.join(result,'ask.js'));//拿到后让用户填写 返回填写的信息const obj = await inquirer.prompt(args)const meta = metal.metadata();//获取的信息合并传入下一个useObject.assign(meta,obj);//合并对象//aks.js文件无用了delete files['ask.js']done();}).use((files,metal,done)=>{//根据用户信息,渲染模板const obj = metal.metadata();Reflect.ownKeys(files).forEach(async(file)=>{if(file.includes("js") || file.includes("json")){//文件内容let content = files[file].contents.toString();//判断是不是模板if(constent.includes("<%")){content = await render(content,obj);//渲染files[file].contents = Buffer.from(content)}}})done();}).build(err => {if(err){reject();}else{resolve();}})})}
}

对于简单的项目可以直接把下载好的项目拷贝到当前执行命令的目录即可。

npc这里也需要做的严谨一些,判断一下当前目录下是否有重名文件等。。。。还有很多细节也需要多次创建项目是否需要利用已经下载好的模板,按照项目需要编写

4.项目模板中增加ask.js

module.exports=[{type:'confirm',name:'private'message:'ths resgistery is private?'    },{type:'input',name:'author'message:'author?'    },{type:'input',name:'description'message:'description?'    },{type:'input',name:'license'message:'license?'    }
]

根据相对应的询问生成最终的package.json

7.项目模板添加下载模板可以只用ejs模板

{"name": "test-cli","version": "0.0.1","private":"<%=private&>""description": "<%=description&>",,"main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"bin": {"test-cli": "./bin/test-cli.js"},"keywords": [],"author": "<%=author&>","license": "<%=license&>","dependencies": {"axios": "^1.3.4","commander": "^10.0.0"},"devDependencies": {"inquirer": "^8.0.0","ora": "^5.0.0"}
}

核心原理就是将下载的模板文件,依次遍历根据用户填写的信息渲染模板,将渲染好的结果拷贝到执行命令的目录下

8.发布脚手架

npm addUser // 注册账号密码

npm login // 登录账号

npm publish // 发布

js配置自定义脚手架相关推荐

  1. 我开发了一个基于 Egg.js 的后端脚手架

    背景 之前基于 Egg.js 开发了几个项目,发现每个项目中都有配置文件.数据库连接操作.数据模型定义.微信登陆授权处理等功能,而做新项目时总会复制之前的项目来删删改改,有时候在 A 项目中添加了一个 ...

  2. vue-cli的webpack模版,相关配置文件dev-server.js与webpack.config.js配置解析

    1.下载vue-cli [html] view plain copy npm install vue-cli -g vue-cli的使用与详细介绍,可以到github上获取https://github ...

  3. Vue Cli3 项目 vue.config.js 配置

    Vue Cli3 项目 vue.config.js 配置 配置优化 一.js文件最小化处理 二.分割代码 三.图片资源压缩 四.开启gzip压缩 先看一下优化配置之前的文件大小 通过vue-cli3脚 ...

  4. ThinkPHP框架配置自定义的模板变量(十)

    原文:ThinkPHP框架配置自定义的模板变量(十) 模板替换(手册有详细介绍对应的目录) __PUBLIC__:会被替换成当前网站的公共目录 通常是 /Public/ __ROOT__: 会替换成当 ...

  5. Vue.js 介绍及其脚手架工具搭建

    vue.js介绍 (MVVM.核心思想) vue.js 是一套轻量级的 MVVM 的渐进式框架.Vue 的核心库只关注视图层. vue.js 的官方网址是:点我,我是网址 MVVM 介绍 MVVM 全 ...

  6. R语言构建xgboost模型:交叉验证(cross validation)训练xgboost模型,配置自定义的损失函数评估函数并使用交叉验证训练xgboost模型

    R语言构建xgboost模型:交叉验证(cross validation)训练xgboost模型,配置自定义的损失函数(loss function).评估函数(evaluation function) ...

  7. 使用PHPStorm 配置自定义的Apache与PHP环境

    使用PHPStorm 配置自定义的Apache与PHP环境之一 关于phpstorm配置php开发环境,大多数资料都是直接推荐安装wapmserver.而对于如何配置自定义的PHP环境和Apache则 ...

  8. Knative 实战:如何在 Knative 中配置自定义域名及路由规则

    作者 | 元毅 阿里云智能事业群高级开发工程师 当前 Knative 中默认支持是基于域名的转发,可以通过域名模板配置后缀,但目前对于用户来说并不能指定全域名设置.另外一个问题就是基于 Path 和 ...

  9. SpringBoot之配置自定义新建文件

    SpringBoot之配置自定义新建文件 适用于自定义mapper.xml文件 1.找到相关设置 2.点击+,新建一个模板 3.mapper.xml文件的模板我放在图片下面了 复制粘贴到上图右侧中间黑 ...

最新文章

  1. 2017年7个主要的金融行业数据趋势
  2. 静态库与动态库详细剖析
  3. Floating Window 详解
  4. linux6的关机快捷键是,桌面应用|Fedora GNOME 的常用快捷键
  5. 从零开始学习docker(十八)Swarm mode 部署wordpress
  6. [Shell]条件判断与流程控制:if, case, for, while, until
  7. linux usb 升级脚本,linux – 使用bash脚本更新CRON
  8. 从qplot开始入门
  9. OSL LLVM 3.3 Related Changes
  10. 【EXCEL批量查询手机号归属地小技巧】很多网友想看excel怎么批量查询手机号归属地,今天它来了
  11. PyCharm怎么来更新pip
  12. android 被自动安装cibn合一,Android v4.2.2 ROOT方法,可能也适用于很多同版系统的安卓电视!...
  13. php搜索功能与jquery搜索功能,JavaScript_基于jQuery实现页面搜索功能,jQuery实现页面搜索,搜索筛选 - phpStudy...
  14. 计算机标题怎么操作,电脑的ppt 一级,二级,三级标题怎么设置
  15. Java - 为什么Java不支持运算符重载?
  16. ArcGIS提取NDVI
  17. 健身房有哪些令人讨厌的行为?
  18. 火车头采集ajax网址的技巧,使用post方法获得采集网址
  19. Jboot框架的使用
  20. MikTex中如何使用BibTeX添加参考文献

热门文章

  1. 计算机 中的位、字节和字
  2. 登记在未成年子女名下的房产离婚时如何处理?
  3. 西南交通大学计算机专业考研报录比,考研报录比分析|这所西南的211工科院校,实力强劲...
  4. 中国版类似Google Earth软件问世(组图)
  5. 图书管理系统需求规格说明书
  6. 【机器学习】SVM多分类问题及基于sklearn的Python代码实现
  7. vue3 antd 每次点击不同按钮对话框里的数据都是一样的
  8. 在线支付系列【1】支付演变史
  9. LINUX下USB1.1设备学习小记(2)_协…
  10. 声卡注册流程(linux-5.4)