背景:当我们每次要创建一个新的vue项目时都得使用vue create创建最基础的模板然后从头开始配置,得创建常用的目录结构、配置vue.config.js、对网络请求axios进行安装并二次封装、配置router等。呜呜呜有没有专属于懒人的方法让我们不用每次创建一个新的项目时都重复这些活呢!!!
哦天哪!!为什么我们不搭个脚手架呢!!把这些东西都一次性配置好,之后我们只要一行命令就可以搞定!
开摆,不,开干!
首先我们得明确我们的脚手架都需要提供哪些功能。
- 当我们安装完这个脚手架后,我们使用命令ckt create your_project_name就可以创建一个配置好的vue项目(ckt为我的脚手架里自定义的命令名称)
- 创建项目过程中支持自动拉取项目模板、安装项目依赖、打开浏览器
http://localhost:8080/
、自动启动项目 - 开发中支持命令式 创建Vue组件、创建Vue页面并配置路由、创建Vuex子模块
创建Vue组件:
ckt addcpn YourComponentName # 例如ckt add NavBar,默认会存放到src/components文件夹中
ckt addcpn YourComponentName -d src/pages/home # 也可以指定存放的具体文件夹
创建Vue页面,并配置路由:
ckt addpage YourPageName # 例如ckt addpage Home,默认会放到src/pages/home/Home.vue中,并且会创建src/page/home/router.js
ckt addpage YourPageName -d src/views # 也可以指定文件夹,但需要手动集成路由
创建Vuex子模块:
ckt addstore YourVuexChildModuleName # 例如ckt addstore home,默认会放到src/store/modules/home/index.js和types.js
ckt addstore YourVuexChildModuleName -d src/vuex/modules # 也可以指定文件夹
好的下面开始撸代码吧。
首先npm initi -y 初始化包,然后在package.json中配置包名 “name”: “ckt”,和指令入口文件
"bin": {
"ckt": "index.js"
}
表示指令为ckt时执行index.js文件。配置好后npm link把bin和环境变量进行链接,把ckt配置到环境变量。
目录结构:
配置index.js
#!/usr/bin/env node
//一个指令,读为shebang/hashbang。但代码执行到这个文件时,会根据#!后面定义的环境执行代码。
//#!后面写的是node代码,代码相对固定。 /usr/bin/env node表示在当前电脑环境中找到node,并用node来执行文件
const program = require('commander');
const helpOptions = require('./lib/core/help');
const createCommands = require('./lib/core/create');
const log = require('./lib/utils/log');
// 定义显示模块的版本号
// program.version('1.0.1') //写死的,每次package.json中修改这里都需要同步
program.version(require('./package.json').version)
program.version(require('./package.json').version, '-v,--version') //不加'-v,--version'则只有使用--version或者-V才可以访问到版本号,使用此后使用-v也可以访问到。ps:上面一句不加会覆盖-V
// 给help增加其他选项
helpOptions(); //--help是commander自带的,也可以自己配置
// 创建命令
createCommands();
// 解析终端指令
program.parse(process.argv); //必须,把参数交过来进行解析
help.js中配置可选参数:
const program = require('commander');
// 增加自己的options(可选参数)
const helpOptions = () => {
program.option('-s --src <src>', 'a source folder');
program.option('-d --dest <dest>', 'a destination folder, 例如: -d src/pages, 错误/src/pages');
program.option('-f --framework <framework>', 'your framework name');
//除了option,也可以使用on监听help.执行顺序在option后面
program.on('--help', function() {
console.log("");
console.log("usage");
})
// console.log(program.dest)
}
module.exports = helpOptions;
create.js中配置命令:
const program = require('commander');
const {
createProject,
addComponent,
addPage,
addStore
} = require('./actions');
//<>表示必选参数, []表示可选参数, ...表可以多个
//.command为commander里面带有的,用于创建指令,description描述指令的功能,action传入执行指令时的回调函数
const createCommands = () => {
// 创建项目指令
program
.command('create <project> [otherArgs...]')
.description('clone a repository into a newly created directory')
.action(createProject);
program
.command('addcpn <name>')
.description('add vue component, 例如: ckt addcpn NavBar [-d src/components]')
.action(name => addComponent(name, program.dest || 'src/components'))
program
.command('addpage <name>')
.description('add vue page, 例如: ckt addpage Home [-d dest]')
.action(name => {
addPage(name, program.dest || `src/pages/${name.toLowerCase()}`)
})
program
.command('addstore <name>')
.description('add vue store, 例如: ckt addstore favor [-d dest]')
.action(name => {
addStore(name, program.dest || `src/store/modules/${name.toLowerCase()}`)
})
}
module.exports = createCommands;
由于每个指令调用执行的方法逻辑复杂,若把方法直接放在action()里面会使create.js内容太多难以维护,故抽取到action.js
const { promisify } = require('util');//node中带有的promisify方法用于把带有回调的函数封装成promise的新式,避免回调地狱
const path = require('path');
const fs = require('fs');
const downloadRepo = promisify(require('download-git-repo'));//本来download-git-repo不支持promise,这样封装后download支持promise
const open = require('open'); //用于打开浏览器
const log = require('../utils/log');
const terminal = require('../utils/terminal');
const { ejsCompile, writeFile, mkdirSync } = require('../utils/file');
const repoConfig = require('../config/repo_config');
const createProject = async (project, otherArg) => {//把promise转为async的形式让代码更加优雅,以同步的方式执行异步函数
// 1.提示信息
log.hint('I will helps you create your project, please wait a moment~');
// 2.clone项目从仓库
await downloadRepo(repoConfig.vueGitRepo, project, { clone: true });
// 3.执行终端命令npm install
// terminal.exec('npm install', {cwd: `./${project}`});
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
await terminal.spawn(npm, ['install'], { cwd: `./${project}` });
// 4.打开浏览器
open('http://localhost:8080/');
// 5.运行项目
await terminal.spawn(npm, ['run', 'serve'], { cwd: `./${project}` });
}
const handleEjsToFile = async (name, dest, template, filename) => {
// 1.获取模块引擎的路径
const templatePath = path.resolve(__dirname, template);
const result = await ejsCompile(templatePath, {name, lowerName: name.toLowerCase()});
// 2.写入文件中
// 判断文件不存在,那么就创建文件
mkdirSync(dest);
const targetPath = path.resolve(dest, filename);
writeFile(targetPath, result);
}
const addComponent = async (name, dest) => {
handleEjsToFile(name, dest, '../template/component.vue.ejs', `${name}.vue`);
}
const addPage = async (name, dest) => {
addComponent(name, dest);
handleEjsToFile(name, dest, '../template/vue-router.js.ejs', 'router.js')
}
const addStore = async (name, dest) => {
handleEjsToFile(name, dest, '../template/vue-store.js.ejs', 'index.js')
handleEjsToFile(name, dest, '../template/vue-types.js.ejs', 'types.js')
}
module.exports = {
createProject,
addComponent,
addPage,
addStore
}
同时把不同的模板放在templete文件夹中
componet.vue.ejs
<template>
<div class="<%= data.lowerName %>">
<h2>{{ message }}</h2>
</div>
</template>
<script>
export default {
name: "<%= data.name %>",
components: {
},
mixins: [],
props: {
},
data: function() {
return {
message: "Hello <%= data.name %>"
}
},
created: function() {
},
mounted: function() {
},
computed: {
},
methods: {
}
}
</script>
<style scoped>
.<%= data.lowerName %> {
}
</style>
vue-router.js.ejs
// 普通加载路由
// import <%= data.name %> from './<%= data.name %>.vue'
// 懒加载路由
const <%= data.name %> = () => import('./<%= data.name %>.vue')
export default {
path: '/<%= data.lowerName %>',
name: '<%= data.name %>',
component: <%= data.name %>,
children: [
]
}
vue-store.js.ejs
import * as types from './types.js'
export default {
namespaced: true,
state: {
},
mutations: {
},
actions: {
},
getters: {
}
}
vue-types.js.ejs
export {
}
而utils中的file.js则用于定义编译模板和写入文件的方法
const fs = require('fs');
const path = require('path');
const ejs = require('ejs'); //编译模板
const log = require('./log');
const ejsCompile = (templatePath, data={}, options = {}) => {
return new Promise((resolve, reject) => {
ejs.renderFile(templatePath, {data}, options, (err, str) => {
if (err) {
reject(err);
return;
}
resolve(str);
})
})
}
const writeFile = (path, content) => {
if (fs.existsSync(path)) {
log.error("the file already exists~")
return;
}
return fs.promises.writeFile(path, content);
}
const mkdirSync = (dirname) => {
if (fs.existsSync(dirname)) {
return true
} else {
// 不存在,判断父亲文件夹是否存在?
if (mkdirSync(path.dirname(dirname))) {
// 存在父亲文件,就直接新建该文件
fs.mkdirSync(dirname)
return true
}
}
}
module.exports = {
ejsCompile,
writeFile,
mkdirSync
}
log.js用于配置控制台执行命令过程中提示信息的内容和样式
const chalk = require('chalk'); //用于设置控制台显示的内容及样式
const hint = (...info) => {
console.log(chalk.blue(info));
}
const error = (...info) => {
console.log(chalk.red(info));
}
const clear = () => {
console.clear();
}
module.exports = {
hint,
error,
clear
}
别忘了,在config文件夹中的repo_config.js是用来定义当你执行创建项目命令使拉取代码的地址
//仓库配置
const vueGitRepo = "direct:https://github.com/coderwhy/hy-vue-temp.git";
module.exports = {
vueGitRepo
}
最后。使用npm public把包推送到npm上就可以。
如果没有注册过自己的npm账号的需要到 npm (https://www.npmjs.com/)上注册一个账号,注册过程略。
然后在终端执行登录命令npm login,输入用户名、密码、邮箱即可登录。
登录成功后,输入命令npm publish,发布组件。
发布成功后,一般会有npm发送给你的通知邮件,进入npm官方,登录自己的账号,查看刚刚发布的组件库。
项目地址:https://github.com/cccccccclove/easyVue