Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions bridge/core/arraybuffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,43 @@ func ToArrayBuffer(vm *sobek.Runtime, data []byte) sobek.Value {
return vm.ToValue(vm.NewArrayBuffer(data))
}

// ToUint8Array converts a Go byte slice to a JavaScript Uint8Array.
// The returned Uint8Array is a copy of the original data, meaning modifications
// in JavaScript will not affect the Go slice.
//
// If the Uint8Array constructor is not available in the global scope, it gracefully
// falls back to returning an ArrayBuffer.
func ToUint8Array(vm *sobek.Runtime, data []byte) sobek.Value {
if len(data) == 0 {
buf := vm.NewArrayBuffer(nil)
ctor := vm.Get("Uint8Array")
if ctor != nil && !sobek.IsNull(ctor) && !sobek.IsUndefined(ctor) {
typedArray, err := vm.New(ctor.ToObject(vm), vm.ToValue(buf))
if err == nil && typedArray != nil {
return typedArray
}
}
return vm.ToValue(buf)
}

copiedData := make([]byte, len(data))
copy(copiedData, data)

buf := vm.NewArrayBuffer(copiedData)

ctor := vm.Get("Uint8Array")
if ctor == nil || sobek.IsNull(ctor) || sobek.IsUndefined(ctor) {
return vm.ToValue(buf)
}

typedArray, err := vm.New(ctor.ToObject(vm), vm.ToValue(buf))
if err != nil || typedArray == nil {
return vm.ToValue(buf)
}

return typedArray
}

// MapSharedBuffer exposes a Go byte slice as a global JavaScript TypedArray.
// The backing memory is shared between Go and JavaScript, meaning modifications
// from either side are immediately visible to the other.
Expand Down
36 changes: 31 additions & 5 deletions bridge/core/reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"fmt"
"reflect"
"strconv"
"sync"

"github.com/grafana/sobek"
Expand Down Expand Up @@ -337,6 +338,21 @@ func wrapJSCallback(vm *sobek.Runtime, callable sobek.Callable, goType reflect.T
}

func bindSlice(vm *sobek.Runtime, v reflect.Value, visited map[uintptr]sobek.Value) (sobek.Value, error) {
// @optimized: Fast path for []byte using ToUint8Array to avoid repeated reflection and per-element allocation.
if v.Type().Elem() == reflect.TypeOf(byte(0)) {
var bytes []byte
if v.CanAddr() || v.Kind() == reflect.Slice {
bytes = v.Bytes()
} else {
l := v.Len()
bytes = make([]byte, l)
for i := 0; i < l; i++ {
bytes[i] = byte(v.Index(i).Uint())
}
}
return ToUint8Array(vm, bytes), nil
}

// @optimized: Pre-allocate slice and use NewArray(vals...) to avoid repeated Set calls.
l := v.Len()
vals := make([]interface{}, l)
Expand All @@ -352,16 +368,26 @@ func bindSlice(vm *sobek.Runtime, v reflect.Value, visited map[uintptr]sobek.Val

func bindMap(vm *sobek.Runtime, v reflect.Value, visited map[uintptr]sobek.Value) (sobek.Value, error) {
obj := vm.NewObject()
for _, key := range v.MapKeys() {

// @optimized: Use MapRange to avoid allocating a slice of keys.
iter := v.MapRange()
for iter.Next() {
key := iter.Key()
var keyStr string
// @optimized: Avoid Sprintf if key is already a string.
if key.Kind() == reflect.String {

// @optimized: Fast path string formatting for common map key types to avoid fmt.Sprint allocation overhead.
switch key.Kind() {
case reflect.String:
keyStr = key.String()
} else {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
keyStr = strconv.FormatInt(key.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
keyStr = strconv.FormatUint(key.Uint(), 10)
default:
keyStr = fmt.Sprint(key.Interface())
}

val, err := bindValue(vm, v.MapIndex(key), visited)
val, err := bindValue(vm, iter.Value(), visited)
if err != nil {
return nil, err
}
Expand Down
8 changes: 0 additions & 8 deletions examples/projects/link-shortener/typego.lock

This file was deleted.

2 changes: 1 addition & 1 deletion examples/projects/link-shortener/typego.modules.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"dependencies": {
"github.com/gin-gonic/gin": "latest",
"github.com/gin-gonic/gin": "v1.11.0",
"github.com/teris-io/shortid": "latest"
},
"compiler": {
Expand Down
8 changes: 8 additions & 0 deletions pkg/cli/cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var BuildCmd = &cobra.Command{

virtualModules := make(map[string]string)
var bindBlock string
inspectedModules := make(map[string]bool)

if res != nil {
for _, imp := range res.Imports {
Expand All @@ -71,6 +72,7 @@ var BuildCmd = &cobra.Command{
continue
}

inspectedModules[cleanImp] = true
bindBlock += linker.GenerateShim(info, "pkg_"+info.Name)

var vmContent strings.Builder
Expand Down Expand Up @@ -100,6 +102,12 @@ var BuildCmd = &cobra.Command{
if cleanImp == "fmt" || cleanImp == "os" {
continue
}
// Skip standard library bypass (which don't need fetcher but still need imports)
// or external modules that successfully inspected.
// If it's an external module that failed inspection, don't import it to prevent "imported and not used".
if !inspectedModules[cleanImp] && strings.Contains(cleanImp, ".") {
continue
}
importBlock.WriteString(fmt.Sprintf("\t\"%s\"\n", cleanImp))
}
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/cli/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func runStandalone(filename string) {

virtualModules := make(map[string]string)
var bindBlock string
inspectedModules := make(map[string]bool)

if res != nil {
for _, imp := range res.Imports {
Expand All @@ -98,6 +99,7 @@ func runStandalone(filename string) {
}
if err := fetcher.Get(cleanImp); err == nil {
if info, err := linker.Inspect(cleanImp, fetcher.TempDir); err == nil {
inspectedModules[cleanImp] = true
bindBlock += linker.GenerateShim(info, "pkg_"+info.Name)
var vmContent strings.Builder
for _, fn := range info.Exports {
Expand Down Expand Up @@ -125,6 +127,9 @@ func runStandalone(filename string) {
case "fmt", "os", "sync", "net/http", "memory":
continue
}
if !inspectedModules[cleanImp] && strings.Contains(cleanImp, ".") {
continue
}
importBlock.WriteString(fmt.Sprintf("\t\"%s\"\n", cleanImp))
}
}
Expand Down
Loading