四、脚手架命令注册和执行过程开发
1、npminstall
const path = require("path")
const npminstall = require("npminstall")
const userHome = require("user-home")
npminstall({
root: path.resolve(userHome, ".linding-cli-dev"),
storeDir: path.resolve(userHome, ".linding-cli-dev", "node_modules"),
registry: "https://registry.npmjs.org",
pkgs: [
{name: "foo", version: "~1.0.0"}
]
})
const fse = require("fs-extra")
const pathExists = require("path-exists").sync
const pathStr = "E:\\study\\aaa\\bbb\\ccc"
if (!pathExists(pathStr)) {
fse.mkdirpSync(pathStr)
}
3、webstorm调试脚手架
* edit configurations -> add new configuration -> node.js
* 命令:<node interpreter> <working directory>+<node parameters>
4、查询进程
* linux:ps -ef|grep 关键字或进程号
- pid:进程号
- ppid:父进程号
* windows:tasklist|findstr 关键字或进程号
5、child_process异步方法使用
const cp = require("child_process")
const path = require("path")
const iconv = require("iconv-lite")
const encoding = 'cp936';
const binaryEncoding = 'binary';
cp.exec(path.resolve(__dirname, "test.bat param1 param2"), {
timeout: 0,
cwd: path.resolve(__dirname),
encoding: binaryEncoding
}, function (error, stdout, stderr) {
console.log(error)
console.log(iconv.decode(new Buffer.from(stdout, binaryEncoding), encoding))
console.log(iconv.decode(new Buffer.from(stderr, binaryEncoding), encoding))
})
cp.execFile(path.resolve(__dirname, "test.shell"), ["-al", "-bl"], function (error, stdout, stderr) {
console.log(error)
console.log(stdout)
console.log(stderr)
})
6、child_process中spawn的用法
const cp = require("child_process")
const iconv = require("iconv-lite")
const encoding = 'cp936';
const binaryEncoding = 'binary';
const child = cp.spawn('npm.cmd', ['i'], {
cwd: 'E:\\study\\web-architect\\work-space-01\\imooc-test-lib',
encoding: binaryEncoding,
})
child.stdout.on('data', function (chunk) {
console.log('stdout', iconv.decode(new Buffer.from(chunk, binaryEncoding), encoding))
})
child.stderr.on('data', function (chunk) {
console.log('stderr', iconv.decode(new Buffer.from(chunk, binaryEncoding), encoding))
})
7、fork用法及父子进程通信机制
const cp = require("child_process")
const path = require("path");
const child = cp.fork(path.resolve(__dirname, 'child.js'))
child.send('hello child process!', () => {
})
child.on('message', (msg) => {
console.log(msg)
})
console.log('main pid:' + process.pid)
const cp = require("child_process")
const ret = cp.execSync('ls -al|grep node_modules')
console.log(ret.toString())
const ret2 = cp.execFileSync('ls', ['-al'])
console.log(ret2.toString())
const ret3 = cp.spawnSync('ls', ['-al'])
console.log(ret3.stdout.toString())
8、node多进程child_process库源码分析
* shell的使用
- 直接执行shell文件:/bin/sh test.shell
- 直接执行shell语句:/bin/sh -c "ls -al|grep node_modules"
* exec/execFile/spawn/fork的区别
- exec:原理是调用/bin/sh -c执行我们传入的shell脚本,底层调用了execFile
- execFile:原理是直接执行我们传入的file和args,底层调用spawn创建和执行子进程,并建立了回
调用,一次性将所有的stdout和stderr结果返回
- spawn:原理是调用了internal/child_process,实例化了ChildProcess子进程对象,再调用
child.spawn创建子进程并执行命令,底层是调用了child._handle.spawn执行process_wrap中的
spawn方法,执行过程是异步的,执行完毕后通过PIPE进行单向数据通信,通信结束后会子进程发起
onexit回调,同时Socket会执行close回调
- fork:原理是通过spawn创建子进程和执行命令,采用node执行命令,通过setupchannel创建IPC用
于子进程和父进程之间的双向通信
* data/error/exit/close回调的区别
- data:主进程读取数据过程中通过onStreamRead发起的回调
- error:命令执行失败后发起的回调
- exit:子进程关闭完成后发起的回调
- close:子进程所有Socket通信端口全部关闭后发起的回调
- stdout close/stderr close:特定的PIPE读取完成后调用onReadableStreamEnd关闭Socket时发起的
回调
const cp = require("child_process")
const child = cp.exec(`dir E:\\study\\web-architect\\work-space-01\\imooc-test | findstr node_modules`, function (error, stdout, stderr) {
console.log("callback start-----------------")
console.log(error)
console.log(stdout)
console.log(stderr)
console.log("callback end-----------------")
})
child.on("error", err => {
console.log("error!", err)
})
child.stdout.on("data", chunk => {
console.log("stdout data", chunk)
})
child.stderr.on("data", chunk => {
console.log("stderr data", chunk)
})
child.stdout.on("close", () => {
console.log("stdout close")
})
child.stderr.on("close", () => {
console.log("stderr close")
})
child.on("exit", (exitCode) => {
console.log("exit!", exitCode)
})
child.on("close", () => {
console.log("close!")
})
五、脚手架创建项目流程设计和开发
1、概念
* 架构背后的思考
- 可扩展:能够快速复用到不同团队,适应不同团队之间的差异
- 低成本:在不改动脚手架源码的情况下,能够新增模板,且新增模板的成本很低
- 高性能:控制存储空间,安装时充分利用node多进程提升安装性能
2、inquirer基本用法
const inquirer = require('inquirer')
inquirer
.prompt([
{
name: "yourName",
type: "input",
message: "your name",
default: "noname",
validate: function (v) {
return typeof v === "string"
},
transformer: function (v) {
return v + ":name"
},
filter: function (v) {
return "name:" + v
}
},
{
name: "num",
type: "number",
message: "your number",
default: 0
},
{
name: "choice",
type: "confirm",
message: "your choice",
default: false
},
{
name: "choiceList",
type: "list",
message: "your choice list",
default: 0,
choices: [
{value: 1, name: "科比"},
{value: 2, name: "乔丹"},
{value: 3, name: "约基奇"}
]
},
{
name: "choiceRawList",
type: "rawlist",
message: "your choice raw list",
default: 0,
choices: [
{value: 1, name: "科比"},
{value: 2, name: "乔丹"},
{value: 3, name: "约基奇"}
]
},
{
name: "choiceExpand",
type: "expand",
message: "your choice expand",
default: "red",
choices: [
{key: "R", value: "red"},
{key: "G", value: "green"},
{key: "B", value: "blue"}
]
},
{
name: "choiceCheckbox",
type: "checkbox",
message: "your choice checkbox",
default: 0,
choices: [
{value: 1, name: "科比"},
{value: 2, name: "乔丹"},
{value: 3, name: "约基奇"}
]
},
{
name: "yourPassword",
type: "password",
message: "your password"
},
{
name: "yourEditor",
type: "editor",
message: "your editor"
}
])
.then(answers => {
console.log(answers)
})
.catch(error => {
if (error.isTtyError) {
} else {
}
})
3、egg.js快速初始化
mkdir egg-example && cd egg-example
npm init egg --type=simple
npm i
npm run dev
open http://localhost:7001
4、egg.js框架添加新的api
- linding-cli-dev-server/app/router.js
'use strict';
module.exports = app => {
const { router, controller } = app;
router.get('/project/template', controller.project.getTemplate);
};
- linding-cli-dev-server/app/controller/project.js
'use strict';
const { Controller } = require('egg');
class ProjectController extends Controller {
async getTemplate() {
const { ctx } = this;
ctx.body = 'get template';
}
}
module.exports = ProjectController;
* 手动修改host文件
- C:\Windows\System32\drivers\etc\hosts
* 使用SwitchHosts工具
- https://github.com/oldj/SwitchHosts
5、egg.js接入mongodb
- linding-cli-dev-server/config/db.js
'use strict';
const mongodbUrl = 'mongodb://linding:123456@localhost:27017/linding-cli-dev';
const mongodbDbName = 'linding-cli-dev';
module.exports = {
mongodbUrl,
mongodbDbName,
};
- linding-cli-dev-server/app/utils/mongo.js
'use strict';
const Mongodb = require('@pick-star/cli-mongodb');
const { mongodbUrl, mongodbDbName } = require('../../config/db');
function mongo() {
return new Mongodb(mongodbUrl, mongodbDbName);
}
module.exports = mongo;
- linding-cli-dev-server/app/controller/project.js
'use strict';
const { Controller } = require('egg');
const mongo = require('../utils/mongo');
class ProjectController extends Controller {
async getTemplate() {
const { ctx } = this;
const data = await mongo().query('project');
ctx.body = data;
}
}
module.exports = ProjectController;
6、通过spinner实现命令行loading效果
(async function () {
const Spinner = require('cli-spinner').Spinner
const spinner = new Spinner("loading.. %s")
spinner.setSpinnerString("|/-\\")
spinner.start()
await new Promise(resolve => setTimeout(resolve, 1000))
spinner.stop(true)
})()
7、readline的使用方法和实现原理
const readline = require("readline")
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
rl.question('your name: ', answer => {
console.log(answer)
rl.close()
})
function Interface(input, output, completer, terminal) {
if (!(this instanceof Interface)) {
return new Interface(input, output, completer, terminal);
}
}
* nodejs的三大特性
- 单线程
- 非阻塞IO
- 事件驱动
function* g() {
console.log('read')
let ch = yield
console.log(ch)
let s = yield
console.log(s)
}
const f = g()
f.next()
f.next('a')
f.next('b')
function stepRead(callback) {
function onkeypress(s) {
output.write(s)
line += s
switch (s) {
case '\r':
input.pause()
callback(line)
break
}
}
const input = process.stdin
const output = process.stdout
let line = ''
emitKeypressEvents(input)
input.on('keypress', onkeypress)
input.setRawMode(true)
input.resume()
}
function emitKeypressEvents(stream) {
function onData(chunk) {
g.next(chunk.toString())
}
const g = emitKeys(stream)
g.next()
stream.on('data', onData)
}
function* emitKeys(stream) {
while (true) {
let ch = yield
stream.emit('keypress', ch)
}
}
stepRead(function (s) {
console.log('answer:' + s)
})
8、命令行样式修改的核心原理:ansi转义序列
console.log('\x1B[41m\x1B[4m%s\x1B[0m', 'your name:')
console.log('\x1B[2B%s', 'your name2:')
9、响应式库rxjs快速入门
const {range} = require('rxjs')
const {map, filter} = require("rxjs/operators")
range(1, 200).pipe(
filter(x => x % 2 === 1),
map(x => x + x)
).subscribe(x => console.log(x))
10、手写命令行交互式列表组件
const EventEmitter = require('events')
const readline = require('readline')
const MuteStream = require("mute-stream")
const {fromEvent} = require("rxjs")
const ansiEscapes = require("ansi-escapes")
const option = {
type: "list",
name: "name",
message: "select your name:",
choices: [{
name: "sam", value: "sam"
}, {
name: "shuangyue", value: "sy"
}, {
name: "zhangxuan", value: "zx"
}]
}
function Prompt(option) {
return new Promise((resolve, reject) => {
try {
const list = new List(option)
list.render()
list.on('exit', function (answers) {
resolve(answers)
})
} catch (e) {
reject(e)
}
})
}
class List extends EventEmitter {
constructor(option) {
super();
this.name = option.name
this.message = option.message
this.choices = option.choices
this.input = process.stdin
const ms = new MuteStream()
ms.pipe(process.stdout)
this.output = ms
this.rl = readline.createInterface({
input: this.input,
output: this.output
})
this.selected = 0
this.height = 0
this.keypress = fromEvent(this.rl.input, 'keypress')
.forEach(this.onkeypress);
this.haveSelected = false;
}
onkeypress = (keymap) => {
const key = keymap[1]
if (key.name === 'down') {
this.selected++
if (this.selected > this.choices.length - 1) {
this.selected = 0
}
this.render()
} else if (key.name === 'up') {
this.selected--
if (this.selected < 0) {
this.selected = this.choices.length - 1
}
this.render()
} else if (key.name === 'return') {
this.haveSelected = true
this.render()
this.close()
this.emit('exit', this.choices[this.selected])
}
}
render() {
this.output.unmute()
this.clean()
this.output.write(this.getContent())
this.output.mute()
}
getContent = () => {
if (!this.haveSelected) {
let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + "\x1B[22m\x1B[0m\x1B[0m\x1B[2m(Use arrow keys)\x1B[22m\n"
this.choices.forEach((choice, index) => {
if (index === this.selected) {
if (index === this.choices.length - 1) {
title += '\x1B[36m> ' + choice.name + '\x1B[39m '
} else {
title += '\x1B[36m> ' + choice.name + '\x1B[39m \n'
}
} else {
if (index === this.choices.length - 1) {
title += ' ' + choice.name
} else {
title += ' ' + choice.name + '\n'
}
}
})
this.height = this.choices.length + 1
return title
} else {
const name = this.choices[this.selected].name
let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + "\x1B[22m\x1B[0m\x1B[36m" + name + "\x1B[39m\x1B[0m \n"
return title
}
}
clean() {
const emptyLines = ansiEscapes.eraseLines(this.height)
this.output.write(emptyLines)
}
close() {
this.output.unmute()
this.rl.output.end()
this.rl.pause()
this.rl.close()
}
}
Prompt(option).then(answers => {
console.log('answers:', answers)
})
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
2021-04-17 kubernetes入门