@thelacanians/vue-native-cli 0.4.13 → 0.4.15
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/dist/cli.js +472 -192
- package/native/android/VueNativeCore/build.gradle.kts +1 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +38 -5
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VListFactory.kt +33 -13
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VScrollViewFactory.kt +27 -6
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VSliderFactory.kt +9 -2
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Helpers/EventThrottle.kt +57 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +79 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/JSPolyfills.swift +15 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +106 -112
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift +19 -2
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift +9 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift +8 -3
- package/package.json +1 -1
|
@@ -69,6 +69,59 @@ public final class NativeBridge {
|
|
|
69
69
|
|
|
70
70
|
private static var traitObserverKey: UInt8 = 99
|
|
71
71
|
|
|
72
|
+
// MARK: - Bridge Function Registration
|
|
73
|
+
|
|
74
|
+
/// Register `__VN_flushOperations`, `__VN_teardown`, `__VN_log`, and `__VN_handleError`
|
|
75
|
+
/// on the given JSContext. Called from both `initialize()` and `reloadWithBundle()`.
|
|
76
|
+
private func registerBridgeFunctions(on context: JSContext) {
|
|
77
|
+
let flushOps: @convention(block) (JSValue) -> Void = { [weak self] opsValue in
|
|
78
|
+
guard let self = self else { return }
|
|
79
|
+
guard let jsonString = opsValue.toString(), !jsonString.isEmpty else {
|
|
80
|
+
NSLog("[VueNative Bridge] Warning: Empty operations batch")
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
guard let data = jsonString.data(using: .utf8),
|
|
85
|
+
let operations = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
86
|
+
NSLog("[VueNative Bridge] Error: Failed to parse operations JSON")
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
DispatchQueue.main.async { [weak self] in
|
|
91
|
+
self?.processOperations(operations)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
context.setObject(flushOps, forKeyedSubscript: "__VN_flushOperations" as NSString)
|
|
95
|
+
|
|
96
|
+
let teardown: @convention(block) () -> Void = { [weak self] in
|
|
97
|
+
NSLog("[VueNative] Teardown called from JS")
|
|
98
|
+
_ = self
|
|
99
|
+
}
|
|
100
|
+
context.setObject(teardown, forKeyedSubscript: "__VN_teardown" as NSString)
|
|
101
|
+
|
|
102
|
+
let log: @convention(block) (JSValue) -> Void = { message in
|
|
103
|
+
NSLog("[VueNative JS] \(message.toString() ?? "")")
|
|
104
|
+
}
|
|
105
|
+
context.setObject(log, forKeyedSubscript: "__VN_log" as NSString)
|
|
106
|
+
|
|
107
|
+
let handleError: @convention(block) (JSValue) -> Void = { errorInfoValue in
|
|
108
|
+
let jsonString = errorInfoValue.toString() ?? "{}"
|
|
109
|
+
NSLog("[VueNative Error] %@", jsonString)
|
|
110
|
+
|
|
111
|
+
if let data = jsonString.data(using: .utf8),
|
|
112
|
+
let info = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
113
|
+
let message = info["message"] as? String ?? "Unknown error"
|
|
114
|
+
let stack = info["stack"] as? String ?? ""
|
|
115
|
+
let componentName = info["componentName"] as? String ?? "unknown"
|
|
116
|
+
NSLog("[VueNative Error] Component: %@, Message: %@", componentName, message)
|
|
117
|
+
if !stack.isEmpty {
|
|
118
|
+
NSLog("[VueNative Error] Stack: %@", stack)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
context.setObject(handleError, forKeyedSubscript: "__VN_handleError" as NSString)
|
|
123
|
+
}
|
|
124
|
+
|
|
72
125
|
// MARK: - Setup
|
|
73
126
|
|
|
74
127
|
/// Initialize the bridge. Must be called after JSRuntime.initialize().
|
|
@@ -83,61 +136,7 @@ public final class NativeBridge {
|
|
|
83
136
|
runtime.jsQueue.async { [weak self] in
|
|
84
137
|
guard let self = self, let context = runtime.context else { return }
|
|
85
138
|
|
|
86
|
-
|
|
87
|
-
guard let self = self else { return }
|
|
88
|
-
guard let jsonString = opsValue.toString(), !jsonString.isEmpty else {
|
|
89
|
-
NSLog("[VueNative Bridge] Warning: Empty operations batch")
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Parse JSON on the JS queue (avoid main thread work)
|
|
94
|
-
guard let data = jsonString.data(using: .utf8),
|
|
95
|
-
let operations = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
96
|
-
NSLog("[VueNative Bridge] Error: Failed to parse operations JSON")
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Process operations on the main thread
|
|
101
|
-
DispatchQueue.main.async { [weak self] in
|
|
102
|
-
self?.processOperations(operations)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
context.setObject(flushOps, forKeyedSubscript: "__VN_flushOperations" as NSString)
|
|
106
|
-
|
|
107
|
-
// Register __VN_teardown for graceful JS-side shutdown on hot reload
|
|
108
|
-
let teardown: @convention(block) () -> Void = { [weak self] in
|
|
109
|
-
// Graceful shutdown: bridge will be re-initialized on reload
|
|
110
|
-
NSLog("[VueNative] Teardown called from JS")
|
|
111
|
-
_ = self
|
|
112
|
-
}
|
|
113
|
-
context.setObject(teardown, forKeyedSubscript: "__VN_teardown" as NSString)
|
|
114
|
-
|
|
115
|
-
// Register __VN_log for debug logging from JS
|
|
116
|
-
let log: @convention(block) (JSValue) -> Void = { message in
|
|
117
|
-
NSLog("[VueNative JS] \(message.toString() ?? "")")
|
|
118
|
-
}
|
|
119
|
-
context.setObject(log, forKeyedSubscript: "__VN_log" as NSString)
|
|
120
|
-
|
|
121
|
-
// Register __VN_handleError for JS error reporting
|
|
122
|
-
// Called from JS with a JSON-encoded string containing error info
|
|
123
|
-
// (message, stack, componentName).
|
|
124
|
-
let handleError: @convention(block) (JSValue) -> Void = { errorInfoValue in
|
|
125
|
-
let jsonString = errorInfoValue.toString() ?? "{}"
|
|
126
|
-
NSLog("[VueNative Error] %@", jsonString)
|
|
127
|
-
|
|
128
|
-
// Try to extract structured fields for clearer logging
|
|
129
|
-
if let data = jsonString.data(using: .utf8),
|
|
130
|
-
let info = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
131
|
-
let message = info["message"] as? String ?? "Unknown error"
|
|
132
|
-
let stack = info["stack"] as? String ?? ""
|
|
133
|
-
let componentName = info["componentName"] as? String ?? "unknown"
|
|
134
|
-
NSLog("[VueNative Error] Component: %@, Message: %@", componentName, message)
|
|
135
|
-
if !stack.isEmpty {
|
|
136
|
-
NSLog("[VueNative Error] Stack: %@", stack)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
context.setObject(handleError, forKeyedSubscript: "__VN_handleError" as NSString)
|
|
139
|
+
self.registerBridgeFunctions(on: context)
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
// Register all native modules synchronously so they are available
|
|
@@ -153,19 +152,44 @@ public final class NativeBridge {
|
|
|
153
152
|
"setRootView", "setText", "setElementText"
|
|
154
153
|
]
|
|
155
154
|
|
|
155
|
+
/// Style properties that affect Yoga layout and require a layout pass when changed.
|
|
156
|
+
/// When a batch contains only updateStyle ops for non-layout properties (e.g.
|
|
157
|
+
/// backgroundColor, opacity), we skip the expensive layout recalculation.
|
|
158
|
+
private static let layoutAffectingStyles: Set<String> = [
|
|
159
|
+
// Dimensions
|
|
160
|
+
"width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight",
|
|
161
|
+
// Flex
|
|
162
|
+
"flex", "flexGrow", "flexShrink", "flexBasis", "flexDirection",
|
|
163
|
+
"flexWrap", "alignItems", "alignSelf", "alignContent", "justifyContent",
|
|
164
|
+
// Spacing
|
|
165
|
+
"padding", "paddingTop", "paddingRight", "paddingBottom", "paddingLeft",
|
|
166
|
+
"paddingHorizontal", "paddingVertical", "paddingStart", "paddingEnd",
|
|
167
|
+
"margin", "marginTop", "marginRight", "marginBottom", "marginLeft",
|
|
168
|
+
"marginHorizontal", "marginVertical", "marginStart", "marginEnd",
|
|
169
|
+
// Gap
|
|
170
|
+
"gap", "rowGap", "columnGap",
|
|
171
|
+
// Position
|
|
172
|
+
"position", "top", "right", "bottom", "left", "start", "end",
|
|
173
|
+
// Other layout
|
|
174
|
+
"aspectRatio", "display", "overflow", "direction",
|
|
175
|
+
]
|
|
176
|
+
|
|
156
177
|
/// Process a batch of operations on the main thread.
|
|
157
178
|
/// Each operation has an "op" key and "args" array.
|
|
158
|
-
///
|
|
159
|
-
/// (create, appendChild, insertBefore, removeChild, etc.)
|
|
160
|
-
///
|
|
179
|
+
/// Triggers a Yoga layout pass when the batch contains tree mutations
|
|
180
|
+
/// (create, appendChild, insertBefore, removeChild, etc.) OR style changes
|
|
181
|
+
/// that affect layout (width, height, padding, margin, flex, etc.).
|
|
182
|
+
/// Batches that only update visual styles/events skip the expensive layout.
|
|
161
183
|
///
|
|
162
184
|
/// Access: internal (not private) so that `@testable import` can exercise
|
|
163
185
|
/// operation handling without going through JSContext.
|
|
164
186
|
func processOperations(_ operations: [[String: Any]]) {
|
|
165
187
|
dispatchPrecondition(condition: .onQueue(.main))
|
|
188
|
+
#if DEBUG
|
|
166
189
|
NSLog("[VueNative Bridge] Processing %d operations", operations.count)
|
|
190
|
+
#endif
|
|
167
191
|
|
|
168
|
-
var
|
|
192
|
+
var needsLayout = false
|
|
169
193
|
|
|
170
194
|
for operation in operations {
|
|
171
195
|
guard let op = operation["op"] as? String,
|
|
@@ -174,8 +198,20 @@ public final class NativeBridge {
|
|
|
174
198
|
continue
|
|
175
199
|
}
|
|
176
200
|
|
|
177
|
-
if !
|
|
178
|
-
|
|
201
|
+
if !needsLayout && NativeBridge.treeMutationOps.contains(op) {
|
|
202
|
+
needsLayout = true
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check if updateStyle changes any layout-affecting property
|
|
206
|
+
if !needsLayout && op == "updateStyle",
|
|
207
|
+
args.count >= 2,
|
|
208
|
+
let styles = args[1] as? [String: Any] {
|
|
209
|
+
for key in styles.keys {
|
|
210
|
+
if NativeBridge.layoutAffectingStyles.contains(key) {
|
|
211
|
+
needsLayout = true
|
|
212
|
+
break
|
|
213
|
+
}
|
|
214
|
+
}
|
|
179
215
|
}
|
|
180
216
|
|
|
181
217
|
switch op {
|
|
@@ -212,8 +248,8 @@ public final class NativeBridge {
|
|
|
212
248
|
}
|
|
213
249
|
}
|
|
214
250
|
|
|
215
|
-
//
|
|
216
|
-
if
|
|
251
|
+
// Trigger layout when tree was mutated or layout-affecting styles changed
|
|
252
|
+
if needsLayout {
|
|
217
253
|
triggerLayout()
|
|
218
254
|
}
|
|
219
255
|
}
|
|
@@ -388,8 +424,12 @@ public final class NativeBridge {
|
|
|
388
424
|
if let factory = ComponentRegistry.factory(for: parentView) {
|
|
389
425
|
factory.insertChild(childView, into: container, before: beforeView)
|
|
390
426
|
} else if let index = container.subviews.firstIndex(of: beforeView) {
|
|
391
|
-
container.flex.addItem(childView)
|
|
392
427
|
container.insertSubview(childView, at: index)
|
|
428
|
+
// Rebuild Yoga children to match UIView subview order
|
|
429
|
+
container.flex.removeAllChildren()
|
|
430
|
+
for subview in container.subviews {
|
|
431
|
+
container.flex.addItem(subview)
|
|
432
|
+
}
|
|
393
433
|
} else {
|
|
394
434
|
container.flex.addItem(childView)
|
|
395
435
|
}
|
|
@@ -781,55 +821,9 @@ public final class NativeBridge {
|
|
|
781
821
|
return
|
|
782
822
|
}
|
|
783
823
|
|
|
784
|
-
// Re-register
|
|
824
|
+
// Re-register bridge functions on new context
|
|
785
825
|
guard let context = self.runtime.context else { return }
|
|
786
|
-
|
|
787
|
-
let flushOps: @convention(block) (JSValue) -> Void = { [weak self] opsValue in
|
|
788
|
-
guard let self = self else { return }
|
|
789
|
-
guard let jsonString = opsValue.toString(), !jsonString.isEmpty else {
|
|
790
|
-
NSLog("[VueNative Bridge] Warning: Empty operations batch")
|
|
791
|
-
return
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
guard let data = jsonString.data(using: .utf8),
|
|
795
|
-
let operations = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
|
|
796
|
-
NSLog("[VueNative Bridge] Error: Failed to parse operations JSON")
|
|
797
|
-
return
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
DispatchQueue.main.async { [weak self] in
|
|
801
|
-
self?.processOperations(operations)
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
context.setObject(flushOps, forKeyedSubscript: "__VN_flushOperations" as NSString)
|
|
805
|
-
|
|
806
|
-
let teardown: @convention(block) () -> Void = { [weak self] in
|
|
807
|
-
NSLog("[VueNative] Teardown called from JS")
|
|
808
|
-
_ = self
|
|
809
|
-
}
|
|
810
|
-
context.setObject(teardown, forKeyedSubscript: "__VN_teardown" as NSString)
|
|
811
|
-
|
|
812
|
-
let log: @convention(block) (JSValue) -> Void = { message in
|
|
813
|
-
NSLog("[VueNative JS] \(message.toString() ?? "")")
|
|
814
|
-
}
|
|
815
|
-
context.setObject(log, forKeyedSubscript: "__VN_log" as NSString)
|
|
816
|
-
|
|
817
|
-
let handleError: @convention(block) (JSValue) -> Void = { errorInfoValue in
|
|
818
|
-
let jsonString = errorInfoValue.toString() ?? "{}"
|
|
819
|
-
NSLog("[VueNative Error] %@", jsonString)
|
|
820
|
-
|
|
821
|
-
if let data = jsonString.data(using: .utf8),
|
|
822
|
-
let info = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
|
823
|
-
let message = info["message"] as? String ?? "Unknown error"
|
|
824
|
-
let stack = info["stack"] as? String ?? ""
|
|
825
|
-
let componentName = info["componentName"] as? String ?? "unknown"
|
|
826
|
-
NSLog("[VueNative Error] Component: %@, Message: %@", componentName, message)
|
|
827
|
-
if !stack.isEmpty {
|
|
828
|
-
NSLog("[VueNative Error] Stack: %@", stack)
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
context.setObject(handleError, forKeyedSubscript: "__VN_handleError" as NSString)
|
|
826
|
+
self.registerBridgeFunctions(on: context)
|
|
833
827
|
|
|
834
828
|
NSLog("[VueNative Bridge] reloadWithBundle: bridge re-registered on new context")
|
|
835
829
|
}
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VListFactory.swift
CHANGED
|
@@ -37,6 +37,7 @@ final class VListFactory: NativeComponentFactory {
|
|
|
37
37
|
switch event {
|
|
38
38
|
case "scroll":
|
|
39
39
|
container.onScroll = handler
|
|
40
|
+
container.scrollThrottle = EventThrottle(interval: 0.016, handler: handler)
|
|
40
41
|
case "endReached":
|
|
41
42
|
container.onEndReached = handler
|
|
42
43
|
default:
|
|
@@ -47,7 +48,9 @@ final class VListFactory: NativeComponentFactory {
|
|
|
47
48
|
func removeEventListener(view: UIView, event: String) {
|
|
48
49
|
guard let container = view as? VListContainerView else { return }
|
|
49
50
|
switch event {
|
|
50
|
-
case "scroll":
|
|
51
|
+
case "scroll":
|
|
52
|
+
container.onScroll = nil
|
|
53
|
+
container.scrollThrottle = nil
|
|
51
54
|
case "endReached": container.onEndReached = nil
|
|
52
55
|
default: break
|
|
53
56
|
}
|
|
@@ -106,6 +109,7 @@ final class VListContainerView: UIView {
|
|
|
106
109
|
var itemViews: [UIView] = []
|
|
107
110
|
var estimatedItemHeight: CGFloat = 44
|
|
108
111
|
var onScroll: ((Any?) -> Void)?
|
|
112
|
+
var scrollThrottle: EventThrottle?
|
|
109
113
|
var onEndReached: ((Any?) -> Void)?
|
|
110
114
|
fileprivate var firedEndReached = false
|
|
111
115
|
private lazy var internalDelegate = VListInternalDelegate(container: self)
|
|
@@ -195,7 +199,20 @@ private final class VListInternalDelegate: NSObject,
|
|
|
195
199
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
196
200
|
guard let container = container else { return }
|
|
197
201
|
let offset = scrollView.contentOffset
|
|
198
|
-
|
|
202
|
+
let payload: [String: Any] = [
|
|
203
|
+
"x": Double(offset.x),
|
|
204
|
+
"y": Double(offset.y),
|
|
205
|
+
"contentWidth": Double(scrollView.contentSize.width),
|
|
206
|
+
"contentHeight": Double(scrollView.contentSize.height),
|
|
207
|
+
"layoutWidth": Double(scrollView.frame.width),
|
|
208
|
+
"layoutHeight": Double(scrollView.frame.height),
|
|
209
|
+
]
|
|
210
|
+
// Use throttle if available, otherwise fire directly
|
|
211
|
+
if let throttle = container.scrollThrottle {
|
|
212
|
+
throttle.fire(payload)
|
|
213
|
+
} else {
|
|
214
|
+
container.onScroll?(payload)
|
|
215
|
+
}
|
|
199
216
|
|
|
200
217
|
// endReached detection (threshold = 20% from bottom)
|
|
201
218
|
let contentH = scrollView.contentSize.height
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VScrollViewFactory.swift
CHANGED
|
@@ -221,20 +221,25 @@ private final class RefreshTarget: NSObject {
|
|
|
221
221
|
// MARK: - ScrollDelegateProxy
|
|
222
222
|
|
|
223
223
|
/// UIScrollViewDelegate proxy that forwards scroll events to a JS handler.
|
|
224
|
+
/// Uses EventThrottle to limit bridge round-trips to ~60/s during fast scrolling.
|
|
224
225
|
private final class ScrollDelegateProxy: NSObject, UIScrollViewDelegate {
|
|
225
|
-
private let
|
|
226
|
+
private let throttle: EventThrottle
|
|
226
227
|
|
|
227
228
|
init(handler: @escaping (Any?) -> Void) {
|
|
228
|
-
self.
|
|
229
|
+
self.throttle = EventThrottle(interval: 0.016, handler: handler)
|
|
229
230
|
super.init()
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
|
233
234
|
let payload: [String: Any] = [
|
|
234
235
|
"x": scrollView.contentOffset.x,
|
|
235
|
-
"y": scrollView.contentOffset.y
|
|
236
|
+
"y": scrollView.contentOffset.y,
|
|
237
|
+
"contentWidth": scrollView.contentSize.width,
|
|
238
|
+
"contentHeight": scrollView.contentSize.height,
|
|
239
|
+
"layoutWidth": scrollView.frame.width,
|
|
240
|
+
"layoutHeight": scrollView.frame.height,
|
|
236
241
|
]
|
|
237
|
-
|
|
242
|
+
throttle.fire(payload)
|
|
238
243
|
}
|
|
239
244
|
}
|
|
240
245
|
#endif
|
package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VSliderFactory.swift
CHANGED
|
@@ -53,11 +53,16 @@ final class VSliderFactory: NativeComponentFactory {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/// Throttled target for UISlider .valueChanged events.
|
|
57
|
+
/// Limits bridge round-trips to ~60/s during continuous slider dragging.
|
|
56
58
|
private final class SliderTarget: NSObject {
|
|
57
|
-
let
|
|
58
|
-
init(handler: @escaping (Any?) -> Void) {
|
|
59
|
+
private let throttle: EventThrottle
|
|
60
|
+
init(handler: @escaping (Any?) -> Void) {
|
|
61
|
+
self.throttle = EventThrottle(interval: 0.016, handler: handler)
|
|
62
|
+
super.init()
|
|
63
|
+
}
|
|
59
64
|
@objc func handleValueChanged(_ slider: UISlider) {
|
|
60
|
-
|
|
65
|
+
throttle.fire(["value": Double(slider.value)])
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
#endif
|