likes
comments
collection
share

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

作者站长头像
站长
· 阅读数 49

Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到文件变化时自动重新启动应用程序。它在开发过程中非常有用,因为它可以显著提高开发效率,避免手动重启服务器的繁琐操作

这篇文章打算教大家动手做一个min-nodemon, 实现nodemon的核心功能。

nodemon的核心功能

  1. 执行js文件
  2. 监听文件变化
  3. 传递额外的参数给文件

搭建cli框架

目标:在命令行输出 hello
效果:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

在控制台输出mon-toy, 然后输出 hello


先准备一个空文件夹: mon-toy,然后执行 npm init

mkdir mon-toy
cd mon-toy
npm init

创建src/index.js, 这是我们的文件入口

console.log('hello');

然后创建lib/index.js

#!/usr/bin/env node
require('../src/index.js');

第一行,告诉系统要用 node 执行这个文件;第二行就是引入src/index.js

然后编辑 package.json:

{
  "bin":{
    "mon-toy":"./lib/index.js"
  }
}

添加 bin字段。bin 的作用是指定执行的文件。当在控制台直接执行mon-toy的时候,bin 就告诉系统,去执行./lib/index.js

bin的值,可以为对象,也可以是字符串。对象的作用是改变控制台命令名称,也表示可以有多个命令

{
  "bin":{
    "mon-toy1":"./lib/index.js"//命令为:mon-toy1
    "mon-toy2":"./lib/index.js"//命令为:mon-toy2
  }
}

现在的文件目录是这个样子:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

然后在终端执行:npm link

这个作用是在全局 node_module 中增加了一个指向 mon-toy 项目的硬链接,然后在系统的 /usr/local/bin 目录中增加一个mon-toy命令,这个命令是个软链接,指向了全局node_module 中的 mon-toy 项目下的 lib/index.js

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

当我们在终端执行mon-toy,系统就会从 bin 目录开始,顺着一个个链接找到最终的执行文件。这样看起来,npm link 就是将当前的 npm 包在全局安装一样,为了可以在任意目录执行mon-toy

npm link是我们在本地测试经常用的命令

最终效果:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

修改src/index.js:

console.log('hello mon-toy')

再执行 mon-toy:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

搞定🤝

框架搭起来了,之后我们想执行什么样的逻辑,只需要修改src/index.js的内容就可以了

执行文件

目标:执行mon-toy test.js命令, 然后 node test.js命令得到执行,即让 node 运行 test.js 文件
效果:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

修改文件src/index.js

const { exec } = require("child_process");
const path = require("path");

const filename = process.argv[2];
const cwd = process.cwd();
const filePath = path.resolve(cwd, filename);

const run = ()=>{
    exec(`node ${filePath}`);
}

run();

代码很简单,就不解释了。

exec 是一个可以通过子进程执行终端命令的方法

传递文件参数

目标:接收文件名之外的数据,在 exec 执行的时候当作参数传进去

这个很简单,首先改造 test.js

console.log("i am test file");

const add = (a, b) => {
	a = Number(a);
	b = Number(b);
	if (isNaN(a) || isNaN(b)) {
		console.error("> type error");
		process.exit(1);
	}
	return a + b;
};

const args = process.argv.slice(2, 4);

console.log(add(...args));

test.js 中会接收前两个参数,当作 add 函数的参数,并且打印出 add 函数的返回值

然后改造src/index.js

const { exec } = require("child_process");
const path = require("path");

const filename = process.argv[2];
const cwd = process.cwd();
const filePath = path.resolve(cwd, filename);

const commandArgs = process.argv.slice(3);

const run = () => {
	exec(`node ${filePath} ${commandArgs.join(" ")}`, (error, stdout, stderr) => {
		console.log("exec");
		if (error) {
			console.log("error: ", error);
			return;
		}
		if (stderr) {
			console.log("stderr: ", stderr);
			return;
		}
		console.log(stdout);
	});
};

run();
效果:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

完美😍

监控文件变更

目标:当文件变更后,会自动再次执行 mon-toy 命令

添加一个文件 src/watchFile.js,用来监听文件变更:

const chokidar = require("chokidar");

const watchFile = (fileName, callback) => {
	chokidar.watch(fileName).on("change", (path) => callback(path));
};

module.exports = watchFile;

使用了chokidar库来监听文件的变动,这个依赖相较于 node 原生的库,性能更好,且磨平了不同系统之间的差异。

修改src/index.js

// ...

const action = () => {
	// 执行文件
	run();
	const watchFiles = watch ? path.resolve(cwd, watch) : filePath;
	// 监听文件的变化
	watchFile(watchFiles, (path) => {
		console.log(`${path} changed...\n`);
		run();
	});

	console.log(`\nListen ${watchFiles}....\n`);
};

action();

添加了新的函数,在内部执行文件,并且自动监听被执行的文件。

效果:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

这里小小地改变了 test.js文件的输出,保存后文件的变化被监听到,而且再次执行了test.js。是预期的效果,没问题

指定监听文件

我想要通过-w, 来指定监听的文件,而不是默认监听被执行的文件:

创建一个新文件src/handleArgs.js:

const getArgs = (source, key) => {
  const index = source.indexOf(key);
  let value = null;
  if (index !== -1) {
    value = source[index + 1];
    source.splice(index, 2);
    return {
      [key]: value,
    };
  }
  return null;
};

const handleArgs = () => {
  const args = process.argv;
  const watchFile = getArgs(args, "-w");
  let filename = getArgs(args, "-f");
  
  if (filename == null) {
    filename = { "-f": args[2] };
    args.splice(2, 1);
  }
  
  return {
    fileName: filename["-f"],
    watch: watchFile ? watchFile["-w"] : null,
    fileArgs: args.slice(2),
  };
};

module.exports = handleArgs;

这个文件主要处理在终端传给 mon-toy的参数,这里读取了三个部分,-w-f,fileArgsfileArgs是传给被执行文件的参数

其中-f可以不被指出

举个例子, 下面三种命令都是同样的结果:

mon-toy test.js 1 3 -w some.js
mon-toy -w some.js test.js 1 3
mon-toy -f test.js -w some.js 1 3

修改src/index.js

const getArgs = (source, key) => {
  const index = source.indexOf(key);
  let value = null;
  if (index !== -1) {
    value = source[index + 1];
    source.splice(index, 2);
    return {
      [key]: value,
    };
  }
  return null;
};

const handleArgs = () => {
  const args = process.argv;
  const watchFile = getArgs(args, "-w");
  let filename = getArgs(args, "-f");
  if (filename == null) {
    filename = { "-f": args[2] };
    args.splice(2, 1);
  }
  return {
    fileName: filename["-f"],
    watch: watchFile ? watchFile["-w"] : null,
    fileArgs: args.slice(2),
  };
};

module.exports = handleArgs;

修改test.js

console.log("i am test file..");

require("./add.js");

这里将原来的 add函数的逻辑拆开成一个单独的文件add.js

console.log("i am add file!!!");
const add = (a, b) => {
  a = Number(a);
  b = Number(b);

  if (isNaN(a) || isNaN(b)) {
    console.error("> type error");
    return;
  }
  return a + b;
};

const args = process.argv.slice(2, 4);
console.log(add(...args));

现在的文件目录结构:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

现在看看效果:

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

这里指定了被监听的文件是 add.js, 效果符合预期,没问题😋

👨手把手,教你实现一个 nodemon☝️Nodemon 是一个用于 Node.js 应用程序的实用工具,能够在检测到

-f 也生效了

重构代码

现在解析参数和核心功能是混在一起的,我们将其分开。解析参数的部分分为 src/cli.js; 核心功能就是 src/index.js

src/cli.js:

const args = process.argv;

const options = require("./handleArgs.js")(args);

require("./core.js")(options);

src/index.js:

const { exec } = require("child_process");
const path = require("path");
const watchFile = require("./watchFile");

module.exports = function core(options) {
	const { fileName, fileArgs, watch } = options;
  
	const cwd = process.cwd();
	const filePath = path.resolve(cwd, fileName);

	const run = () => {
		exec(`node ${fileName} ${fileArgs.join(" ")}`, (error, stdout, stderr) => {
			if (error) {
				console.log("error: ", error);
				return;
			}
			if (stderr) {
				console.log("stderr: ", stderr);
				return;
			}
			console.log(stdout);
		});
	};

	const action = () => {
		// 执行文件
		run();
		// 指定被监听的文件
		const watchFiles = watch ? path.resolve(cwd, watch) : filePath;
		// 监听文件的变化
		watchFile(watchFiles, (path) => {
			console.log(`${path} changed!!!!\n`);
			run();
		});

		console.log(`\nListen ${watchFiles}\n`);
	};

	action();
};

bin/index.js:

#!/usr/bin/env node
require("../src/cli.js");

将 cli 功能和 croe 功能分开来,这是很正常的操作。自此 mon-toy 就支持了两种调用方式,一种是 cli 方式,一种是 api 调用方式。

全部代码

全部代码,已上传:github.com/zenoskongfu…

发布

为了能让大家直接体验到 mon-toy,我将其发布到了 npm。在本地执行 npm i zenos-mon-toy -g,就可以体验了,快来试试吧

试过之后,可以执行npm uninstall zenos-mon-toy -g卸载

更多功能

nodemon 中,还可以修改默认执行的命令,即除了可以执行 node 命令,还可以执行python,dotnet等其他的命令。

更多功能, 大家感兴趣的可以自己实现

nodemon 官方文档: www.npmjs.com/package/nod…

总结

这篇文章给大家拆解以及手把手,自己实现了一个 nodemon 的核心功能,执行 js 文件,以及文件监听。文章内容简单清晰,快动手试一试吧

转载自:https://juejin.cn/post/7391699424844562483
评论
请登录