Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8bcc80f8d |
@ -1,5 +1,10 @@
|
|||||||
# Changelog: @go/jsmod
|
# Changelog: @go/jsmod
|
||||||
|
|
||||||
|
## v1.5.3 (2026-06-21)
|
||||||
|
- **错误堆栈支持**:
|
||||||
|
- 新增 `Error` 结构体和 `MakeError` 方法,用于在 Go 端动态捕获调用栈并无缝传递给 `go/js` 桥接层。
|
||||||
|
- 新增 `isNoiseFrame` 噪声过滤和 `trimGoPath` 路径缩短算法,确保堆栈清晰直白。
|
||||||
|
|
||||||
## v1.5.1 (2026-06-08)
|
## v1.5.1 (2026-06-08)
|
||||||
- **重构**: 完全隐藏内部 Context 键值(采用 `__GoJSContext__`),并废弃暴露的 `SafeModeKey`。
|
- **重构**: 完全隐藏内部 Context 键值(采用 `__GoJSContext__`),并废弃暴露的 `SafeModeKey`。
|
||||||
- **新增**: 新增 `Get(ctx, key)` 辅助方法,统一承接来自 `go/js` 注入的 `map[string]any` 运行时配置。
|
- **新增**: 新增 `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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,3 +84,100 @@ func GetModules() map[string]*Module {
|
|||||||
}
|
}
|
||||||
return res
|
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