啥是脚手架啊
百度解释:脚手架是为了保证各施工过程顺利进行而搭设的工作平台,它长这样...
那么我们在开发中说的脚手架又是啥呢?通常来说,编程中的脚手架指的是能帮助开发人员快速搭建项目、文件框架的工具,再简单点就是复制模板生成文件,当然脚手架可以做的不止是“复制”这么简单,今儿咱们只说说这个最常用的。
开干!!!
初始化项目
在命令行一次执行如下命令
mkdir my-cli
cd my-cli
npm init
npm link
mkdir my-cli
创建my-cli文件夹,在该文件夹下(cd my-cli
切换到my-cli文件)执行npm init
初始化项目,npm link
生成package-lock.json文件。
在根目录创建脚手架入口文件 cli.js,并配置在packgage.json
接下来,开始写咱们的第一个脚手架吧。 cli.js
#!/usr/bin/env node
console.log("This is my-cli!");
#!/usr/bin/env node
必须顶格书写,它的作用是告诉操作系统执行该脚本的时候去哪里找node解释器。
在当前目录执行my-cli
测试一下:
OK,飞起来了!
使用模板生成文件
现在我们的脚手架已经飞起来了,接下来让它拥有“复制”功能。逻辑很简单,就是先读取文件A的内容,再创建一个文件B,将A的内容写到B。
首先,创建templates文件夹来放置模板文件,然后创建模板文件index.vue:
/**
* templates\index.vue
*
*/
<template>
<div>
<section class="search-area">
这里是搜索区
</section>
<section class="table-area">
这里是表格区
</section>
</div>
</template>
<script type="text/javascript">
export default {
name: "template",
data(){
return {
//查询数据
searchData: {},
//表格数据
tableData: []
}
},
created(){
this.getDataList();
},
methods: {
//获取数据
getDataList(){},
//添加数据
addData(){},
//编辑数据
editData(){},
//删除数据
deleteData(){}
}
}
</script>
<style lang="scss">
</style>
修改cli.js:
#!/usr/bin/env node
const fs = require("fs"); // Node.js的文件处理模块
const path = require("path"); // Node.js的处理文件和目录路径处理模块
const templatePath = path.join(__dirname, "templates/index.vue"); // 获取templates绝对路径
const currentPath = process.cwd(); // process.cwd方法返回Node.js进程的当前工作目录
const writePath = path.join(currentPath, "index.vue"); // 文件的写入路径
// readFileSync 读取文件内容
fs.readFile(templatePath, {}, function (err, fileContent) {
if(err) throw err;
// writeFileSync 写文件内容
fs.writeFileSync(writePath, fileContent);
});
在当前目录执行my-cli
测试一下:
my-cli文件夹下有了我们复制的模板文件。
commander 接收命令行指令参数
现在我们的文件“复制”成功了,但是我们不想让它叫index.vue,想自定义“复制”文件的名称,又该咋个办呢?
这时候就该我们的命令行交互工具 commander 出场啦!
npm install commander
安装commander,修改cli.js:
#!/usr/bin/env node
const fs = require("fs"); // Node.js的文件处理模块
const path = require("path"); // Node.js的处理文件和目录路径处理模块
const { program } = require("commander");
/**
* command 设置命令
* create为自定义的命令名称
* <> 包裹的name为必须输入的参数,不输入会报错
* action为命令callback,参数为commond中配置的参数
*
**/
program
.command("create <name>")
.action((name) => {
createFileByTemplate(name);
});
/**
* parse 处理命令行参数
* process.argv 返回命令行参数,process是Node.js进程控制模块
**/
program.parse(process.argv);
function createFileByTemplate(name){
const templatePath = path.join(__dirname, "templates/index.vue");
const currentPath = process.cwd();
const writePath = path.join(currentPath, `${name}.vue`); // 使用name参数设置文件的写入路径
// readFileSync 读取文件内容
fs.readFile(templatePath, {}, function (err, fileContent) {
if(err) throw err;
// writeFileSync 写文件内容
fs.writeFileSync(writePath, fileContent);
});
}
在命令行执行my-cli create xx
测试一下:
可以看到,不输入name的时候是会报错的,因为它是必要参数。
inquirer 命令行交互
命令参数接收到了,那能不能接收用户实时输入,跟用户互动一下呢?命令行交互工具inquirer这不就来了吗。
老规矩npm install inquirer
安装,在cli.js中引入使用 const inquirer = require("inquire");
,修改cli.js:
function createFileByTemplate(name){
const templatePath = path.join(__dirname, "templates/index.vue");
const currentPath = process.cwd();
const context = { name };
prompt(templatePath, currentPath, context);
}
function prompt(readPath, writePath, context){
// 使用inquirer进行交互
inquirer.prompt({
type: "input", // 交互类型: 输入
message: "confirm name: ", //提示信息
name: "name", // 参数名称
default: context.name // 默认值
}).then(answer => {
// answer 是用户输入的回答
writePath = path.join(writePath, `${answer.name}.vue`);
loadFile(readPath, writePath, answer);
});
}
//加载文件
function loadFile(readPath, writePath, context){
fs.readFile(readPath, context, function (err, fileContent) {
if(err) throw err;
fs.writeFileSync(writePath, fileContent);
});
}
命令行执行一下:
这里会提示确认文件名称,直接回车会使用默认值,否则使用这里输入的名称:
ejs向模板传递数据
现在文件名称修改成功啦,要是我们还想修改这个组件的name属性,又要咋办叻?
当然是用ejs模板引擎啦!
npm install ejs
安装ejs,在cli.js中引入使用 const ejs = require("ejs");
修改 loadFile 方法,
function loadFile(readPath, writePath, context){
ejs.renderFile(readPath, context, (err, result) => {
if(err) throw err;
fs.writeFileSync(writePath, result)
});
}
修改index.vue模板:
再次执行create命令,查看生成文件中的name属性,已经是我们自定义的名称了:
处理多个模板文件
上边我们已经完成了单个模板文件的“复制”,但是通常我们需要“复制”的不止一个文件,加入我们的模板是这样的呢:
这时候我们就需要遍历该目录下的文件,逐个复制了,修改cli.js:
function createFileByTemplate(name){
const templatePath = path.join(__dirname, "templates");
const currentPath = process.cwd();
const context = { name };
prompt(templatePath, currentPath, context);
}
function prompt(readPath, writePath, context){
// 使用inquirer进行交互
inquirer.prompt({
type: "input", // 交互类型: 输入
message: "confirm name: ", //提示信息
name: "name", // 参数名称
default: context.name // 默认值
}).then(answer => {
// answer 是用户输入的回答
writePath = path.join(writePath, answer.name);
loadDirectory(readPath, writePath, answer);
});
}
// 加载目录
function loadDirectory(readPath, writePath, context = {}){
writePath = createDirectory(writePath);
// readdir 读取readPath目录内容,files为目录下的文件列表
fs.readdir(readPath, (err, files) => {
if(err) throw err;
//遍历列表
files.forEach(file => {
const _readPath = path.join(readPath, file); // 待“复制”文件的绝对路径
const _writePath = path.join(writePath, file); // 待写入文件的绝对路径
// stat 获取文件对象描述
fs.stat(_readPath, (err, stats) => {
if(err) throw err;
if(stats.isDirectory()){
// 目录
loadDirectory(_readPath, _writePath, context);
} else if(stats.isFile()){
//文件
loadFile(_readPath, _writePath, context);
}
});
});
});
}
// 创建文件(夹)
function createDirectory(directory){
//如果文件(夹)不存在,则新建
if (!fs.existsSync(directory)) {
//创建文件(夹)
fs.mkdir(directory, (error,result) => {
if(error) throw error;
});
}
return directory;
}
再次执行my-cli create user
:
我们又成功啦!!!
发布到npm
所谓好东西要大家一起分享嘛,快把你的脚手架分享给小伙伴们一起摇起来~~~ 根目录执行:
npm adduser
npm publish
然后再安装npm install my-cli -g
,成功后查看npm文件夹:
然后咱就可以在全局使用my-cli啦!