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 一遍太贵。
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user