想实现一个page复用的功能
https://blog.csdn.net/luchuanqi67/article/details/91283513
https://www.jianshu.com/p/55addf3cbbbd
通过命令 leah create myproject 创建一个项目
npm init -y 生成package.json
1.在package.json中加入创建指令
在package.json中配置项目入口文件
"bin":{
"leah" : "index.js"
}
index.js是项目入口文件,这个指令告诉node可以通过leah create demo创建一个项目
怎么执行这个入口文件呢?
2.在index.js中加入运行文件的环境
需要在入口文件中加入下面这行命令,表示在node环境中运行这个文件
#!/usr/bin/env node
通过npm link与环境变量进行关联
3.自定义指令
依赖commander这个库
npm install commander
在index.js中引入
const program = require("commander")
3.1版本指令
program.version(require('./package.json').version);
通过package.json获取版本信息
通过 leah —version 可以查看当前包的版本
3.2help指令
通过program.option('--leah', 'a vue cli')
这样的方式定义help指令,然后通过leah --help
可以查看到加入option的help指令.
我们的目的是可以通过 leah addComponent Home -d src/component 这样的方式创建一个一个组件到指定目录下,所以就需要拿到这个指定的目录地址。
program.option('-d --dest <dest>', 'a destination folder, 例如: -d src/pages, 错误/src/pages');
**<dest>**
表示可选参数, 可以通过**program.dest**
拿到指定的路径 src/pages
program.option('-w --why', 'a coderwhy option');
program.option('-s --src <src>', 'a source folder');
// <dest> 表示可选参数, 可以通过program.dest 拿到指定的路径
program.option('-d --dest <dest>', 'a destination folder, 例如: -d src/pages, 错误/src/pages');
// 指定选择哪个框架 vue react
program.option('-f --framework <framework>', 'your framework name');
// 监听help指令,可以做一些操作,比如修改指令等
program.on('--help', function() {
console.log("");
console.log("usage");
console.log(" coderwhy -v");
console.log(" coderwhy -version");
})
4.创建项目指令
敲下 leah create demo 发生了什么事
- 去github仓库拉取我们配置好的项目结构
- 执行 npm install 安装项目依赖
- 执行 npm run serve 启动项目
- 打开浏览器
4.1 git仓库拉取代码download-git-repo
// 创建项目指令
program
.command('create <project> [otherArgs...]') // 其中command 是添加子命令 <project> 是项目名称 [otherArgs...] 其他可选参数 可以通过project拿到项目名称
.description('clone a repository into a newly created directory') // description是描述
.action(createProject); // action 是执行这个命令的回调 可以接收一个promise回调
此命令表示要创建一个 demo的项目,就会去github上拉取我们的项目放到 demo的文件夹里面,这个就是上面指定的action回调需要做的。将这部分回调函数放到单独的一个文件里action.js
action.js
const { promisify } = require('util');
const program = require('commander');
const downloadRepo = promisify(require('download-git-repo'));
const open = require('open');
const log = require('../utils/log');
const terminal = require('../utils/terminal');
const repoConfig = require('../config/repo_config');
const createProject = async (project, otherArg) => {
// 1.提示信息
log.hint('leah-cli 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';
// cwd: 子进程的当前工作目录。
await terminal.spawn(npm, ['install'], { cwd: `./${project}` }); //
// 4.打开浏览器
open('http://localhost:8080/');
// 5.运行项目
await terminal.spawn(npm, ['run', 'serve'], { cwd: `./${project}` });
}
repo_config.js 存放我们的下载地址
// https的地址要通过direct下载
const vueGitRepo = "direct:https://github.com/coderwhy/hy-vue-temp.git";
module.exports = {
vueGitRepo
}
补充:promisify
promisify是nodejs提供的一个工具函数,可以将一些不支持promise写法的回调函数转换成promise函数,之后我们就可以通过 .then或者async和await来调用。
例如:
require('fs').readFile('./test.js', 'utf-8', function (err, data) {
if (err) {
console.log(err)
}
console.log(data)
})
使用promise改造
function readfile() {
return new Promise(function (resolve, reject) {
require('fs').readFile('./test.js', 'utf-8', function (err, data) {
if (err) {
reject(err)
}
resolve(data)
})
})
}
readfile().then(function (data) {console.log(data)})
promisify的原理
function promisify (f) {
return function () {
let args = Array.prototype.slice.call(arguments) // f函数的参数
console.log(args)
return new Promise( (resolve, reject) => {
args.push(function(err, result) {
console.log(err)
console.log(result)
if(err) reject(err) // 错误原因
else resolve(result) // 输出结果
})
f.apply(null, args) // 执行f函数
})
}
}
4.2 安装项目依赖指令 npm install
如上我们可以通过执行 npm install 安装项目依赖
将这些终端命令封装到一个terminal.js文件
const { spawn, exec } = require('child_process');
const spawnCommand = (...args) => {
// const childProcess = spawn(...args,callback); // 这个函数会创建异步进程
// // 让用户看到这个安装过程
// childProcess.stdout.pipe(process.stdout); //标准输出流 process 全局对象,通过它我们可以获取,运行该程序的用户,环境变量等信息。
// childProcess.stderr.pipe(process.stderr); // 标准错误流
// // 执行完,关闭
// childProcess.on('close', () => {
// callback(); // 执行回调
// })
// 将上面改写成promise
return new Promise((resole, reject) => {
const childProcess = spawn(...args); // 这个函数会创建异步进程
// 让用户看到这个安装过程
childProcess.stdout.pipe(process.stdout); //标准输出流 process 全局对象,通过它我们可以获取,运行该程序的用户,环境变量等信息。
childProcess.stderr.pipe(process.stderr); // 标准错误流
// 执行完,关闭
childProcess.on('close', () => {
resole(); // 将执行结果返回,可以通过.then调用,也可以通过async await执行回调
})
})
}
为了防止回调地狱问题,我们要将普通函数封装成promise对象,resolve将执行结果抛出,可以通过.then调用,也可以通过async await执行回调 。
child_process
child_process有两种启动方式,一中是spawn,一种是exec,他俩的区别是:
https://www.cnblogs.com/xiaoniuzai/p/6889164.html
https://segmentfault.com/a/1190000002913884
child_process.spawn(command[, args][, options])
command
要运行的命令。args
字符串参数的列表。- options
cwd
子进程的当前工作目录。
我们需要传入这三个参数
await terminal.spawn(npm, ['install'], { cwd: `./${project}` });
4.3 运行项目 npm run serve
// 5.运行项目
await terminal.spawn(npm, ['run', 'serve'], { cwd: `./${project}` });
4.4 浏览器打开项目
// 4.打开浏览器
open('http://localhost:8080/'); // open是一个库
也可以在项目的webpack配置中自动打开项目。
inquirer库 可以做出这种流程询问的效果
5.添加组件指令
需要一个组件名称name,创建的文件目录dest
使用ejs创建模板代码
将ejs编译成字符串,
再将这个字符串写入.vue文件,
最后创建好的.vue文件写入到dest目录
5.1 创建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>
我们可以通过传入data对象来创建我们的模板
5.2编译模板
通过ejs.renderFile这个api实现
file.js
const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const log = require('./log');
const ejsCompile = (templatePath, data={}, options = {}) => {
// ejs.renderFile(templatePath, {data}, options, (err, str) => {
// if (err) {
// return;
// }
// callback(); // 执行回调
// })
// 将上面改写成 promise
return new Promise((resolve, reject) => {
// renderFile 渲染哪个ejs模板文件
ejs.renderFile(templatePath, {data}, options, (err, str) => {
if (err) {
reject(err); // 将错误抛出
return;
}
resolve(str); // 将结果输出
})
})
}
module.exports = {
ejsCompile,
}
action.js
/**
*
* @param {*} name 用户输入的要闯将的组件名称
* @param {*} dest 创建在哪个目录下
* @param {*} template 用哪个模板创建
* @param {*} filename 文件名
*/
const handleEjsToFile = async (name, dest, template, filename) => {
// 1.获取模块引擎的路径 path.resolve(__dirname, template)获取当前模块文件所在目录的完整绝对路径
const templatePath = path.resolve(__dirname, template);
// 2. ejsCompile 编译
const result = await ejsCompile(templatePath, {name, lowerName: name.toLowerCase()});
}
const addComponent = async (name, dest) => {
handleEjsToFile(name, dest, '../template/component.vue.ejs', `${name}.vue`);
}
create.js
program
.command('addcpn <name>')
.description('add vue component, 例如: coderwhy addcpn NavBar [-d src/components]')
.action(name => addComponent(name, program.dest || 'src/components'))
addComponent(name, program.dest || 'src/components'))
——> handleEjsToFile(name, dest, '../template/component.vue.ejs',
${name}.vue);
——> const result = await ejsCompile(templatePath, {name, lowerName: name.toLowerCase()});
path.resolve(__dirname) 获取当前模块文件所在目录的完整绝对路径,比如:
d:\资料库\学习资料\封装自己的脚手架\coderwhy-main\lib\utils
5.3写入文件
使用fs.promises.writeFile
写入
file.js
const writeFile = (path, content) => {
if (fs.existsSync(path)) {
log.error("the file already exists~")
return;
}
return fs.promises.writeFile(path, content);
}
actions.js
const handleEjsToFile = async (name, dest, template, filename) => {
// 1.获取模块引擎的路径
const templatePath = path.resolve(__dirname, template);
// 2. ejsCompile 编译
const result = await ejsCompile(templatePath, {name, lowerName: name.toLowerCase()});
// 3.写入文件中
// 判断文件不存在,那么就创建文件
mkdirSync(dest);
const targetPath = path.resolve(dest, filename);
writeFile(targetPath, result);
}
6.添加page指令
如何看待前端这个行业
最近在关注前端什么技术