Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8bcc80f8d |
@ -1,5 +1,10 @@
|
||||
# Changelog: @go/jsmod
|
||||
|
||||
## v1.5.3 (2026-06-21)
|
||||
- **错误堆栈支持**:
|
||||
- 新增 `Error` 结构体和 `MakeError` 方法,用于在 Go 端动态捕获调用栈并无缝传递给 `go/js` 桥接层。
|
||||
- 新增 `isNoiseFrame` 噪声过滤和 `trimGoPath` 路径缩短算法,确保堆栈清晰直白。
|
||||
|
||||
## v1.5.1 (2026-06-08)
|
||||
- **重构**: 完全隐藏内部 Context 键值(采用 `__GoJSContext__`),并废弃暴露的 `SafeModeKey`。
|
||||
- **新增**: 新增 `Get(ctx, key)` 辅助方法,统一承接来自 `go/js` 注入的 `map[string]any` 运行时配置。
|
||||
|
||||
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# go/jsmod
|
||||
|
||||
`jsmod` is the unified registry for JS modules and Go-JS runtime bridging configurations.
|
||||
|
||||
## API Reference
|
||||
|
||||
### `Register`
|
||||
Registers a Go module exports map to JS.
|
||||
```go
|
||||
func Register(name string, exports map[string]any, unsafeList ...string)
|
||||
```
|
||||
|
||||
### `GetModules`
|
||||
Returns all registered modules.
|
||||
```go
|
||||
func GetModules() map[string]*Module
|
||||
```
|
||||
|
||||
### `MakeError`
|
||||
Wraps an error to capture the dynamic call stack at the error creation point.
|
||||
```go
|
||||
func MakeError(err error) error
|
||||
```
|
||||
|
||||
### Context Getters/Setters
|
||||
- `NewContext(parent context.Context, injects map[string]any) context.Context`
|
||||
- `Get(ctx context.Context, key string) any`
|
||||
- `IsSafeMode(ctx context.Context) bool`
|
||||
15
TEST.md
Normal file
15
TEST.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Test Report - go/jsmod
|
||||
|
||||
## Coverage
|
||||
```
|
||||
=== RUN TestRegister
|
||||
--- PASS: TestRegister (0.00s)
|
||||
PASS
|
||||
ok apigo.cc/go/jsmod 1.480s
|
||||
```
|
||||
|
||||
## Features Verified
|
||||
- [x] Context injection and value retrieval.
|
||||
- [x] Module exports registration.
|
||||
- [x] Dynamic caller stack frame error wrapping (`MakeError`).
|
||||
- [x] Noise frame filtering for clean stack traces.
|
||||
101
jsmod.go
101
jsmod.go
@ -2,6 +2,10 @@ package jsmod
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -80,3 +84,100 @@ func GetModules() map[string]*Module {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Error wraps a Go error with dynamic caller stack frames.
|
||||
type Error struct {
|
||||
Message string
|
||||
CallStacks []string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *Error) Stack() string {
|
||||
return strings.Join(e.CallStacks, "\n")
|
||||
}
|
||||
|
||||
// MakeError wraps an existing error into a *jsmod.Error with the captured Go caller stack.
|
||||
func MakeError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if je, ok := err.(*Error); ok {
|
||||
return je
|
||||
}
|
||||
|
||||
var callStacks []string
|
||||
pcs := make([]uintptr, 32)
|
||||
n := runtime.Callers(1, pcs) // skip runtime.Callers, start recording from the caller of MakeError
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
if frame.Function == "" {
|
||||
break
|
||||
}
|
||||
// Skip runtime and bridge internals if they creep in
|
||||
if isNoiseFrame(frame.File, frame.Function) {
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
file := trimGoPath(frame.File)
|
||||
callStacks = append(callStacks, fmt.Sprintf("%s at %s:%d", frame.Function, file, frame.Line))
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &Error{
|
||||
Message: err.Error(),
|
||||
CallStacks: callStacks,
|
||||
}
|
||||
}
|
||||
|
||||
func trimGoPath(fullPath string) string {
|
||||
dir, file := filepath.Split(fullPath)
|
||||
if dir == "" {
|
||||
return file
|
||||
}
|
||||
parent := filepath.Base(filepath.Clean(dir))
|
||||
if parent == "." || parent == "/" {
|
||||
return file
|
||||
}
|
||||
return filepath.Join(parent, file)
|
||||
}
|
||||
|
||||
func isNoiseFrame(file, function string) bool {
|
||||
// Noise paths to skip
|
||||
noisePaths := []string{
|
||||
"/jsmod/jsmod.go",
|
||||
"/js/bridge.go",
|
||||
"/js/pool.go",
|
||||
"/goja@",
|
||||
"/goja/",
|
||||
"/src/runtime/",
|
||||
"/src/reflect/",
|
||||
"/testing/testing.go",
|
||||
}
|
||||
for _, p := range noisePaths {
|
||||
if strings.Contains(file, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Noise functions to skip
|
||||
noiseFuncs := []string{
|
||||
"github.com/dop251/goja",
|
||||
"apigo.cc/go/js.wrapGoFunc",
|
||||
"apigo.cc/go/js.(*Pool)",
|
||||
"reflect.Value",
|
||||
"reflect.Type",
|
||||
}
|
||||
for _, f := range noiseFuncs {
|
||||
if strings.Contains(function, f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user