-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathmodule.go
More file actions
176 lines (149 loc) · 5.27 KB
/
module.go
File metadata and controls
176 lines (149 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package quickjs
import (
"errors"
"fmt"
"unsafe"
)
/*
#include "bridge.h"
*/
import "C"
// =============================================================================
// MODULE TYPES AND STRUCTURES
// =============================================================================
// ModuleExportEntry represents a single module export
type ModuleExportEntry struct {
Name string // Export name ("default" for default export)
Spec ValueSpec // Export definition for Build-time materialization
}
// ModuleBuilder provides a fluent API for building JavaScript modules
// Uses builder pattern for easy and readable module definition
type ModuleBuilder struct {
name string // Module name
exports []ModuleExportEntry // All exports (including default)
}
// =============================================================================
// MODULE BUILDER API
// =============================================================================
// NewModuleBuilder creates a new ModuleBuilder with the specified name
// This is the entry point for building JavaScript modules
func NewModuleBuilder(name string) *ModuleBuilder {
return &ModuleBuilder{
name: name,
exports: make([]ModuleExportEntry, 0),
}
}
// Export adds an export to the module
// This is the core method that handles all types of exports including default
// For default export, use name="default"
// Deprecated: Use ExportValue or ExportLiteral for declarative, reusable module definitions.
func (mb *ModuleBuilder) Export(name string, value *Value) *ModuleBuilder {
var spec ValueSpec
if value != nil {
spec = contextValueSpec{value: value}
}
return mb.ExportValue(name, spec)
}
// ExportValue adds an export ValueSpec to the module.
func (mb *ModuleBuilder) ExportValue(name string, spec ValueSpec) *ModuleBuilder {
mb.exports = append(mb.exports, ModuleExportEntry{
Name: name,
Spec: spec,
})
return mb
}
// ExportLiteral adds a literal export definition to the module.
func (mb *ModuleBuilder) ExportLiteral(name string, value interface{}) *ModuleBuilder {
return mb.ExportValue(name, MarshalSpec{Value: value})
}
// Build creates and registers the JavaScript module in the given context
// The module will be available for import in JavaScript code
// ValueSpec entries are captured by shallow snapshot. Do not mutate pointer-based
// ValueSpec implementations after Build, or module initialization may observe changes.
// Do not modify the state of passed spec objects after Build.
func (mb *ModuleBuilder) Build(ctx *Context) error {
return createModule(ctx, mb)
}
// =============================================================================
// MODULE CREATION IMPLEMENTATION
// =============================================================================
// validateModuleBuilder validates ModuleBuilder configuration
func validateModuleBuilder(builder *ModuleBuilder) error {
if builder.name == "" {
return errors.New("module name cannot be empty")
}
// Check for duplicate export names
nameSet := make(map[string]bool)
for _, export := range builder.exports {
if export.Name == "" {
return errors.New("export name cannot be empty")
}
if export.Spec == nil {
return fmt.Errorf("export value is required: %s", export.Name)
}
if nameSet[export.Name] {
return fmt.Errorf("duplicate export name: %s", export.Name)
}
nameSet[export.Name] = true
}
return nil
}
func cloneModuleBuilder(builder *ModuleBuilder) *ModuleBuilder {
if builder == nil {
return nil
}
clonedExports := make([]ModuleExportEntry, len(builder.exports))
copy(clonedExports, builder.exports)
return &ModuleBuilder{
name: builder.name,
exports: clonedExports,
}
}
// createModule implements the core module creation logic
// This function handles the QuickJS module creation and registration:
// 1. Module creation phase: create C module and declare exports
// 2. Module initialization phase: set actual export values via proxy
// The module will be available for import in JavaScript code
func createModule(ctx *Context, builder *ModuleBuilder) error {
// Step 1: Validate module builder
if err := validateModuleBuilder(builder); err != nil {
return fmt.Errorf("module validation failed: %v", err)
}
snapshot := cloneModuleBuilder(builder)
// Step 2: Store a build snapshot in HandleStore for initialization access.
builderID := ctx.handleStore.Store(snapshot)
// Step 3: Prepare export names for C function
exportNames := make([]*C.char, len(snapshot.exports))
exportCount := len(snapshot.exports)
// Convert Go strings to C strings
for i, export := range snapshot.exports {
exportNames[i] = C.CString(export.Name)
}
// Prepare parameters for C function call
moduleName := C.CString(snapshot.name)
var exportNamesPtr **C.char
if exportCount > 0 {
exportNamesPtr = &exportNames[0]
}
// Step 4: Call C function to create module
result := C.CreateModule(
ctx.ref,
moduleName,
exportNamesPtr,
C.int(exportCount),
C.int32_t(builderID),
)
// Step 5: Clean up C strings
C.free(unsafe.Pointer(moduleName))
for _, cStr := range exportNames {
C.free(unsafe.Pointer(cStr))
}
// Step 6: Check result and handle errors
if result < 0 {
// Clean up HandleStore on failure
ctx.handleStore.Delete(builderID)
return ctx.Exception()
}
// Module is now created and registered, ready for import
return nil
}