本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
1. 前言
第五次参与活动咯,如果你有时间(摸鱼),想学又不知道学什么的话,欢迎一同参与
如果你有一些钱不知道花在A还是B上,你先不作决定,没问题,因为钱还是你的。 但如果你有一些时间,不知道花在A上还是B上,不行,因为过了这段时间,这段时间就不是你的了。 ——《暗时间》
1.1 场景
在我们启动项目的时候,浏览器都会自动弹出并且显示项目内容,那么这一步到底是谁叫电脑做的呢?
如果你想直接知道原理以及具体代码可以直接跳到 3.5 baseOpen方法
1.2 你能学到
- 电脑打开浏览器的原理
- 具体的源码实现
- 调试源码
2. 日常开发中是用什么打开浏览器的
2.1 在webpack 中
webpage提供了一个 dev-Server,使我们启动服务器后打开浏览器,在webpack.config.js
将其open
属性设置为指定页面
即可
open 接收一个数组—— 也就是可以一同打开多个指定页面,其余具体操作细节可以查看官方文档module.exports = {
//...
devServer: {
open: ['/my-page'],
},
};
一般我们开发项目都不会自己用 webpack 从头搭建一个了,而是使用脚手架
2.2 在vue-cli 中
npx @vue/cli create vue3-project
# npm i -g yarn
# yarn serve 不会自动打开浏览器
yarn serve
# 添加 --open 参数后会自动打开浏览器
yarn serve --open
2.3 create-react-app 中
npx create-react-app react-project
# 我的 open-analysis 项目中 react-project 文件夹
# npm i -g yarn
# 默认自动打开了浏览器
yarn start
而以上三者打开浏览器共同用到到的第三方库就是 open,也就是我们今天的主角。
- create-react-app 引用的地方
-
3. 理解源码
3.1 先了解其原理
读开源项目,先从readme 开始。
为什么用 open
Why?
- Actively maintained.
- Supports app arguments.
- Safer as it uses spawn instead of exec.
- Fixes most of the original node-open issues.
- Includes the latest xdg-openscript for Linux.
- Supports WSL paths to Windows apps.
积极维护
- 支持应用参数
- 更安全,使用
spawn
而不是exec
(这里有关键词) - 修复了大多数
node-open
的问题 - 包括适用
Linux
最新版xdf-open
脚本 - 支持
Windows
应用的WSL
路径
这上面说到能够支持在不同系统上帮你打开浏览器,但是开发者在使用的时候只需要一种命令,那就是open
const open = require('open');
// Opens the image in the default image viewer and waits for the opened app to quit.
await open('unicorn.png', {wait: true});
console.log('The image viewer app quit');
那么我的第一反应就是 用 Node.js
获取系统类型,再分析获得的字符串,根据情况调用对应的方法。事实也是如此:
一句话概括
open
原理则是:针对不同的系统,使用Node.js
的子进程child_process
模块的spawn
方法,调用系统的命令打开浏览器。
spawn 和 exec
这上面还提到了,更安全,因为使用 spawn 而不是 exec
两者的相同点
- 都用于开一个子进程执行指定命令
- 都可以自定义子进程的运行环境
都返回一个
ChildProcess
对象,所以他们都可以取得子进程的标准输入流
、标准输出流
和标准错误流
两者的不同点
接收参数方式不同
spawn
使用了参数数组exec
则直接接在命令后
子进程返回给Node的数据量
spawn
没有限制子进程可以返回给Node
的数据大小,exec
则在options
配置对象中有maxBuffer
参数限制,且默认为200K
,如果超出,那么子进程将会被杀死,并报错:Error:maxBuffer exceeded,虽然可以手动调大maxBuffer
参数,但是并不被推荐这或许与这两个
api
设置的本意有关:spawn
应用来运行返回大量数据的子进程,如图像处理,文件读取等。而exec
则应用来运行只返回少量返回值的子进程,如只返回一个状态码。
exec
方法相比spawn
方法,多提供了一个回调函数,可以更便捷得获取子进程输出。
看起来两个效果差不多,并且exec
更为便携,但是可能就是在用于便携性的同时,也付出了maxBuffer
的代价,大小超出子进程就会被直接杀死。而这,可能这就是安全性方面的问题
解决方案
maxBuffer
这个传一个足够大的参数- 直接使用
spawn
,放弃使用exec
显然 open
源码中直接用的是第二个解决方案
3.2 环境准备
# 克隆官方项目
git clone https://github.com/sindresorhus/open.git
# npm i -g yarn
cd open
yarn
3.3 测试demo
新建一个文件夹examples
用于存储测试demo,
// open/examples/index.js
(async () => {
const open = require("../index.js");
await open("https://juejin.cn/user/2164280112722760");
})();
在终端执行命令:node examples/index.js
然后就开始 debug
3.4 open 方法
跟随断点,我们来到 open/index.js
的第227行左右,找到调用的open
方法
const open = (target, options) => {
if (typeof target !== 'string') {
throw new TypeError('Expected a `target`');
}
return baseOpen({
...options,
target
});
};
其返回一个函数baseOpen
的调用结果,我们再跟着进去查看
3.5 baseOpen 方法
这个方法非常的长,这里就不完全粘代码过来了,这是仓库代码位置,也就是72行到225行。
从前面的介绍中可以得知关键处必有spawn
方法,我们Ctrl+F
快速定位关键处:
可以发现这里接收三个参数command
、cliArguments
、childProcessOptions
我们找到这三个变量定义的地方(剩下分析都写在注释)
const childProcess = require('child_process');
const {platform, arch} = process; //获取当前机器的platform信息,arch下面这个方法未涉及
const baseOpen = async options => {
options = {
wait: false,
background: false,
newInstance: false,
allowNonzeroExitCode: false,
...options
};
//... 此处省略部分代码
//源代码101行左右位置
let command; //命令
const cliArguments = []; //命令携带参数
const childProcessOptions = {}; //子进程选项
if (platform === 'darwin') {//Darwin 是MacOSX 操作环境的操作系统成份
command = 'open';//使用mac系统的open命令
// ...对 mac 系统的其他操作
} else if (platform === 'win32' || (isWsl && !isDocker())) {//windows || windows子系统
// ...对windows的
const encodedArguments = ['Start'];//用windows系统下的start命令
//...
} else {
if (app) {
command = app;
} else {
//... linux系统
const useSystemXdgOpen = process.versions.electron ||
platform === 'android' || isBundled || !exeLocalXdgOpen;
command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;
}
if (appArguments.length > 0) { //根据命令携带参数处理
cliArguments.push(...appArguments);
}
//...
}
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
//...
subprocess.unref();
return subprocess;
};
4. 总结 & 收获
4.1 原理 && 不同系统打开浏览器的命令
- 综上所述,分析得知,
open
原理就是:先通过Node.js
的process
获取当前机器platform
信息,再根据机器信息使用Node.js
中child_process
模块中的spawn
方法,调用对应操作系统打开浏览器的命令# mac 用open
open https://juejin.cn/user/2164280112722760
# win 用start
start https://juejin.cn/user/2164280112722760
# linux 用 xdg-open
xdg-open https://juejin.cn/user/2164280112722760
4.2 知其然
启动项目时候打开服务器是如此的频繁,然而在此之前我从未了解过,这下可算是其然工作中常用的知识,做到知其然知其所以然
4.3 读源码的技巧
尽管全部的代码很长,但如果只是想了解其原理,达到知其然的效果,实际上并不需要全部阅读,只需要跟随断点以及几个关键词即可找到关键代码,即可大大降低阅读难度
🌊如果有所帮助,欢迎点赞关注,一起进步⛵