青训营Pro️从0到1实现一个自己的前端约定路由项目脚手架️ 工具~
Posted YK菌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了青训营Pro️从0到1实现一个自己的前端约定路由项目脚手架️ 工具~相关的知识,希望对你有一定的参考价值。
文章目录
前言
今天我们来复习&总结青训营实战班第一天的课程,也就是全栈然叔带来的Node基础Api与CLI实战课程。
今天主要学习如何创建一个自己的脚手架工具 🛠️,这样可以避免每次写小demo的时候要进行各种重复配置 🕹️。最后我们发布到npm仓库,让周围的小伙伴也可以使用你写的工具~ 其实我们今天主要要学习的是一种自动化的思想,这也是前端工程化重要的一环。
本文从 ① Node基础 到 ② 脚手架搭建 到 ③ npm仓库上传 ,完全从0到1造轮子,没有基础的小伙伴也能看懂~
PS:这是第一篇,后面还有一篇 我们用happy-path小步走的编码方式再写一个koa的脚手架工具,你会收获更多~~ 点赞 👍 越多更新越快 ✈️
相关的代码在这里https://gitee.com/ykang2020/youth_camp_ykjun
一、Node基础API
在字节跳动青训营基础班,我们学习了一些Node.js的基础概念,今天我们先来学习下Node.js的一些基础API,为我们后面实践部分做好铺垫 🏃♂️ 🏃 ~
PS: 基础班的课程笔记博文在这里:
- 【青训营】Node.js基础 - 特点 - 模块化CommonJS&ESM - npm包管理
- 【青训营】Node.js基础 - 异步编程四种解决方案
- 【青训营】Node.js基础 - Web应用开发 - 开发调试 - 线上部署
node.jsAPI 官方文档 中文:http://nodejs.cn/api/ 英文:https://nodejs.org/dist/latest-v14.x/docs/api/
核心API - 无需 require
- buffer
- module
- process
内置API - 需要 require
无需 install
- os
- fs
- path
- http
- event
1. fs与异步IO
参考:http://nodejs.cn/api/fs.html 以及 https://nodejs.org/dist/latest-v14.x/docs/api/fs.html
首先引入 fs
文件模块
const fs = require("fs");
① 同步读取文件
// 同步读取
const data = fs.readFileSync("./04_buffer.js");
console.log("data", data.toString());
这里如果不调用 data
的 toString()
方法,结果则会返回一个 buffer
,因为对于计算机来说,它所存储的所有文件都是二进制文件
使用 data
的 toString()
方法是默认将二进制文件按 utf-8
转换成文本,也就是默认调用 toString('utf-8')
② 异步读取文件
错误优先的回调函数,在回调函数中进行文件处理操作
fs.readFile("./04_buffer.js", (err, data) => {
if (err) throw err;
console.log(data.toString());
})
我们知道使用回调形式进行异步操作不好,我们现在都是用 promise
了,所以自然的想到可以将异步文件读取有没有 promise
风格的API可以使用,然后就可以使用 async/await
来使用异步文件操作
Node.js提供了Promise风格的fs文件操作API,可以看文档
③ promisify
在Node.js的 util
中提供了一个方法 promisify
,可以将回调的方法修饰成 promise
风格的API,就可以使用 async/await
语法了
(async () => {
const fs = require("fs");
const { promisify } = require("util");
const readFile = promisify(fs.readFile);
const data = await readFile("./04_buffer.js");
console.log(data.toString());
})();
2. buffer与字符集
buffer
翻译过来就是缓冲区
分配一个10个字节的 buffer
const buf1 = Buffer.alloc(10)
console.log(buf1) // <Buffer 00 00 00 00 00 00 00 00 00 00>
打印输出看看,方便显示阅读,转换成了十六进制,底层当然是二进制存储的
下面使用 buffer
存储一个英文文字,一个字母一个字节
const buf2 = Buffer.from('yk')
console.log(buf2) //<Buffer 79 6b>
使用 buffer
存储一个中文文字,一个文字三个字节(utf-8)
const buf3 = Buffer.from('菌')
console.log(buf3) // <Buffer e8 8f 8c> utf-8/16/32
可以连接两个 buffer
const buf4 = Buffer.concat([buf2, buf3])
console.log(buf4, buf4.toString()) // <Buffer 79 6b e8 8f 8c> yk菌
之前也说过,使用 toString
方法就可以显示文字
3. http
使用 http
模块快速写一个 http
服务器
const http = require("http");
http
.createServer((request, response) => {
console.log("a request");
response.end("Hi YK菌"); // 这个end不太好,不好理解
})
.listen(3000, () => {
console.log("Server at 3000");
});
我们来看看原型链,先来定义一个函数,来获取对象的原型链
function getPrototypeChain(obj) {
const protoChain = [];
while ((obj = Object.getPrototypeOf(obj))) {
protoChain.push(obj);
}
return protoChain;
}
来看看 request
和 response
的原型链
const http = require("http");
http
.createServer((request, response) => {
console.log(
"a request",
getPrototypeChain(request),
getPrototypeChain(response)
);
response.end("Hi YK node");
})
.listen(3000, () => {
console.log("Server at 3000");
});
request的原型链
response的原型链
可以发现 request
与 response
都是继承自 Stream
的
这个问题我们后面再讨论,我们继续使用我们的 http
模块来搭建服务器
我们返回一个index.html文件,这就用到我们上面说到的 fs
文件读取API了
const fs = require("fs");
const http = require("http");
http
.createServer((request, response) => {
const { url, method } = request;
console.log("url:", url);
if (url === "/" && method === "GET") {
fs.readFile("index.html", (err, data) => {
if (err) {
response.writeHead(500, {
"Content-Type": "text/plain;charset=utf-8",
});
response.end("500 服务器挂了,兄弟!!!");
}
response.statusCode = 200;
response.setHeader("Content-Typr", "text/html");
response.end(data);
});
} else {
response.statusCode = 400;
response.setHeader("Content-Type", "text/plain;charset=utf-8");
response.end("404 找不到了呀!");
}
})
.listen(3000, () => {
console.log("Server at 3000");
});
练习1 node基础 fs\\buffer\\http 搭建一个http服务
写一个http服务器,返回index.html
如果我们的index.html文件中添加了一个img标签,引入了一张图片,我们应该怎么处理?直接用 readFile
读取可以吗?图片一般都比较大,这样会大量消耗内存资源,不要直接使用 readFile
读取, 因为它要把全部图片内容加载到服务器。
那应该用什么? 我们引入下面 Stream
流的概念
4. stream
在本地复制一个图片
const fs = require("fs");
// 图片复制 使用 fs read+write也可以,但是需要读到内存再从内存中写出去
// 我们换一种方式
const rs = fs.createReadStream("./YK菌.jpg");
const ws = fs.createWriteStream("./YK菌分身.jpg");
// 管道,文件流,从一个文件流到另一个文件
rs.pipe(ws);
文件流,从一个文件流到另一个文件
所以我们就可以这样编写代码
const fs = require("fs");
const http = require("http");
http
.createServer((request, response) => {
const { url, method } = request;
console.log("url:", url);
if (url === "/" && method === "GET") {
fs.readFile("index.html", (err, data) => {
if (err) {
response.writeHead(500, {
"Content-Type": "text/plain;charset=utf-8",
});
response.end("500 服务器挂了,兄弟!!!");
}
response.statusCode = 200;
response.setHeader("Content-Typr", "text/html");
response.end(data);
});
} else if (url === "/users" && method === "GET") {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ name: "yk菌" }));
} else if (method === "GET" && headers.accept.indexOf("image/*" !== -1)) {
// 拿到所有图片
// 不要直接使用readFile读取, 因为它要把全部图片内容加载到服务器
// 使用 流 stream !!!!
fs.createReadStream("." + url).pipe(response);
} else {
response.statusCode = 400;
response.setHeader("Content-Type", "text/plain;charset=utf-8");
response.end("404 找不到了呀!");
}
})
.listen(3000, () => {
console.log("Server at 3000");
});
在index.html添加图片,然后在服务器返回图片的时候使用流的形式返回,这样可以节省内存
5. 子进程
child_process
模块给予Node可以随意创建子进程(child_process)的能力。
它提供了4个方法用于创建子进程。
spawn()
:启动一个子进程来执行命令。exec()
:启动一个子进程来执行命令,与spawn()
不同的是其接口不同,它有一个回调函数获知子进程的状况。execFile()
:启动一个子进程来执行可执行文件。fork()
:与spawn()
类似,不同点在于它创建Node的子进程只需指定要执行的javascript文件模块即可。
二、实战:实现 cli
工具
目标:利用我们学过的Node.js的知识,实现一个自己的脚手架工具,脚手架用来快速创建一个项目,一个会自动生成前端路由的模板项目。我们叫它约定路由
1. 初始化
新建一个项目目录,并初始化
mkdir vue-auto-router-cli
cd vue-auto-router-cli
npm init -y
创建一个文件夹bin,然后创建一个yk.js文件,作为入口文件
要让全局可以使用 yk
指令,在 package.json 中加入这个配置项
"bin": {
"yk": "./bin/yk.js"
},
在 yk.js 头部加上,用来指定解释器类型为node
#!/usr/bin/env node
console.log('欢迎使用YK菌的 cli 工具');
最后别忘了 link
一下
npm link
此时在任意目录下的控制台中执行 yk
指令 后台就都会用 node
执行yk.js文件
2. 定制命令行页面
接下来我们来定制一个我们自己的命令行页面
#!/usr/bin/env node
console.log("欢迎使用YK菌的 cli 工具");
console.log(process.argv);
可以看到,用户输入的信息都保存在process.argv
中
我们先安装一下接下来要用到的所有的第三方库
npm i commander download-git-repo ora@5 handlebars figlet clear chalk open -s
这里我们使用一个第三方库 commander
来定制我们的命令行
我们的yk.js这样编写
#!/usr/bin/env node
// console.log("欢迎使用YK菌的 cli 工具");
// console.log(process.argv);
const program = require("commander");
program.version(require("../package.json").version);
program
.command("init <name>")
.description("init project")
.action((name) => {
console.log("init " + name);
});
program.parse(process.argv);
现在再使用我们的 yk
指令,就像点样子了~
3. 定制初始化项目的欢迎界面
我们将program.action中不可能只打印一句话,我们把初始化项目的逻辑抽离出来
program.action(require("../lib/init"))
我们就创建文件 lib/init.js
// 打印欢迎界面
const { promisify } = require("util");
const figlet = promisify(require("figlet"));
const clear = require("clear");
const chalk = require("chalk");
// 封装一个输出绿色文字的API
const log = (content) => console.log(chalk.green(content));
module.exports = async (name) => {
// 打印欢迎界面
clear();
const data = await figlet.textSync("YK!Welcome");
log(data);
}
此时使用 yk
初始化一个项目的时候,就会出现欢迎页面了~
当然这里的文字的样式可以自己定制 可以看看文档 https://www.npmjs.com/package/figlet
这里我就随便配置一个字体样式了~
const data = await figlet.textSync("YK!Welcome",{
font: "Ghost",
horizontalLayout: "default",
verticalLayout: "default",
width: 200,
whitespaceBreak: true,
});
4. 下载代码模板
我们新建一个文件用来处理下载代码模板的逻辑
新建 lib/download.js
const { promisify } = require("util");
module.exports.clone = async function (repo, desc) {
const download = promisify(require("download-git-repo"));
const ora = require("ora"); // 进度条
const process = ora(`✈下载.......${repo}`);
await process.start();
await download(repo, desc);
process.succeed();
};
这里用到两个库 download-git-repo
库用来下载代码模板 和 ora
库用来显示下载进度条(这里我们安装的是5版本,最新的是6版本,为什么不用最新版呢,因为最新版只支持ESM模块化引入)
在GitHub上面准备一个代码模板
在init.js中引入并使用下载模块即可
const { clone } = require("./download");
module.exports = async (name) => {
log("创建项目" + name);
// 这里的git仓库可以自己指定
await clone("github:yk2012/vue-template", name);
};
可以看到文件就已经被下载到当前目录下了
5. 自动安装项目依赖
在Node.js中使用子进程 child_process
安装依赖
创建一个子进程安装的 promise
风格的接口,然后还要合并子进程的流到主进程中
const spawn = async (...args) => {
// 同步Promise api
const { spawn } = require("child_process");
return new Promise((resolve) => {
// windows系统兼容性处理
const options = args[args.length - 1];
if (process.platform === "win32") {
options.shell = true;
}
const proc = spawn(...args);
// 输出流 子进程 合并到 主进程
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
proc.on("close", () => {
resolve();
});
});
};
注意中间有一段处理Windows兼容性问题的代码。
调用子进程安装依赖 (就是在项目目录下执行npm install
)
// 下载依赖 npm i
// 子进程
// spawn("npm", ["install"]);
log(`安装依赖....`);
await spawn("npm", ["install"], { cwd: `./<以上是关于青训营Pro️从0到1实现一个自己的前端约定路由项目脚手架️ 工具~的主要内容,如果未能解决你的问题,请参考以下文章
青训营Pro 前端框架设计理念 - Vue3动机 - 手写实现mini-vue
青训营Pro 前端框架设计理念 - Vue3动机 - 手写实现mini-vue
青训营Pro自定义脚手架从1到∞ - 自动化思维搭建koa脚手架