前言
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第13期,链接:第13期 | open 打开浏览器
代码结构
const baseOpen = async options => {
// ...处理参数
// ...平台兼容处理
// 使用 childProcess.spawn 启动应用,传入上面处理好的命令和参数
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
return subprocess;
}
const open = (target, options) => {
if (typeof target !== 'string') {
throw new TypeError('Expected a `target`');
}
return baseOpen({
...options,
target
});
};
module.exports = open;
源码地址 open
使用方式
const open = require('open');
await open('https://baidu.com');
is-wsl 判断运行环境
'use strict';
const os = require('os');
const fs = require('fs');
const isDocker = require('is-docker');
const isWsl = () => {
if (process.platform !== 'linux') {
return false;
}
if (os.release().toLowerCase().includes('microsoft')) {
if (isDocker()) {
return false;
}
return true;
}
try {
return fs.readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft') ?
!isDocker() : false;
} catch (_) {
return false;
}
};
if (process.env.__IS_WSL_TEST__) {
module.exports = isWsl;
} else {
module.exports = isWsl();
}
getWslDrivesMountPoint 获取wsl驱动安装路径
const {promises: fs, constants: fsConstants} = require('fs');
const isWsl = require('is-wsl');
/**
Get the mount point for fixed drives in WSL.
@inner
@returns {string} The mount point.
*/
const getWslDrivesMountPoint = (() => {
// Default value for "root" param
// according to https://docs.microsoft.com/en-us/windows/wsl/wsl-config
const defaultMountPoint = '/mnt/';
let mountPoint;
return async function () {
if (mountPoint) {
// Return memoized mount point value
return mountPoint;
}
const configFilePath = '/etc/wsl.conf';
let isConfigFileExists = false;
try {
await fs.access(configFilePath, fsConstants.F_OK);
isConfigFileExists = true;
} catch {}
if (!isConfigFileExists) {
return defaultMountPoint;
}
const configContent = await fs.readFile(configFilePath, {encoding: 'utf8'});
const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
if (!configMountPoint) {
return defaultMountPoint;
}
mountPoint = configMountPoint.groups.mountPoint.trim();
mountPoint = mountPoint.endsWith('/') ? mountPoint : `${mountPoint}/`;
return mountPoint;
};
})();
pTryEach 循环打开多个应用
const pTryEach = async (array, mapper) => {
let <p align=left>latestError</p>;
for (const item of array) {
try {
return await mapper(item); // eslint-disable-line no-await-in-loop
} catch (error) {
latestError = error;
}
}
throw latestError;
};
baseOpen 启动应用函数
const baseOpen = async options => {
options = {
wait: false,
background: false,
newInstance: false,
allowNonzeroExitCode: false,
...options
};
添加默认配置
多应用启动处理
if (Array.isArray(options.app)) {
return pTryEach(options.app, singleApp => baseOpen({
...options,
app: singleApp
}));
}
let {name: app, arguments: appArguments = []} = options.app || {};
appArguments = [...appArguments];
if (Array.isArray(app)) {
return pTryEach(app, appName => baseOpen({
...options,
app: {
name: appName,
arguments: appArguments
}
}));
}
区分带参数和无参数形式
平台兼容处理
let command;
const cliArguments = [];
const childProcessOptions = {};
if (platform === 'darwin') {、
command = 'open';
// ...处理 options 其他参数转变成启动命令一部分配置
else if (platform === 'win32' || (isWsl && !isDocker())) {
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' );
// ...处理启动命令及参数
} else {
// ...处理启动命令及参数
}
使用 child_process.spawn 启动命令
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
if (options.wait) {
return new Promise((resolve, reject) => {
subprocess.once('error', reject);
subprocess.once('close', exitCode => {
if (options.allowNonzeroExitCode && exitCode > 0) {
reject(new Error(`Exited with code ${exitCode}`));
return;
}
resolve(subprocess);
});
});
}
subprocess.unref();
return subprocess;
}
设置默认应用
function detectArchBinary(binary) {
if (typeof binary === 'string' || Array.isArray(binary)) {
return binary;
}
const {[arch]: archBinary} = binary;
if (!archBinary) {
throw new Error(`${arch} is not supported`);
}
return archBinary;
}
function detectPlatformBinary({[platform]: platformBinary}, {wsl}) {
if (wsl && isWsl) {
return detectArchBinary(wsl);
}
if (!platformBinary) {
throw new Error(`${platform} is not supported`);
}
return detectArchBinary(platformBinary);
}
const apps = {};
defineLazyProperty(apps, 'chrome', () => detectPlatformBinary({
darwin: 'google chrome',
win32: 'chrome',
linux: ['google-chrome', 'google-chrome-stable', 'chromium']
}, {
wsl: {
ia32: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',
x64: ['/mnt/c/Program Files/Google/Chrome/Application/chrome.exe', '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe']
}
}));
defineLazyProperty(apps, 'firefox', () => detectPlatformBinary({
darwin: 'firefox',
win32: 'C:\\Program Files\\Mozilla Firefox\\firefox.exe',
linux: 'firefox'
}, {
wsl: '/mnt/c/Program Files/Mozilla Firefox/firefox.exe'
}));
defineLazyProperty(apps, 'edge', () => detectPlatformBinary({
darwin: 'microsoft edge',
win32: 'msedge',
linux: ['microsoft-edge', 'microsoft-edge-dev']
}, {
wsl: '/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
}));
默认设置设置了 chrome、firefox、edge三个浏览器应用
open 对外暴露接口
const open = (target, options) => {
if (typeof target !== 'string') {
throw new TypeError('Expected a `target`');
}
return baseOpen({
...options,
target
});
};
open.apps = apps;
open.openApp = openApp;
module.exports = open;
总结
这个库就是使用 node子进程 spawn 方法调用系统命令启动系统应用,然后内部默认配置了chrome、firefox、edge三个浏览器应用,所以可以直接调用 open 方法打开网址