该文章是在 node编写cli命令行(二)
的基础上继续编写的
1.编写 addcom 指令
编写一个 addcom 指令,例如当执行:vue-temp-cli addcom HelloWorld -d /src/view/main
时,会在/src/view/main
目录下新建一个 HelloWorld
组件
1.添加模板文件
新建一个 template 文件夹
lib
|--
index.js
template
|-- mockjs
`-- src
|-- base-ui
| `-- src
| `-- components
|-- components
| `-- hello-world.vue.ejs
|-- service
|-- store
`-- view
|-- login
| |-- login.vue.ejs
| `-- route.js.ejs
`-- main
hello-world.vue.ejs
文件
<%_ if (data) { _%>
<template>
<div class="<%= data.name %>">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: '<%= data.humpName %>',
components: {
},
mixins: [],
props: {
msg: {
type: String,
default: '<%= data.humpName %>'
}
},
data: function() {
return {
}
},
computed: {
},
watch: {
},
created() {
},
mounted() {
},
methods: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.<%- data.name %>{
}
</style>
<%_ } _%>
2.编写 addcom 指令
1.修改index.js文件
添加新建项目的 addcom 指令, 看下面的第5步
#!/usr/bin/env node
var create = require('./lib/create')
// 导入addcom.js文件
var addcom = require('./lib/addcom')
var program = require('commander');
// 1.添加版本
program.version(require('./package.json').version, '-v, --version')
// 2.添加 options 选项(可供后面定义的指令使用该选项,获取选项的属性 program.xxx )
program
.option('-d, --dir <dir>', '指定目录路劲,例如,src/view/main/。错误:/src/view/main/', './') // 获取 program.dir
// 3.添加create指令
.....
// 5.添加 addcom 指令,例如:vue-temp-cli addcom Xxx -d src/view/main/
program
.command('addcom <name>')
.description('add component, 例如:vue-temp-cli addcom XXX -d src/view/main/')
.action((name)=>{
addcom.addcompoent(name, program.dir)
})
// 4.添加help提示信息
.....
program.parse(process.argv);
2.编写 addcom.js 文件
该文件是addcom 指令 代码的具体实现过程。
1)获取模板需要的数据( 并且定义好:模板文件路径,生成目标文件的路劲 )
2)开始编译模板,使用(模板文件,数据),返回编译后的结果
3)把编译后的结果写到指定 生成目标文件的路劲
const utils = require('./utils')
/**
* 1.新建一个组件
* @param {} name 组件的名称 hello-world helloworld helloWorld HelloWorld Helloworld
* @param {*} dir 新建组件存放的目录(该路径不能以/开头)
*/
const addcompoent = async (name, dir)=>{
// 1.获取模板需要的数据
const data = utils.getTemplateData(name, dir)
// 模板文件路径
const templateFilePath = utils.resolveReallyPath('../template/src/components/hello-world.vue.ejs')
// 生成目标文件的路劲
const targetFilePath = utils.resolveRelativePath(`${dir}/${data.name}.vue`)
// 2.开始编译模板(模板文件,数据)
utils.compiler(templateFilePath, data)
// 3.编译成功,新建文件
.then((str)=>{
// 新建目录
utils.mkdirsSync(dir)
// 新建文件
utils.generateFile(targetFilePath, str)
})
// console.log(name, dir)
// console.log('utils=', utils.resolveReallyPath('../'))
// console.log('utils=', utils.resolveRelativePath(dir))
}
module.exports = {
addcompoent
}
3.安装模板引擎ejs
上面的模板编译使用到了ejs的模板引擎
npm install ejs
PS F:\blog\node-cli\vue-temp-cli> npm install ejs
> ejs@3.1.3 postinstall F:\blog\node-cli\vue-temp-cli\node_modules\ejs
> node --harmony ./postinstall.js
Thank you for installing EJS: built with the Jake JavaScript build tool (https://jakejs.com/)
npm WARN vue-temp-cli@0.0.1 No description
npm WARN vue-temp-cli@0.0.1 No repository field.
+ ejs@3.1.3
added 10 packages from 5 contributors in 2.364s
PS F:\blog\node-cli\vue-temp-cli>
4.编写 utils.js 的工具类
该工具类是用来获取 模板所需要的数据
.....
.....
const path = require('path')
const fs = require('fs')
// 模板引擎
const ejs = require('ejs')
const nameUtils = require('./name-utils')
const log = (str)=>{
console.log(chalk.green(str))
}
const errorLog = (str)=>{
console.log(chalk.red(str))
}
// 1.打印欢迎界面
......
// 2.克隆模板项目(Download a git `repository` to a `destination` folder with `options` )
......
// 3.获取可以 执行终端命令的 子线程
.......
// 4.获取绝对路径(就是这个文件所在的路径),__dirname 总是指向被执行 js 文件的绝对路径
const resolveReallyPath = (...file) => path.resolve(__dirname, ...file)
// 5.获取相对路径( ./ 是获取命令行执行命令时所在的路径)
const resolveRelativePath = (...file) => path.resolve('./', ...file)
/**
* 6.模板的编译
* @param {*} templateFilePath 模板文件的路劲
* @param {*} data ejs模板数据, { }
* @param {*} options ejs模板选项, { }
*/
const compiler = (templateFilePath, data={}, options={})=>{
return new Promise((resolve,reject)=>{
ejs.renderFile(templateFilePath, {data}, options, (err, str) => {
if (!err) {
// 编译成功
resolve(str)
} else {
// 编译失败
errorLog(err.message)
reject(err)
}
})
})
}
/**
* 7.生成文件
* @param {*} path 生成文件存放的路径
* @param {*} data 文件的字符串内容
*/
const generateFile = (path, data) => {
if (fs.existsSync(path)) {
errorLog(`${args.dir}组件已存在`)
// 退出程序
process.exit(0)
}
return new Promise((resolve, reject) => {
// 写文件到指定的文件下
fs.writeFile(path, data, 'utf8', err => {
if (err) {
errorLog(err.message)
reject(err)
} else {
resolve(true)
}
})
})
}
/**
* 8.获取ejs模板对应的数据
* @param {*} name
*/
const getTemplateData = (name,dirPath)=>{
// src/view/login/login 1; src/view/main/broad/broad 2;
let pathDir = dirPath+'/'+name
pathDir = pathDir.split('/').filter((v)=>{return v!==''}) // [src,view,login,login]
return {
// dirPath :getDirPath(name), // 新建组件的路劲
dirPath, // 新建组件的路劲 src/view/main/
name :nameUtils.getComponentDirName(name), // 组件的名称(小写) demo1btn 或者 demo1btn 或者 demo1-btn
humpName : nameUtils.getComponentName(name), // 组件的名称(首字母大写并驼峰命名) Demo1Btn
firLowName : nameUtils.getComponentNameFirLow(name), // 组件的名称(首字母小写,其它字符首字符大写)demo1Btn
routeLevel : nameUtils.getRouteLevel(pathDir.join('/')) -3 , // 组件路由的级别(1,2,3)
parentRouteName : nameUtils.getParentRouteName(name), // 组件父亲路由的名称 main 、 '' 、
}
}
/**
* 9.递归新建目录(已存在不用管,不存在就新建)
* @param {*} dirname /src/view/main/
*/
function mkdirsSync(dirname) {
// 存在,跳过
if (fs.existsSync(dirname)) {
return true
} else {
// 不存在,判断父亲文件夹是否存在?
if (mkdirsSync(path.dirname(dirname))) {
// 存在父亲文件,就直接新建该文件
fs.mkdirSync(dirname)
return true
}
}
}
module.exports = {
.....
.....
resolveReallyPath,
resolveRelativePath,
compiler,
getTemplateData,
generateFile,
mkdirsSync
}
5.编写 name-utils.js 工具类
/**
* 首字母 大写
* @param {*} name Name
*/
const upperCaseFirstName = (name) => {
return name.charAt(0).toUpperCase() + name.slice(1)
}
/**
* 首字母 小写
* @param {*} name name
*/
const lowerCaseFirstName = (name) => {
return name.charAt(0).toLowerCase() + name.slice(1)
}
/**
* 获取组件文件夹的名称
* @param {*} dirName dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
* @param return demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
*/
const getDirPath = (dirName) => {
// 2.转成小写
return dirName.toLowerCase()
}
/**
* 获取组件文件夹的名称
* @param {*} dirName dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
* @param return demo1btn 或者 demo1btn 或者 demo1-btn
*/
const getComponentDirName = (dirName) => {
// 1.先分词
const dirs = dirName.split('/')
const componentDirName = dirs.pop()
// 2.转成小写
return componentDirName.toLowerCase()
}
/**
* 获取组件的名称
* @param {*} dirName dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
* @param return // Demo1btn 或者 Demo1btn 或者 Demo1Btn
*/
const getComponentName = (componentName) => {
// 1.先分词
const names = componentName.split('/')
let compName = names.pop()
compName = compName.toLowerCase()
const compNames = compName.split('-')
for (let i = 0; i < compNames.length; i++) {
compNames[i] = upperCaseFirstName(compNames[i])
}
const name = compNames.join('')
return name
}
/**
* 获取组件的名称
* @param {*} dirName dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
* @param return // demo1btn 或者 demo1btn 或者 demo1Btn
*/
const getComponentNameFirLow = (componentName) => {
// 1.先分词
const names = componentName.split('/')
let compName = names.pop()
compName = compName.toLowerCase()
const compNames = compName.split('-')
for (let i = 0; i < compNames.length; i++) {
compNames[i] = upperCaseFirstName(compNames[i])
}
const name = compNames.join('')
return lowerCaseFirstName(name)
}
/**
* 获取是一级路由,还是二级路由,还是三级路由
* @param {*} dirName dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
* @param return // 1 或者 1 或者 2
*/
const getRouteLevel = (dirName) => {
// 1.先分词
const names = dirName.split('/')
return names.length
}
/**
* 获取父亲路由名称
* @param {*} dirName dirName = demo1btn 或者 demo/demo1btn 或者 demo/demo1-btn
* @param return // '' 或者 demo 或者 demo
*/
const getParentRouteName = (dirName) => {
// 1.先分词
const names = dirName.split('/')
let result = ''
if (names.length > 1) {
result = names[names.length - 2] // 倒数第二个
}
return result
}
// console.log(getParentRouteName('demo1btn'))
// console.log(getParentRouteName('demo/demo1btn'))
// console.log(getParentRouteName('demo/demo1-btn'))
// console.log(getParentRouteName('demo/Demo1-Btn'))
// console.log(getParentRouteName('demo/Demo1-btn/sd'))
module.exports = {
getDirPath,
getComponentName,
getComponentDirName,
getComponentNameFirLow,
getRouteLevel,
getParentRouteName,
upperCaseFirstName,
lowerCaseFirstName
}
3.测试addcom指令
执行 vue-temp-cli addcom hellow-World -d src/components
命令,就会在执行该命令的路劲下:新建src/components/
这个目录,然后在该目录下新建hellow-World.vue
文件。
# 在执行指令目录下下面一个`hellow-World.vue`文件
vue-temp-cli addcom hellow-World
# 在执行指令目录下新建src/components/文件夹,然后在该文件夹下面新建一个`hellow-World.vue`文件
vue-temp-cli addcom hellow-World -d src/components/
vue-temp-cli addcom hellow-World -d src/components
# 下面这种是错误的写法(不能以/开头)
vue-temp-cli addcom hellow-World -d /src/components
2.编写addPage 指令
1.添加模板
login.vue.ejs
<%_ if (data) { _%>
<template>
<div class="<%= data.name %>">
<h1>{{ msg }} Page</h1>
</div>
</template>
<script>
export default {
name: '<%= data.humpName %>',
components: {
},
mixins: [],
props: {
msg: {
type: String,
default: '<%= data.humpName %>'
}
},
data: function() {
return {
}
},
computed: {
},
watch: {
},
created() {
},
mounted() {
},
methods: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.<%- data.name %>{
}
</style>
<%_ } _%>
route.js.ejs
<%_ if (data) { _%>
// 普通加载路由
// import <%= data.humpName %> from './<%= data.name %>.vue'
// 懒加载路由
const <%= data.humpName %> = () => import(/* webpackChunkName: "<%= data.name %>" */ './<%= data.name %>.vue')
export default {
<%_ if (data.routeLevel === 1) { _%>
path: '/<%= data.name %>', // 一级路由前面多一个 /
<%_ } else { _%>
path: '<%= data.name %>', // 二级路由前面没有/
<%_ } _%>
name: '<%= data.name %>',
pname: '<%= data.parentRouteName %>', // 父亲路由的名称
level: <%= data.routeLevel %>, // <%= data.routeLevel %>级路由
component: <%= data.humpName %>,
children: [
]
}
<%_ } _%>
2.编写addPage指令
1.修改 index.js 文件
在第6步中添加 addPage 指令
#!/usr/bin/env node
var create = require('./lib/create')
var addcom = require('./lib/addcom')
var addPage = require('./lib/addPage')
var program = require('commander')
// 1.添加版本
program.version(require('./package.json').version, '-v, --version')
// 2.添加 options 选项(可供后面定义的指令使用该选项,获取选项的属性 program.xxx )
.....
// 3.添加create指令
.....
// 5.添加 addcom 指令,例如:vue-temp-cli addcom Xxx -d src/view/main/
.....
// 6.添加 addPage 指令,例如:vue-temp-cli addPage Xxx -d src/view/main/
program
.command('addPage <name>')
.description('add page component, 例如:vue-temp-cli addPage XXX -d src/view/main/')
.action((name)=>{
addPage.addPageCompoent(name, program.dir)
})
// 4.添加help提示信息
.....
program.parse(process.argv);
2.编写 addPage.js 文件
在 addPage.js 文件是 addPage 指令代码具体实现
1)自动生成.vue文件
1.获取模板需要的数据( 并且定义好:模板文件路径,生成目标文件的路劲 )
2.开始编译模板,使用(模板文件,数据),返回编译后的结果
3.把编译后的结果写到指定 生成目标文件的路劲
2)自动生成route.js文件
1.获取模板需要的数据( 并且定义好:模板文件路径,生成目标文件的路劲 )
2.开始编译模板,使用(模板文件,数据),返回编译后的结果
3.把编译后的结果写到指定 生成目标文件的路劲
const utils = require('./utils')
const addPageCompoent = (name, dir)=>{
// 1.自动生成.vue文件
addPageVue(name, dir)
// 2.自动生成route.js文件
addPageRoute(name, dir)
}
const addPageVue = (name, dir)=>{
// 1.获取模板需要的数据
const data = utils.getTemplateData(name, dir)
const templateFilePath = utils.resolveReallyPath('../template/src/view/login/login.vue.ejs')
const targetFilePath = utils.resolveRelativePath(`${dir}/${data.name}.vue`)
// 2.开始编译模板(模板文件,数据)
utils.compiler(templateFilePath, data)
// 3.编译成功后新建文件
.then((str)=>{
utils.mkdirsSync(dir)
utils.generateFile(targetFilePath, str)
})
}
const addPageRoute = (name, dir)=>{
// 1.获取模板需要的数据
const data = utils.getTemplateData(name, dir)
const templateFilePath = utils.resolveReallyPath('../template/src/view/login/route.js.ejs')
const targetFilePath = utils.resolveRelativePath(`${dir}/route.js`)
// 2.开始编译模板(模板文件,数据)
utils.compiler(templateFilePath, data)
// 3.编译成功后新建文件
.then((str)=>{
utils.mkdirsSync(dir)
utils.generateFile(targetFilePath, str)
})
}
module.exports = {
addPageCompoent
}
3.测试addPage指令
执行 vue-temp-cli addPage login -d src/view/login/
命令,就会在执行该命令的路劲下:新建src/view/login/
这个目录,然后在该目录下新建login.vue, 和 route.js
文件。
# 在执行指令目录下下面一个`login.vue`文件
vue-temp-cli addPage login
# 在执行指令目录下新建src/view/login/文件夹,然后在该文件夹下面新建一个`login.vue`文件
vue-temp-cli addPage login -d src/view/login/
vue-temp-cli addPage login -d src/view/login
# 下面这种是错误的写法(不能以/开头)
vue-temp-cli addcom hellow-World -d /src/view/login