goscript 0.0.55 → 0.0.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -79
- package/compiler/compiler.go +18 -0
- package/compiler/compiler_test.go +11 -1
- package/compiler/expr-call.go +41 -3
- package/compiler/spec.go +12 -7
- package/compiler/stmt-range.go +1 -2
- package/dist/gs/reflect/iter.js.map +1 -1
- package/gs/reflect/iter.ts +4 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,108 @@ npm install -g goscript
|
|
|
64
64
|
goscript compile --package . --output ./dist
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
## 📦 Using Generated Code in Your Project
|
|
68
|
+
|
|
69
|
+
After compiling your Go code to TypeScript, you'll need to set up your project appropriately.
|
|
70
|
+
|
|
71
|
+
### TypeScript Configuration
|
|
72
|
+
|
|
73
|
+
Create or update your `tsconfig.json` with these settings:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"compilerOptions": {
|
|
78
|
+
"target": "ES2022",
|
|
79
|
+
"module": "ESNext",
|
|
80
|
+
"moduleResolution": "bundler",
|
|
81
|
+
"lib": ["ES2022", "esnext.disposable", "dom"],
|
|
82
|
+
"baseUrl": "./",
|
|
83
|
+
"paths": {
|
|
84
|
+
"@goscript/*": ["./path/to/generated/output/@goscript/*"]
|
|
85
|
+
},
|
|
86
|
+
"allowSyntheticDefaultImports": true,
|
|
87
|
+
"esModuleInterop": true,
|
|
88
|
+
"skipLibCheck": true,
|
|
89
|
+
"strict": true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Important requirements:**
|
|
95
|
+
- **`target: "ES2022"` or newer** - Required for `Disposable` and other features
|
|
96
|
+
- **`lib: ["esnext.disposable"]`** - Enables TypeScript's disposable types for resource management
|
|
97
|
+
- **`baseUrl` and `paths`** - Allows TypeScript to resolve `@goscript/*` imports
|
|
98
|
+
- **`moduleResolution: "bundler"`** - Recommended for modern bundlers
|
|
99
|
+
|
|
100
|
+
You should be able to use any TypeScript bundler to compile the generated TypeScript.
|
|
101
|
+
|
|
102
|
+
## 🛠️ Integration & Usage
|
|
103
|
+
|
|
104
|
+
### Command Line
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
goscript compile --package ./my-go-code --output ./dist
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Options:**
|
|
111
|
+
- `--package <path>` - Go package to compile (default: ".")
|
|
112
|
+
- `--output <dir>` - Output directory for TypeScript files
|
|
113
|
+
|
|
114
|
+
### Programmatic API
|
|
115
|
+
|
|
116
|
+
**Go:**
|
|
117
|
+
```go
|
|
118
|
+
import "github.com/aperturerobotics/goscript/compiler"
|
|
119
|
+
|
|
120
|
+
conf := &compiler.Config{OutputPath: "./dist"}
|
|
121
|
+
comp, err := compiler.NewCompiler(conf, logger, nil)
|
|
122
|
+
_, err = comp.CompilePackages(ctx, "your/package/path")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Node.js:**
|
|
126
|
+
```typescript
|
|
127
|
+
import { compile } from 'goscript'
|
|
128
|
+
|
|
129
|
+
await compile({
|
|
130
|
+
pkg: './my-go-package',
|
|
131
|
+
output: './dist'
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Frontend Frameworks
|
|
136
|
+
|
|
137
|
+
**React + GoScript:**
|
|
138
|
+
```typescript
|
|
139
|
+
import { NewCalculator } from '@goscript/myapp/calculator'
|
|
140
|
+
|
|
141
|
+
function CalculatorApp() {
|
|
142
|
+
const [calc] = useState(() => NewCalculator())
|
|
143
|
+
|
|
144
|
+
const handleAdd = () => {
|
|
145
|
+
const result = calc.Add(5, 3)
|
|
146
|
+
setResult(result)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return <button onClick={handleAdd}>Add 5 + 3</button>
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Vue + GoScript:**
|
|
154
|
+
```vue
|
|
155
|
+
<script setup lang="ts">
|
|
156
|
+
import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
|
|
157
|
+
|
|
158
|
+
const users = ref([
|
|
159
|
+
NewUser(1, "Alice", "alice@example.com")
|
|
160
|
+
])
|
|
161
|
+
|
|
162
|
+
const searchUser = (email: string) => {
|
|
163
|
+
return FindUserByEmail(users.value, email)
|
|
164
|
+
}
|
|
165
|
+
</script>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
|
|
67
169
|
## 💡 See It In Action
|
|
68
170
|
|
|
69
171
|
### Example: User Management
|
|
@@ -196,72 +298,6 @@ async function handleMessages() {
|
|
|
196
298
|
}
|
|
197
299
|
```
|
|
198
300
|
|
|
199
|
-
## 🛠️ Integration & Usage
|
|
200
|
-
|
|
201
|
-
### Command Line
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
goscript compile --package ./my-go-code --output ./dist
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
**Options:**
|
|
208
|
-
- `--package <path>` - Go package to compile (default: ".")
|
|
209
|
-
- `--output <dir>` - Output directory for TypeScript files
|
|
210
|
-
|
|
211
|
-
### Programmatic API
|
|
212
|
-
|
|
213
|
-
**Go:**
|
|
214
|
-
```go
|
|
215
|
-
import "github.com/aperturerobotics/goscript/compiler"
|
|
216
|
-
|
|
217
|
-
conf := &compiler.Config{OutputPath: "./dist"}
|
|
218
|
-
comp, err := compiler.NewCompiler(conf, logger, nil)
|
|
219
|
-
_, err = comp.CompilePackages(ctx, "your/package/path")
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
**Node.js:**
|
|
223
|
-
```typescript
|
|
224
|
-
import { compile } from 'goscript'
|
|
225
|
-
|
|
226
|
-
await compile({
|
|
227
|
-
pkg: './my-go-package',
|
|
228
|
-
output: './dist'
|
|
229
|
-
})
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### Frontend Frameworks
|
|
233
|
-
|
|
234
|
-
**React + GoScript:**
|
|
235
|
-
```typescript
|
|
236
|
-
import { NewCalculator } from '@goscript/myapp/calculator'
|
|
237
|
-
|
|
238
|
-
function CalculatorApp() {
|
|
239
|
-
const [calc] = useState(() => NewCalculator())
|
|
240
|
-
|
|
241
|
-
const handleAdd = () => {
|
|
242
|
-
const result = calc.Add(5, 3)
|
|
243
|
-
setResult(result)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return <button onClick={handleAdd}>Add 5 + 3</button>
|
|
247
|
-
}
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
**Vue + GoScript:**
|
|
251
|
-
```vue
|
|
252
|
-
<script setup lang="ts">
|
|
253
|
-
import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
|
|
254
|
-
|
|
255
|
-
const users = ref([
|
|
256
|
-
NewUser(1, "Alice", "alice@example.com")
|
|
257
|
-
])
|
|
258
|
-
|
|
259
|
-
const searchUser = (email: string) => {
|
|
260
|
-
return FindUserByEmail(users.value, email)
|
|
261
|
-
}
|
|
262
|
-
</script>
|
|
263
|
-
```
|
|
264
|
-
|
|
265
301
|
## 🚀 What's Next?
|
|
266
302
|
|
|
267
303
|
**Current Status:**
|
|
@@ -276,19 +312,7 @@ const searchUser = (email: string) => {
|
|
|
276
312
|
- ⚡ Performance optimizations
|
|
277
313
|
- 🔧 Better tooling integration
|
|
278
314
|
|
|
279
|
-
Check
|
|
280
|
-
|
|
281
|
-
## 🤝 Real-World Use Cases
|
|
282
|
-
|
|
283
|
-
**Fintech:** Share complex financial calculations between Go services and trading dashboards
|
|
284
|
-
|
|
285
|
-
**Gaming:** Run the same game logic on servers and in browser clients
|
|
286
|
-
|
|
287
|
-
**Data Processing:** Use identical algorithms for backend ETL and frontend analytics
|
|
288
|
-
|
|
289
|
-
**Validation:** Keep business rules consistent across your entire stack
|
|
290
|
-
|
|
291
|
-
Ready to eliminate code duplication? [Get started now](#-get-started-in-2-minutes) 🚀
|
|
315
|
+
Check the [compliance tests](./compliance/COMPLIANCE.md) for detailed progress.
|
|
292
316
|
|
|
293
317
|
## License
|
|
294
318
|
|
package/compiler/compiler.go
CHANGED
|
@@ -434,6 +434,24 @@ func (c *PackageCompiler) generateIndexFile(compiledFiles []string) error {
|
|
|
434
434
|
case *ast.FuncDecl:
|
|
435
435
|
if d.Recv == nil && d.Name.IsExported() {
|
|
436
436
|
valueSymbols = append(valueSymbols, sanitizeIdentifier(d.Name.Name))
|
|
437
|
+
} else if d.Recv != nil && len(d.Recv.List) == 1 && d.Name.IsExported() {
|
|
438
|
+
recvField := d.Recv.List[0]
|
|
439
|
+
recvTypeExpr := recvField.Type
|
|
440
|
+
if star, ok := recvTypeExpr.(*ast.StarExpr); ok {
|
|
441
|
+
recvTypeExpr = star.X
|
|
442
|
+
}
|
|
443
|
+
if ident, ok := recvTypeExpr.(*ast.Ident); ok {
|
|
444
|
+
typeObj := c.pkg.TypesInfo.ObjectOf(ident)
|
|
445
|
+
if typeObj != nil && typeObj.Exported() {
|
|
446
|
+
if typeName, ok := typeObj.(*types.TypeName); ok {
|
|
447
|
+
underlying := typeName.Type().Underlying()
|
|
448
|
+
if _, isStruct := underlying.(*types.Struct); !isStruct {
|
|
449
|
+
methodName := sanitizeIdentifier(ident.Name + "_" + d.Name.Name)
|
|
450
|
+
valueSymbols = append(valueSymbols, methodName)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
437
455
|
}
|
|
438
456
|
case *ast.GenDecl:
|
|
439
457
|
for _, spec := range d.Specs {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package compiler_test
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"fmt"
|
|
4
5
|
"os"
|
|
5
6
|
"os/exec"
|
|
6
7
|
"path/filepath"
|
|
@@ -135,5 +136,14 @@ func getParentGoModulePath() (string, error) {
|
|
|
135
136
|
if err != nil {
|
|
136
137
|
return "", err
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
+
// note: in a go work configuration, go list -m can report multiple modules
|
|
140
|
+
// only one of which is the goscript case, so we need to filter:
|
|
141
|
+
pf := strings.Fields(strings.TrimSpace(string(output)))
|
|
142
|
+
pf = slices.DeleteFunc(pf, func(n string) bool {
|
|
143
|
+
return !strings.HasSuffix(n, "goscript")
|
|
144
|
+
})
|
|
145
|
+
if len(pf) != 1 {
|
|
146
|
+
return "", fmt.Errorf("'go list -m' did not have exactly 1 goscript package -- run in root of goscript package")
|
|
147
|
+
}
|
|
148
|
+
return pf[0], nil
|
|
139
149
|
}
|
package/compiler/expr-call.go
CHANGED
|
@@ -349,9 +349,47 @@ func (c *GoToTSCompiler) writeWrapperTypeMethodCall(exp *ast.CallExpr, selectorE
|
|
|
349
349
|
c.tsw.WriteLiterally(selectorExpr.Sel.Name)
|
|
350
350
|
c.tsw.WriteLiterally("(")
|
|
351
351
|
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
352
|
+
// Write the receiver (the object the method is called on)
|
|
353
|
+
// For pointer receiver methods, we need to pass the VarRef instead of the value
|
|
354
|
+
receiverNeedsVarRef := false
|
|
355
|
+
|
|
356
|
+
// Check if the method has a pointer receiver by looking at the method signature
|
|
357
|
+
if selection := c.pkg.TypesInfo.Selections[selectorExpr]; selection != nil {
|
|
358
|
+
if methodObj := selection.Obj(); methodObj != nil {
|
|
359
|
+
if methodFunc, ok := methodObj.(*types.Func); ok {
|
|
360
|
+
if sig, ok := methodFunc.Type().(*types.Signature); ok && sig != nil {
|
|
361
|
+
if recv := sig.Recv(); recv != nil {
|
|
362
|
+
if _, isPointer := recv.Type().(*types.Pointer); isPointer {
|
|
363
|
+
receiverNeedsVarRef = true
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if receiverNeedsVarRef {
|
|
372
|
+
// For pointer receivers, we need to pass the VarRef
|
|
373
|
+
// Convert p.field to p._fields.field
|
|
374
|
+
if selExpr, ok := selectorExpr.X.(*ast.SelectorExpr); ok {
|
|
375
|
+
if baseIdent, ok := selExpr.X.(*ast.Ident); ok {
|
|
376
|
+
c.tsw.WriteLiterally(baseIdent.Name)
|
|
377
|
+
c.tsw.WriteLiterally("._fields.")
|
|
378
|
+
c.tsw.WriteLiterally(selExpr.Sel.Name)
|
|
379
|
+
} else {
|
|
380
|
+
if err := c.WriteValueExpr(selectorExpr.X); err != nil {
|
|
381
|
+
return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
if err := c.WriteValueExpr(selectorExpr.X); err != nil {
|
|
386
|
+
return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
if err := c.WriteValueExpr(selectorExpr.X); err != nil {
|
|
391
|
+
return true, fmt.Errorf("failed to write wrapper type method receiver: %w", err)
|
|
392
|
+
}
|
|
355
393
|
}
|
|
356
394
|
|
|
357
395
|
// Add other arguments
|
package/compiler/spec.go
CHANGED
|
@@ -376,8 +376,10 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
|
|
|
376
376
|
}
|
|
377
377
|
recvField := funcDecl.Recv.List[0]
|
|
378
378
|
recvType := recvField.Type
|
|
379
|
+
isPointerReceiver := false
|
|
379
380
|
if starExpr, ok := recvType.(*ast.StarExpr); ok {
|
|
380
381
|
recvType = starExpr.X
|
|
382
|
+
isPointerReceiver = true
|
|
381
383
|
}
|
|
382
384
|
|
|
383
385
|
// Check for both simple identifiers (FileMode) and generic types (FileMode[T])
|
|
@@ -391,12 +393,7 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
|
|
|
391
393
|
}
|
|
392
394
|
|
|
393
395
|
if recvTypeName == className {
|
|
394
|
-
|
|
395
|
-
c.tsw.WriteLiterally("export ")
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// Generate function signature: export function TypeName_MethodName(receiver: TypeName, ...args): ReturnType
|
|
399
|
-
c.tsw.WriteLiterally("function ")
|
|
396
|
+
c.tsw.WriteLiterally("export function ")
|
|
400
397
|
c.tsw.WriteLiterally(className)
|
|
401
398
|
c.tsw.WriteLiterally("_")
|
|
402
399
|
c.tsw.WriteLiterally(funcDecl.Name.Name)
|
|
@@ -413,7 +410,15 @@ func (c *GoToTSCompiler) WriteNamedTypeWithMethods(a *ast.TypeSpec) error {
|
|
|
413
410
|
}
|
|
414
411
|
c.tsw.WriteLiterally(receiverParamName)
|
|
415
412
|
c.tsw.WriteLiterally(": ")
|
|
416
|
-
|
|
413
|
+
|
|
414
|
+
// For pointer receivers, use VarRef<T>
|
|
415
|
+
if isPointerReceiver {
|
|
416
|
+
c.tsw.WriteLiterally("$.VarRef<")
|
|
417
|
+
c.tsw.WriteLiterally(className)
|
|
418
|
+
c.tsw.WriteLiterally(">")
|
|
419
|
+
} else {
|
|
420
|
+
c.tsw.WriteLiterally(className)
|
|
421
|
+
}
|
|
417
422
|
|
|
418
423
|
// Add other parameters
|
|
419
424
|
if funcDecl.Type.Params != nil && len(funcDecl.Type.Params.List) > 0 {
|
package/compiler/stmt-range.go
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iter.js","sourceRoot":"","sources":["../../../gs/reflect/iter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,OAAO,EAAE,MAAM,WAAW,CAAA;AAKhD,MAAM,UAAU,QAAQ,
|
|
1
|
+
{"version":3,"file":"iter.js","sourceRoot":"","sources":["../../../gs/reflect/iter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,OAAO,EAAE,MAAM,WAAW,CAAA;AAKhD,MAAM,UAAU,QAAQ,CACtB,GAAM,EACN,CAAO;IAEP,8CAA8C;IAE9C,2CAA2C;IAC3C,wBAAwB;IACxB,OAAO,CAAC,MAAsC,EAAQ,EAAE;QACtD,IAAI,OAAO,GAAG,CAAE,CAAC,OAAQ,EAAE,IAAI,EAAE,CAAA;QACjC,8CAA8C;QAE9C,2CAA2C;QAC3C,wBAAwB;QACxB,KAAK,IAAI,CAAC,GAAG,CAAiB,EAAE,CAAC,GAAI,GAAoB,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/D,IAAI,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;YAC5B,2CAA2C;YAC3C,wBAAwB;YACxB,IAAI,OAAO,EAAE,CAAC;gBACZ,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAA;YAC9B,CAAC;YACD,IAAI,CAAC,MAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClB,OAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
|
package/gs/reflect/iter.ts
CHANGED
|
@@ -3,12 +3,10 @@ import { uintptr } from './types.js'
|
|
|
3
3
|
|
|
4
4
|
import * as iter from '@goscript/iter/index.js'
|
|
5
5
|
|
|
6
|
-
export function rangeNum<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
N extends number | number,
|
|
11
|
-
>(num: N, t: Type): iter.Seq<Value> {
|
|
6
|
+
export function rangeNum<T extends number | uintptr, N extends number | number>(
|
|
7
|
+
num: N,
|
|
8
|
+
t: Type,
|
|
9
|
+
): iter.Seq<Value> {
|
|
12
10
|
// cannot use range T(v) because no core type.
|
|
13
11
|
|
|
14
12
|
// if the iteration value type is define by
|