1. 前言
1.1 环境
- 操作系统: macOS 11.5.2
- 浏览器: Chrome 94.0.4606.81
-
1.2 阅读该文章可以get以下知识点
打开了浏览器原理和源码实现
-
1.3 open的用法
open('https://sindresorhus.com', {app: {name: open.apps.chrome}})
open('https://sindresorhus.com', {url: true})
open('https://sindresorhus.com', {app: {name: open.apps.chrome, arguments: ['--incognito']}})
open('index.js')
2. index.js 源码
2.1 vite 中调用的函数就是 open
// 如果target不是字符串,抛出错误
const open = (target, options) => {
if (typeof target !== 'string') {
throw new TypeError('Expected a `target`');
}
return baseOpen({
...options,
target
});
};
2.2 baseOpen
const baseOpen = async options => {
// 配置参数
options = {
wait: false,
background: false,
newInstance: false,
allowNonzeroExitCode: false,
...options
};
// app是不是数组,不是很了解app这个参数干嘛的
if (Array.isArray(options.app)) {
// 拿到可以执行的app,然后执行baseOpen,拿到第一个app
return pTryEach(options.app, singleApp => baseOpen({
...options,
app: singleApp
}));
}
let {name: app, arguments: appArguments = []} = options.app || {};
appArguments = [...appArguments];
// 如果里面还有app,继续执行在调用baseOpen
if (Array.isArray(app)) {
return pTryEach(app, appName => baseOpen({
...options,
app: {
name: appName,
arguments: appArguments
}
}));
}
let command;
const cliArguments = [];
const childProcessOptions = {};
// linux平台
if (platform === 'darwin') {
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);
}
// windows平台或者使用了wsl, wsl是windows子系统,一般是用的Ubuntu系统
} else if (platform === 'win32' || (isWsl && !isDocker())) {
// 获取加载路径
const mountPoint = await getWslDrivesMountPoint();
// wsl和正常window的命令不一样
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);
// 如果有wait,返回一个promise
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;
};
3. 总结
open不止可以打开浏览器也可以打开文件,本质上执行的就是一个脚本
- max系统下,执行的脚本 open -a microsoft\ edge http://www.baidu.com,直接支持打开浏览器
不同的平台脚本不一样,主要是mac/linux/windows三种,windows下还分了wsl和正常系统
4. 参考文档
- github.com/sindresorhus/open
- https://www.npmjs.com/package/ava