手写vue 脚手架
手写vue 脚手架项目地址必备模块commander:参数解析--help借助它inquirer :交互式命令行工具 ,使用可以实现命令行选择功能download-git-repo:在git中下载模板metalsmith:读取所有文件,实现模板渲染consolidate:统一模板引擎git相关知识查看每次提交的信息git log、git show、git show 某个制定的commitId创建t
·
手写vue 脚手架
必备模块
- commander:参数解析 --help借助它
- inquirer :交互式命令行工具 ,使用可以实现命令行选择功能
- download-git-repo:在git中下载模板
- metalsmith:读取所有文件,实现模板渲染
- consolidate:统一模板引擎
git相关知识
-
查看每次提交的信息
git log
、git show
、git show 某个制定的commitId
-
创建tag并与commit建立连接
git tag 版本名 commitid
-
查看所有标签
git tag
-
将tag同步到远程服务器
git push -u origin main 标签名
-
删除tag
git tag -d tag名
-
GitHub API调用
-
查看某个用户下面所有的仓库
https://api.github.com/users/meet-you-123/repos
-
查看某个用户下面特定的仓库信息
https://api.github.com/repos/用户名/仓库名
-
查看某个特定仓库的版本信息
https://api.github.com/repos/用户名/仓库名/tags
-
项目开始
1.目录结构
├── bin
│ └── www // 全局命令执行的根文件
├── package.json
├── src
│ ├── constants.js // 存放常量
│ ├── create.js // create命令逻辑
│ ├── config.js // config命令逻辑
│ ├── main.js // 入口文件
│ └── utils // 存放工具方法
│── .huskyrc // git hook
│── .eslintrc.json // 代码规范校验
2.初始化项目
yarn init -y
// package.json
"bin":{
"tsc_cli":"./bin/www"
}
// ./bin/www.js
#! /usr/bin/env node //执行环境是node
require("../src/main.js")
//链接包到全局
yarn link
3.commander
yarn add commander
// ./src/main.js
const programe=require("commander")
programe.version("1.0.0").parse(process.env)
执行tsc_cli --help
4. 动态获取版本号
// ./src/constants.js
const {version}=require("../package.json")
module.exports={
version
}
// ./src/main.js
const programe=require("commander")
const {version}=require("./utils/constants")
programe.version(version).parse(process.env)
5.配置指令命令
// ./src/main.js
//配置三个指令命令
const mapActions={
create:{
alias:"c",
description:"create a project",
examples:[
"tsc_cli create <project-name>"
]
},
config:{
alias:"conf",
description:"config project variable",
examples:[
"tsc_cli configm set <k><v>",
"tsc_cli config get <k>"
]
},
"*":{
alias:"",
description:"command not found",
examples:[]
}
}
//循环创建命令
Reflect.ownKeys(mapActions).forEach((action)=>{
programe
.command(action)//配置指令名称
.alias(mapActions[action].alias)//命令别名
.description(mapActions[action].description)//命令的描述
.action(()=>{
//访问不到对应的命令 就打印找不到命令
if(action==="*"){
console.log(mapActions[action].description)
}else{
console.log(action)
}
})
})
//监听用户的help事件
programe.on("--help",()=>{
console.log("\nExamples:")
Reflect.ownKeys(mapActions).forEach((action)=>{
mapActions[action].examples.forEach((example)=>{
console.log(`${example}`)
})
})
})
6.create命令
// ./src/main.js
.action(()=>{
if(action==="*"){
console.log(mapActions[action].description)
}else{
require(path.resolve(__dirname,action))(...process.argv.slice(3))
}
})
// ./src/create.js
module.exports=async (proName)=>{
console.log(proName)
}
执行tsc_cli create project
可以打印出project
7.获取所有项目模板的所有名字
yarn add axios
// ./src/create.js
const axios=require("axios")
const fetchRepoList=async()=>{
//获取项目组织中所有的仓库信息,这个项目存放的都是项目模板
const {
data
}=await axios.get("https://api.github.com/users/meet-you-123/repos")
return data
}
module.exports=async ()=>{
let repos=await fetchRepoList()
repos=repos.map((itme)=>item.name)
console.log(repos)
}
8.列出项目名字,并选择inquirer&ora
//注意ora的版本问题
yarn add inquirer ora
// ./src/create.js
const ora=require("ora")
const Inquirer=require("inquirer")
module.exports=async()=>{
const spinner=ora("fetching template ....")
spinner.start()
let repos=await fetchRepoList()
spinner.succeed()
repos=repos.map((item)=>item.name)
const {
repo
}=await Inquirer.prompt({
name:"repo",//获取后选择的结果
type:"list",//什么方式显示在命令行
message:"please choice a template to create project",//提示信息
choices:repos,//选择的数据
})
console.log(repo)
}
9.获取版本信息
// ./src/create.js
//封装等待函数
const waitFetchAddLoadinng=(fn,message)=>async(...args)=>{
const spinner=ora(message);
spinner.start()
const result=await fn(...args)
spinner.succeed()
return result
}
const fetchTagList=async(repo)=>{
const {
data
}=await axios.get(`https://api.github.com/repos/meet-you-123/${repo}/tags`)
return data
}
let tags=await waitFetchAddLoadinng(fetchTagList,"fetching tags.....")(repo)
tags=tags.map((item)=>item.name)
const {
tag
}=await Inquirer.prompt({
name:"tag",//获取后选择的结果
type:"list",//什么方式显示在命令行
message:"please choice a template to create project",//提示信息
choices:tags,//选择的数据
})
10.下载项目
// ./src/constants.js
const downloadDirectory=`${process.env[process.platform==="darwin"?"HOME":"USERPROFILE"]}/.template`
module.exports={
downloadDirectory
}
yarn add download-git-repo
// ./src/create.js
const {promisify}=require("util")
let downloadGitRepo=require("download-git-repo")
downloadGitRepo=promisify(downloadGitRepo)//转成promise方法去使用
const {downloadDirectory}=require("./constants")
const download=async(repo,tag)=>{
let api=`meet-you-123/${repo}`
if(tag){
api+=`#${tag}`
}
const dest=`${downloadDirectory}/${repo}`
await downloadGitRepo(api,dest)
return dest
}
const target=await waitFetchAddLoadinng(download,'download template....')(repo,tag)
yarn add ncp//拷贝文件功能
let ncp=require("ncp")
ncp=promisify(ncp)
await ncp(result, path.resolve(proname))
11.模板编译
//模板目录
├── tem //里面存放的是要下载的模板
│ ├── package.json//定制模板中的package.json
├── meta.js
├── package.json
//在模板中添加mate.js以用来询问用户需要的信息值
const prompts = [{
name: "name",
type: "input",
required: true,
message: "Project name"
},
{
name: "author",
type: "input",
message: "Author",
},
{
name: "router",
type: "confirm",
message: "Install vue-router"
},
{
name: "store",
type: "confirm",
message: "Install vuex"
},
{
name: "vant",
type: "confirm",
message: "Install vant"
}
]
const filter = { //是否需要过滤的文件
"src/router/**/*": "router",
"src/store/**/*": "store",
"src/plugins/**/*": "vant"
}
module.exports = {
prompts,
filter
}
//例如package.json
{
"name": "{{ name }}",
"version": "1.0.0",
"main": "index.js",
"repository": "https://github.com/meet-you-123/vue_ts_cli_template.git",
"author": "{{ author }}",
"license": "MIT",
"scripts": {
"dev": "cross-env envMode=dev webpack serve --config ./build/webpack.dev.conf.js --color",
"build": "cross-env envMode=prod webpack --config build/webpack.prod.conf.js --color",
"build:test": "cross-env envMode=test webpack --config build/webpack.prod.conf.js --color"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/plugin-proposal-object-rest-spread": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@vue/compiler-sfc": "^3.2.21",
"autoprefixer": "^10.4.0",
"babel-loader": "^8.2.3",
"babel-plugin-import": "^1.13.3",
"cross-env": "^7.0.3",
"css-loader": "^6.5.1",
"dotenv": "^10.0.0",
"html-webpack-plugin": "^5.5.0",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"postcss-loader": "^6.2.0",
"style-loader": "^3.3.1",
"ts-loader": "^9.2.6",
"typescript": "^4.4.4",
"vue-loader": "^16.8.3",
"webpack": "^5.62.1",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.4.0"
},
"dependencies": {
"@babel/runtime": "^7.16.0",
"@babel/runtime-corejs3": "^7.16.0",
"axios": "^0.24.0",
"core-js": "^3.19.1",
"mustache": "^4.2.0",
{{#vant}}
"vant": "^3.2.8",
{{/vant}}
"vue": "^3.2.21",
{{#router}}
"vue-router": "4",
{{/router}}
{{#store}}
"vuex": "^4.0.2"
{{/store}}
}
}
//在项目中安装handlebars、consolidate模块
yarn add handlebars consolidate
//在./src/create.js中引入
const {
promisify
} = require("util")
let Handlebars = require("consolidate").handlebars
Handlebars = promisify(Handlebars)// 包装渲染方法
const MetalSmith = require('metalsmith'); // 遍历文件夹
if (!fs.existsSync(path.join(result, "meta.js"))) {
await ncp(result, path.resolve(proname))
} else {
// 复杂模板
await new Promise((resolve, reject) => {
MetalSmith(__dirname)
.source(result) //遍历下载的目录
.destination(path.resolve(proname)) //最终编译好的文件存放位置
.use(async (files, metal, done) => {
const {
prompts,
filter
} = require(path.join(result, "meta.js"))
const obj = await Inquirer.prompt(prompts)
console.log(obj);
const meta = metal.metadata()
Object.assign(meta, obj) //将询问的结果放到metadata中保证在下一个中间件中可以获取到
delete files["meta.js"]
const fileNames = Object.keys(files)
Object.keys(filter).forEach(glob => {
glob = "tem/" + glob
fileNames.forEach(file => {
file = file.replace(/\\/g, "/");
if (match(file, glob, {
dot: true
})) {
console.log('匹配文件');
glob = glob.replace("tem/", "")
file = file.replace(/\//g, "\\");
const condition = filter[glob];
if (!obj[condition]) {
delete files[file];
}
}
})
})
done()
})
.use((files, metal, done) => {
const obj = metal.metadata()
Reflect.ownKeys(files).forEach(async (file) => {
if (file.includes(".json") || file.includes(".js") || file.includes(".ts") || file.includes(".vue")) {
let content = files[file].contents.toString()
if (content.includes("{{")) {
content = await Handlebars.render(content, obj)
files[file].contents = Buffer.from(content)
}
}
})
done()
})
.build(err => {
if (err) {
reject()
} else {
resolve()
}
})
})
}
//注意meta.js和要渲染的文件不能在同一级文件夹中
//使用require("consolidate").handlebars进行渲染,而不是Mustache
//删除文件注意路径中的符号是否一致,要不然匹配不上
//这里的逻辑就是上面描述的那样,实现了模版替换!到此安装项目的功能就完成了。
项目发布
nrm use npm // 准备发布包
npm addUser // 填写账号密码
npm publish // 已经发布成功
更多推荐
已为社区贡献1条内容
所有评论(0)