Files
jscriptx/esm/async_test.go
what 6ce9b483fd feat(esm): 按目录加载脚本、打包与热更新
把一个目录当脚本仓库:按路径寻址、esbuild 打包、内容变了自动重编。

  - Loader    扫目录建索引,Load(name) 给出打好包的源码和版本号
  - bundler   esbuild 的封装。ESM 格式而不是 IIFE——IIFE 会附带一整套
              CommonJS interop helper,每建一个 VM 都要重跑一遍
  - plugin    把扩展的 TS 模块变成可以 import 的虚拟模块,磁盘上没有文件
  - typings   把这些虚拟模块的类型按 node_modules 布局落盘,编辑器才认识

esbuild 原生实现了 Node 的模块解析,所以脚本能直接 import node_modules
里的第三方库。写出来的类型文件是 index.ts 而不是 index.d.ts:扩展给的是
真正的模块源码,里面可能带实现,声明文件里不允许有实现。

node_modules 不参与热更新的版本计算——依赖包是装出来的,改动总伴随显式的
安装动作,而真实的 npm 包动辄上千个文件,每次取脚本 stat 一遍太贵。
2026-09-05 22:13:57 +08:00

95 lines
2.2 KiB
Go

package esm_test
import (
"context"
"errors"
"strings"
"testing"
"git.fsdpf.net/go/jscriptx"
)
// async 方法要能像同步方法一样用:Promise 由本库解包,不泄漏 goja 类型。
func TestAsync_自动解包(t *testing.T) {
e := newEngine(t, "testdata/app/src")
ctx := context.Background()
obj, err := e.New(ctx, "Async/AsyncController")
if err != nil {
t.Fatal(err)
}
defer obj.Close()
for _, c := range []struct{ fn, want string }{
{"Sync", "同步结果"},
{"Async", "异步结果"},
{"AwaitSomething", "await 的结果"},
} {
got, err := obj.Call(ctx, c.fn)
if err != nil {
t.Errorf("%s 失败: %v", c.fn, err)
continue
}
if got != c.want {
t.Errorf("%s = %#v, want %q", c.fn, got, c.want)
}
}
// CallInto 也要解包
var s string
if err := obj.CallInto(ctx, "Async", &s); err != nil {
t.Fatalf("CallInto 失败: %v", err)
}
if s != "异步结果" {
t.Errorf("CallInto = %q", s)
}
}
// async 方法抛异常(Promise reject)要变成正常的 Go error。
func TestAsync_reject转成错误(t *testing.T) {
e := newEngine(t, "testdata/app/src")
ctx := context.Background()
obj, err := e.New(ctx, "Async/AsyncController")
if err != nil {
t.Fatal(err)
}
defer obj.Close()
_, err = obj.Call(ctx, "Fails")
if err == nil {
t.Fatal("应该报错")
}
if !errors.Is(err, jscriptx.ErrPromiseRejected) {
t.Errorf("want ErrPromiseRejected, got %v", err)
}
var jsErr *jscriptx.Error
if errors.As(err, &jsErr) && !strings.Contains(jsErr.Msg, "异步失败") {
t.Errorf("没带上 reject 的原因: %q", jsErr.Msg)
}
t.Logf("%v", err)
}
// goja 没有事件循环,等待真正的异步会让 Promise 永远 pending,要报清楚。
func TestAsync_永不完成的Promise(t *testing.T) {
e := newEngine(t, "testdata/app/src")
ctx := context.Background()
obj, err := e.New(ctx, "Async/AsyncController")
if err != nil {
t.Fatal(err)
}
defer obj.Close()
_, err = obj.Call(ctx, "Never")
if !errors.Is(err, jscriptx.ErrPromisePending) {
t.Fatalf("want ErrPromisePending, got %v", err)
}
var jsErr *jscriptx.Error
errors.As(err, &jsErr)
if !strings.Contains(jsErr.Msg, "事件循环") {
t.Errorf("报错没解释原因: %q", jsErr.Msg)
}
t.Logf("%v", jsErr.Msg)
}