持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第13期 | open 打开浏览器 点击了解本期详情一起参与。
今天阅读的库是:open
使用场景
当我们使用脚手架创建vue或者react项目后,启动项目,命令行会帮助我们自动打开浏览器,访问对应的项目地址。那么,这个打开的动作是怎么实现的呢,我们来一探究竟。
以vite为例,文档中也有提及使用了open这个库
源码分析
首先,我们看下readme
为什么使用这个库呢,这个库有什么优势
支持跨平台执行命令
根据说明,我们可能有如下疑问:
1.为什么要用spawn代替exec,他们之间有什么异同
2.怎么做到跨平台使用
3.open的原理是什么
4.node判断当前平台
整个文件目录很简单
源码应该是在index.js中
我们可以写一个简单的测试用例并打断点
(async () => {
const open = require("../open/index.js");
await open("https://www.juejin.cn");
})();
可以看出,最终返回的是baseOpen,我们来看下baseOpen的实现
const baseOpen = async (options) => {
options = {
wait: false,
background: false,
newInstance: false,
allowNonzeroExitCode: false,
...options,
};
/*
省略...
唤醒外部应用
*/
// 命令行参数
let command;
const cliArguments = [];
const childProcessOptions = {};
// 根据不同平台构建command
if (platform === "darwin") {
// mac
command = "open";
// 等待应用退出
if (options.wait) {
cliArguments.push("--wait-apps");
}
// 后台运行
if (options.background) {
cliArguments.push("--background");
}
// 创建一个新的实例
if (options.newInstance) {
cliArguments.push("--new");
}
// 是否指定打开的应用
if (app) {
cliArguments.push("-a", app);
}
} else if (platform === "win32" || (isWsl && !isDocker())) {
// windows平台 或 wsl
const mountPoint = await getWslDrivesMountPoint();
// 拼接指令
command = isWsl
? `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`
: `${process.env.SYSTEMROOT}\\System32\\WindowsPowerShell\\v1.0\\powershell`;
cliArguments.push(
"-NoProfile",
"-NonInteractive",
"–ExecutionPolicy",
"Bypass",
"-EncodedCommand"
);
if (!isWsl) {
childProcessOptions.windowsVerbatimArguments = true;
}
const encodedArguments = ["Start"];
// 是否等待应用
if (options.wait) {
encodedArguments.push("-Wait");
}
if (app) {
// Double quote with double quotes to ensure the inner quotes are passed through.
// Inner quotes are delimited for PowerShell interpretation with backticks.
encodedArguments.push(`"\`"${app}\`""`, "-ArgumentList");
if (options.target) {
appArguments.unshift(options.target);
}
} else if (options.target) {
encodedArguments.push(`"${options.target}"`);
}
if (appArguments.length > 0) {
appArguments = appArguments.map((arg) => `"\`"${arg}\`""`);
encodedArguments.push(appArguments.join(","));
}
// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
options.target = Buffer.from(
encodedArguments.join(" "),
"utf16le"
).toString("base64");
} else {
if (app) {
command = app;
} else {
// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
const isBundled = !__dirname || __dirname === "/";
// Check if local `xdg-open` exists and is executable.
let exeLocalXdgOpen = false;
try {
await fs.access(localXdgOpenPath, fsConstants.X_OK);
exeLocalXdgOpen = true;
} catch {}
// 其他系统
const useSystemXdgOpen =
process.versions.electron ||
platform === "android" ||
isBundled ||
!exeLocalXdgOpen;
command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
}
if (appArguments.length > 0) {
cliArguments.push(...appArguments);
}
if (!options.wait) {
// `xdg-open` will block the process unless stdio is ignored
// and it's detached from the parent even if it's unref'd.
childProcessOptions.stdio = "ignore";
childProcessOptions.detached = true;
}
}
if (options.target) {
cliArguments.push(options.target);
}
if (platform === "darwin" && appArguments.length > 0) {
cliArguments.push("--args", ...appArguments);
}
const subprocess = childProcess.spawn(
command,
cliArguments,
childProcessOptions
);
/*
省略...
*/
subprocess.unref();
return subprocess;
};
原理:
- 根据
platform判断当前的系统 - 不同的系统拼接对应的命令
- 使用
childProcess.spawn来执行命令,唤起应用
总结
open这个库是根据node js中的 process.platform参数来识别出不同的平台,根据平台构建对应的指令,再调用 childProcess.spawn来执行命令,从而唤起对应的应用。
-
为什么要用spawn代替exec,他们之间有什么异同
-
child_process.exec 使用子进程执行命令,缓存子进程的输出,并将子进程的输出以回调函数参数的形式一次性返回。但是,当程序过大时,会崩溃
-
spawn 会返回一个带有
stdout和stderr流的对象。你可以通过stdout流来读取子进程返回给node js的数据。
-
-
怎么做到跨平台使用
process.platform参数来识别出不同的平台,构建对应的命令