Esbuild 调试 Golang 进程 和 NodeJs 通信 小白教程

1,242 阅读4分钟

Esbuild 调试 Golang 进程 和 NodeJs 通信 小白教程

背景

在 esbuild 中有很多同学,都进行了nodejs 插件的开发,但是具体怎么通过 nodejs 进程 通信到 golang 中呢?通过这篇文章能让你对 esbuild 插件源码有一个 200% 的了解。

Esbuild 隐藏的秘密

重新编译方法 ·installForTests·

installForTests 是一个 可以重新编译 esbuild 函数,是作者特意留下来用于调试开发esbuild用的,下面这段代码 就是一个 使用的 示例

import {loadPlugin, resolvePlugin} from '../plugin'
import fs from 'fs';
import path from 'path';

// esbuild 源码中 目录 esbuild/scripts/esbuild.js
const esbuild = require('../../scripts/esbuild').installForTests()
// const esbuild = require('../../npm/esbuild/lib/main')

async function exec() {

  let res = await esbuild.build({
    entryPoints: ["src/index.tsx"],
    outfile: "dist/main.js",
    minify: false,
    bundle: true,
    // watch: true,
    incremental: true,
    sourcemap: true,
    plugins: [loadPlugin, resolvePlugin]
  });
}

查看这段 installForTests 源码,这里要 关注两行代码

exports.installForTests = () => {
  // Build the "esbuild" binary and library
  const esbuildPath = exports.buildBinary()
  buildNeutralLib(esbuildPath)
  ...
}

buildBinary 该方法是 构建 esbuild 上卷 golang 的字节码,通过 nodejs childprocess 调用 go build 这里我们要留意该方法 buildBinary 中的 github/esbuild/scripts/esbuild.jsline 298

// 目前代码 不支持 gops 附加调试
childProcess.execFileSync('go', ['build', '-gcflags="all=-N -l"', './cmd/esbuild'], { cwd: repoDir, stdio: 'ignore' })

// 注意!!! 建议修改如下 增加 '-ldflags=-s -w' 参数 来支持 gops 调试

childProcess.execFileSync('go', ['build', '-ldflags=-s -w', './cmd/esbuild'], { cwd: repoDir, stdio: 'ignore' })

buildNeutralLib 该方法是 esbuild 自举 构建 nodejs -> npm/esbuild/lib/main.js 调用函数,esbuild 是不含有任何 npm run build 构建调用。我们这里有一些需要关注的代码。lib/shared/common.ts 这个代码大部分都会被构建于 npm/esbuild/lib/main.js 主包调用 nodejs 中逻辑中。

// line 662 这里是 nodejs 发送 streambuffer unit8array 通过 ipc 到 golang 中 核心方法
// 这里的 Req 就是 每个 nodejs 调用 golang 中,参数类型 
// sendRequest<protocol.RebuildRequest, protocol.BuildResponse> 发送重新构建 示例。
// 该方法 是一个 镜像方法 在golang 中 回调 nodejs 中有同样的实现。
 let sendRequest = <Req, Res>(
    refs: Refs | null,
    value: Req,
    callback: (error: string | null, response: Res | null) => void
  ): void => {
    ....
  }

rebuild 参数如下,esbuild 命令类型 “command” 执行 task 时,esbuild 会有个 'id' key 来标识多线程中 构建结果。

export interface RebuildRequest {
  command: "rebuild";
  key: number;
  //changefile: string[];
}

重定向 字节码文件的 环境变量 ESBUILD_BINARY_PATH

在 esbuild 主包中 install.js -> checkAndPreparePackage -> validateBinaryVersion 函数中,可以替换golang go-build 构建结果 esbuild bin文件。

使用示例如下

{
  "scripts": {
		"build": "ESBUILD_BINARY_PATH=/Users/xxx/Desktop/github/esbuild/esbuild ts-node-dev ./build/index.ts"
	},
}

如何附加调试 esbuild 进程,包含(vscode,goland)

  1. 下载esbuild 源码到本地
  2. 在任意目录,编写一个 测试 demo 示例 并 指定 installForTests 为 esbuild 指定导出,(参考 章节2 ),且确定增加了 gops 的编译参数 -ldflags=-s -w
  3. 在 packjson 中指定,环境变量到 本地 github 下载源码目录。(参考 章节3 )
  4. 指定esbuild 构建模式为 watch: true 或者 incremental: true,保证 执行命令行后,golang的进程会挂起不会释放。
  5. 本地确认 golang 版本,确认已经 dlv gotools 调试工具
$ git clone https://github.com/go-delve/delve
$ cd delve
$ go install github.com/go-delve/delve/cmd/dlv

安装成功后,在命令行中输入 dlv 会有如下提示:

➜  ~ dlv
Delve is a source level debugger for Go programs.

Delve enables you to interact with your program by controlling the execution of the process,
evaluating variables, and providing information of thread / goroutine state, CPU register state and more.

The goal of this tool is to provide a simple yet powerful interface for debugging Go programs.

Pass flags to the program you are debugging using `--`, for example:

`dlv exec ./hello -- server --config conf/config.toml`

Usage:
  dlv [command]
  1. vscode 中 创建 .vscode/launch.json 并且填写如下内容

在命令行输入 $: ps -ef|grep esbuild 查询如果下结果

501 72470 72428   0  6:58下午 ttys000    0:00.09 /Users/xxx/Desktop/github/esbuild/esbuild --service=0.14.25 --ping

上述结果的 第二个数字 即为 esbuild golang 进程中的 PID 填写到如下 json 当中

{
  // 使用 IntelliSense 了解相关属性。 
  // 悬停以查看现有属性的描述。
  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach to Process",
      "type": "go",
      "request": "attach",
      "mode": "local",
      "processId": 72470
    }
  ]
}

启动附加后,可以尝试 internal/bundler/bundler.gofunc runOnLoadPlugins 中 设置断点, line 849 行左右。然后尝试修改 demo 中文件 进行欢乐的调试。

PS:vscode golang extension 扩展中已经自动帮你安装了 gops 附加工具,调试的时候自动启动,使用vsocde 同学可以自动忽略!不需要了解 gops 是何种工具,只要保证第5步 中 delve 成功安装即可

  1. Goland 附加调试
  • 确保你已经通过 jetbrains 网站中,下载好了 Goland 源码,且通过 Goland 打开 esbuild 源码
  • 打开 internal/bundler/bundler.go 文件,任意*.go 文件皆可。已 mac 为标准 按住 ctrl + option + f5, 第一次 Goland 会自动帮你下载 gops,然后就出现 当前机器下所有 golang 的进程小窗口,选择其中 esbuild 的 进行调试
  • (参考链接) www.jetbrains.com/help/go/att…
  • 启动附加后,可以尝试 internal/bundler/bundler.gofunc runOnLoadPlugins 中 设置断点, line 849 行左右。然后尝试修改 demo 中文件 进行欢乐的调试。
  1. 严格注意 go version 1.16.x 中 gops 附加会有严重bug 请确保当前 go version 并不是上述版本在进行上述尝试!