open 打开浏览器

508 阅读2分钟

前言

代码结构

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'
}));

默认设置设置了 chromefirefoxedge三个浏览器应用

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 方法调用系统命令启动系统应用,然后内部默认配置了chromefirefoxedge三个浏览器应用,所以可以直接调用 open 方法打开网址