源码学习目录

1. 前言

1.1 环境

  1. 操作系统: macOS 11.5.2
  2. 浏览器: Chrome 94.0.4606.81
  3. open 8.4.0

    1.2 阅读该文章可以get以下知识点

  4. 打开了浏览器原理和源码实现

  5. 学习child_process 模块

    1.3 open的用法

    1. open('https://sindresorhus.com', {app: {name: open.apps.chrome}})
    2. open('https://sindresorhus.com', {url: true})
    3. open('https://sindresorhus.com', {app: {name: open.apps.chrome, arguments: ['--incognito']}})
    4. open('index.js')

    2. index.js 源码

    2.1 vite 中调用的函数就是 open

    1. // 如果target不是字符串,抛出错误
    2. const open = (target, options) => {
    3. if (typeof target !== 'string') {
    4. throw new TypeError('Expected a `target`');
    5. }
    6. return baseOpen({
    7. ...options,
    8. target
    9. });
    10. };

    2.2 baseOpen

    1. const baseOpen = async options => {
    2. // 配置参数
    3. options = {
    4. wait: false,
    5. background: false,
    6. newInstance: false,
    7. allowNonzeroExitCode: false,
    8. ...options
    9. };
    10. // app是不是数组,不是很了解app这个参数干嘛的
    11. if (Array.isArray(options.app)) {
    12. // 拿到可以执行的app,然后执行baseOpen,拿到第一个app
    13. return pTryEach(options.app, singleApp => baseOpen({
    14. ...options,
    15. app: singleApp
    16. }));
    17. }
    18. let {name: app, arguments: appArguments = []} = options.app || {};
    19. appArguments = [...appArguments];
    20. // 如果里面还有app,继续执行在调用baseOpen
    21. if (Array.isArray(app)) {
    22. return pTryEach(app, appName => baseOpen({
    23. ...options,
    24. app: {
    25. name: appName,
    26. arguments: appArguments
    27. }
    28. }));
    29. }
    30. let command;
    31. const cliArguments = [];
    32. const childProcessOptions = {};
    33. // linux平台
    34. if (platform === 'darwin') {
    35. command = 'open';
    36. // 添加一些参数
    37. if (options.wait) {
    38. cliArguments.push('--wait-apps');
    39. }
    40. if (options.background) {
    41. cliArguments.push('--background');
    42. }
    43. if (options.newInstance) {
    44. cliArguments.push('--new');
    45. }
    46. if (app) {
    47. cliArguments.push('-a', app);
    48. }
    49. // windows平台或者使用了wsl, wsl是windows子系统,一般是用的Ubuntu系统
    50. } else if (platform === 'win32' || (isWsl && !isDocker())) {
    51. // 获取加载路径
    52. const mountPoint = await getWslDrivesMountPoint();
    53. // wsl和正常window的命令不一样
    54. command = isWsl ?
    55. `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` :
    56. `${process.env.SYSTEMROOT}\\System32\\WindowsPowerShell\\v1.0\\powershell`;
    57. cliArguments.push(
    58. '-NoProfile',
    59. '-NonInteractive',
    60. '–ExecutionPolicy',
    61. 'Bypass',
    62. '-EncodedCommand'
    63. );
    64. if (!isWsl) {
    65. childProcessOptions.windowsVerbatimArguments = true;
    66. }
    67. const encodedArguments = ['Start'];
    68. if (options.wait) {
    69. encodedArguments.push('-Wait');
    70. }
    71. if (app) {
    72. // Double quote with double quotes to ensure the inner quotes are passed through.
    73. // Inner quotes are delimited for PowerShell interpretation with backticks.
    74. encodedArguments.push(`"\`"${app}\`""`, '-ArgumentList');
    75. if (options.target) {
    76. appArguments.unshift(options.target);
    77. }
    78. } else if (options.target) {
    79. encodedArguments.push(`"${options.target}"`);
    80. }
    81. if (appArguments.length > 0) {
    82. appArguments = appArguments.map(arg => `"\`"${arg}\`""`);
    83. encodedArguments.push(appArguments.join(','));
    84. }
    85. // Using Base64-encoded command, accepted by PowerShell, to allow special characters.
    86. options.target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
    87. } else {
    88. if (app) {
    89. command = app;
    90. } else {
    91. // When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
    92. const isBundled = !__dirname || __dirname === '/';
    93. // Check if local `xdg-open` exists and is executable.
    94. let exeLocalXdgOpen = false;
    95. try {
    96. await fs.access(localXdgOpenPath, fsConstants.X_OK);
    97. exeLocalXdgOpen = true;
    98. } catch {}
    99. const useSystemXdgOpen = process.versions.electron ||
    100. platform === 'android' || isBundled || !exeLocalXdgOpen;
    101. command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;
    102. }
    103. if (appArguments.length > 0) {
    104. cliArguments.push(...appArguments);
    105. }
    106. if (!options.wait) {
    107. // `xdg-open` will block the process unless stdio is ignored
    108. // and it's detached from the parent even if it's unref'd.
    109. childProcessOptions.stdio = 'ignore';
    110. childProcessOptions.detached = true;
    111. }
    112. }
    113. if (options.target) {
    114. cliArguments.push(options.target);
    115. }
    116. if (platform === 'darwin' && appArguments.length > 0) {
    117. cliArguments.push('--args', ...appArguments);
    118. }
    119. // 开启子进程,执行脚本
    120. const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
    121. // 如果有wait,返回一个promise
    122. if (options.wait) {
    123. return new Promise((resolve, reject) => {
    124. subprocess.once('error', reject);
    125. subprocess.once('close', exitCode => {
    126. if (options.allowNonzeroExitCode && exitCode > 0) {
    127. reject(new Error(`Exited with code ${exitCode}`));
    128. return;
    129. }
    130. resolve(subprocess);
    131. });
    132. });
    133. }
    134. // 关闭父进程
    135. subprocess.unref();
    136. return subprocess;
    137. };

    3. 总结

  6. open不止可以打开浏览器也可以打开文件,本质上执行的就是一个脚本

  7. max系统下,执行的脚本 open -a microsoft\ edge http://www.baidu.com,直接支持打开浏览器
  8. 不同的平台脚本不一样,主要是mac/linux/windows三种,windows下还分了wsl和正常系统

    4. 参考文档

  9. https://juejin.cn/post/7026505183819464734

  10. github.com/sindresorhus/open
  11. https://www.npmjs.com/package/ava