组件库自动化脚本:监听构建成功并打开浏览器

一个改善开发体验的小脚本"自动新建组件并且打开浏览器",主要功能是:

  1. 以已有组件为原型,新建组件,其目录结构保持一致(源码、单元测试、README 等),内容替换成新组件。
  2. 创建成功后,自动开启 dev server。server ready 后浏览器自动打开组件所在页面

这个脚本主要目的是解决重复劳动减轻每次新建组件的工作量,以及规范团队的目录结构,还有一个好处是保障单元测试的编写。功能点1主要是复制文件和和替换内容,本文着重第二个功能。

这里有几个难点,如何设计一个通用的控制台输出监控脚本,从输出中获取想要的信息比如 host,难点在通用,即可以是任何框架不依赖特定参数或事件。

先看看效果:

Before

After

可以看到保持了原来的输出以及颜色,加入了自定义日志,并先后成功检测到了 host 启动和编译完成。整个过程没有依赖任何构建工具提供的事件。

接下来我们将设计一个通用的方案。

思路:执行命令 => 拦截输出 => 匹配输出 => 执行回调

graph LR A[开始] --> B[执行命令] B --> C[监听输出] C --> D{是否匹配?} D -->|是| E[执行回调] E --> C

执行命令

用 spawn 而非 exec,因为构建命令是流式输出的,我们需要随时监听并做出想要的业务动作,而 exec 有 buffer 会批量输出。其次通过 spawn 我们可以很方便执行重定向等操作。

js 复制代码
  const child = spawn(cmd, cmdArgs, {
    stdio: 'pipe',
    // 显式启用 shell,否则 Error: spawn npm ENOENT
    shell: true,
    env: {
      ...process.env,
      // 强制启用 真彩色(TrueColor) 或 16 万色(24-bit RGB)支持。
      // 否则没有颜色
      FORCE_COLOR: '3',
    },
  })

接下来我们将输出重定向到终端,以及重定向到我们的拦截逻辑。

ts 复制代码
  // 手动将子进程的 stdout/stderr 转发到父进程,这样终端才能实时输出构建日志
  child.stdout.pipe(process.stdout)
  child.stderr.pipe(process.stderr)

  child.stdout.on('data', (data) => {
    const output = data.toString()

    // 这里监听输出执行业务逻辑
    console.log('[output]:', output)
  })

真正核心内容就这些,我们仅靠 Node.js 的能力,没有使用构建工具提供事件,做到了监听输出内容并且仍然保留原日志输出的内容和色彩,而且相对更灵活我们可以做任何事情。

接下来就是针对输出内容截取有用信息然后"反应"即可。

封装以便使用:

ts 复制代码
import { spawn } from 'node:child_process'

export function chooseOpenCmd(): string {
  // 根据不同平台打开浏览器
  const openCommand =
    process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open'

  return openCommand
}

type IExecuteParams = {
  cmd: string
  cmdArgs?: string[]
  onOutput: (output: string) => void
}

export function execute({ cmd, cmdArgs = [], onOutput }: IExecuteParams) {
  const devCmd = cmd + ' ' + cmdArgs.join(' ')

  console.log('exec: `' + devCmd + '`')

  // 启动 npm run dev 并捕获输出
  const child = spawn(cmd, cmdArgs, {
    stdio: 'pipe',
    // 显式启用 shell,否则 Error: spawn npm ENOENT
    shell: true,
    env: {
      ...process.env,
      // 强制启用 真彩色(TrueColor) 或 16 万色(24-bit RGB)支持。
      // 否则没有颜色
      FORCE_COLOR: '3',
    },
  })
  // 手动将子进程的 stdout/stderr 转发到父进程
  child.stdout.pipe(process.stdout)
  child.stderr.pipe(process.stderr)

  child.stdout.on('data', (data) => {
    const output = data.toString()

    onOutput(output)

    // console.log('[output]:', output)
  })
}

监听并获取 host

ts 复制代码
let url: string | undefined

execute({
    cmd: `npm`,
    cmdArgs: ['run', 'dev'],
    onOutput: (output) => {
      if (output.includes('http://localhost')) {
        // match url from output `>   Local: http://localhost:8001`
        url = output.match(/(http:\/\/localhost:\d+)/)?.[1]

        log(
            chalk.green(
              '本地服务器已启动,正在等待编译完成,编译完成将自动打开该组件对应的浏览器地址...',
            ),
        )
      }
    },
  })

逻辑很直接:从输出匹配 host 然后保存到 url 变量中,打印一行日志,等待编译完成再浏览器打开该地址,当然还会拼接上组件名字,这样不用我们每次从主页进入点击多次才能进入组件文档页。

小问题:如果立即输出日志,将打乱原来输出,我们需要延迟下:

ts 复制代码
// 防止输出打断 npm run dev 日志
setTimeout(() => {
  console.log()
  log(
    chalk.green(
      '本地服务器已启动,正在等待编译完成,编译完成将自动打开该组件对应的浏览器地址...',
    ),
    '\n',
  )
}, 200)

监听构建完成事件

同理,从实时输出的日志匹配 [Webpack] Compiled in 即构建成功日志(你可以灵活调整成自己构建工具表示构建成功的的字符串),然后给第一步获取的 url 拼接组件名浏览器打开:

ts 复制代码
  execute({
    cmd: `npm`,
    cmdArgs: ['run', 'dev'],
    onOutput: (output) => {
      if (output.includes('[Webpack] Compiled in ')) {
        // 这里没有考虑非 Windows 系统。如果需要则使用 open npm 包
        openInBrowserCmd = `start ${url}/components/${hyphenatedComponentName}`
        console.log()
        log(chalk.green('检测到编译完成,打开浏览器', openInBrowserCmd))

        if (dryRun) {
          log('execSync:', openInBrowserCmd)
          return
        }

        execSync(openInBrowserCmd, { stdio: 'inherit' })
      }
    },
  })

这里直接使用 start 打开浏览器,没有考虑非 Windows 系统。其次我们无需实时获取打开浏览器的日志,简单起见使用 execSync,传入 inherit 让其日志能如实显示到终端,当然这里可以删除该参数,因为没有日志。

有些问题需要处理,比如如果第一步没有匹配到 url 怎么办?修改文件导致二次触发构建完成会重复打开浏览器,不想安装 npm 包能否做到兼容非 Windows 系统。下面是比较完整的脚本:

ts 复制代码
  const open = chooseOpenCmd()
  let opened = false

  execute({
    cmd: `npm`,
    cmdArgs: ['run', 'dev'],
    onOutput: (output) => {
      if (output.includes('[Webpack] Compiled in ')) {
        if (!url) {
          throw new Error('无法获取本地服务器地址')
        }

        // 防止 hot reload 二次编译重复打开浏览器
        if (opened) {
          return
        }

        opened = true

        openInBrowserCmd = `${open} ${url}/components/${hyphenatedComponentName}`
        console.log()
        log(chalk.green('检测到编译完成,打开浏览器', openInBrowserCmd))

        if (dryRun) {
          log('execSync:', openInBrowserCmd)
          return
        }

        execSync(openInBrowserCmd, { stdio: 'inherit' })
      }
    },
  })
ts 复制代码
export function chooseOpenCmd(): string {
  // 根据不同平台打开浏览器
  const openCommand =
    process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open'

  return openCommand
}

完整代码详见 github

效果

ts 复制代码
❯ npm run new Triangle -- --category=dataDisplay --title='三角形'

> @neural/[email protected] new
> node --experimental-strip-types scripts/create-component.ts Triangle --category=dataDisplay --title=三角形

[create-component] Create component "Triangle" from template "JSONInput" success: 16.631ms
[create-component] execSync: `npm run dev`

> @neural/[email protected] dev
> npm run check-node-version && dumi dev

info  - dumi v2.2.17
info  - Umi v4.1.5
Browserslist: caniuse-lite is outdated. Please run:
  npx update-browserslist-db@latest
  Why you should do it regularly: https://github.com/browserslist/update-db#readme
        ╔════════════════════════════════════════════════════╗
        ║ App listening at:                                  ║
        ║  >   Local: http://localhost:8001                  ║
ready - ║  > Network: http://10.88.103.87:8001               ║
        ║                                                    ║
        ║ Now you can open browser with the above addresses↑ ║
        ╚════════════════════════════════════════════════════╝

[create-component] 本地服务器已启动,正在等待编译完成,编译完成将自动打开该组件对应的浏览器地址...

event - [Webpack] Compiled in 21389 ms (7453 modules)

[create-component] 检测到编译完成,打开浏览器 start http://localhost:8001/components/triangle

可以看到我们拿到了 url 以及打开了浏览器。

相关推荐
打小就很皮...2 小时前
简单实现Ajax基础应用
前端·javascript·ajax
wanhengidc3 小时前
服务器租用:高防CDN和加速CDN的区别
运维·服务器·前端
哆啦刘小洋4 小时前
HTML Day04
前端·html
再学一点就睡4 小时前
JSON Schema:禁锢的枷锁还是突破的阶梯?
前端·json
从零开始学习人工智能5 小时前
FastMCP:构建 MCP 服务器和客户端的高效 Python 框架
服务器·前端·网络
烛阴6 小时前
自动化测试、前后端mock数据量产利器:Chance.js深度教程
前端·javascript·后端
好好学习O(∩_∩)O6 小时前
QT6引入QMediaPlaylist类
前端·c++·ffmpeg·前端框架
敲代码的小吉米6 小时前
前端HTML contenteditable 属性使用指南
前端·html
testleaf6 小时前
React知识点梳理
前端·react.js·typescript
站在风口的猪11086 小时前
《前端面试题:HTML5、CSS3、ES6新特性》
前端·css3·html5