onelaraveljs 1.0.0
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 +87 -0
- package/docs/integration_analysis.md +116 -0
- package/docs/onejs_analysis.md +108 -0
- package/docs/optimization_implementation_group2.md +458 -0
- package/docs/optimization_plan.md +130 -0
- package/index.js +16 -0
- package/package.json +13 -0
- package/src/app.js +61 -0
- package/src/core/API.js +72 -0
- package/src/core/ChildrenRegistry.js +410 -0
- package/src/core/DOMBatcher.js +207 -0
- package/src/core/ErrorBoundary.js +226 -0
- package/src/core/EventDelegator.js +416 -0
- package/src/core/Helper.js +817 -0
- package/src/core/LoopContext.js +97 -0
- package/src/core/OneDOM.js +246 -0
- package/src/core/OneMarkup.js +444 -0
- package/src/core/Router.js +996 -0
- package/src/core/SEOConfig.js +321 -0
- package/src/core/SectionEngine.js +75 -0
- package/src/core/TemplateEngine.js +83 -0
- package/src/core/View.js +273 -0
- package/src/core/ViewConfig.js +229 -0
- package/src/core/ViewController.js +1410 -0
- package/src/core/ViewControllerOptimized.js +164 -0
- package/src/core/ViewIdentifier.js +361 -0
- package/src/core/ViewLoader.js +272 -0
- package/src/core/ViewManager.js +1962 -0
- package/src/core/ViewState.js +761 -0
- package/src/core/ViewSystem.js +301 -0
- package/src/core/ViewTemplate.js +4 -0
- package/src/core/helpers/BindingHelper.js +239 -0
- package/src/core/helpers/ConfigHelper.js +37 -0
- package/src/core/helpers/EventHelper.js +172 -0
- package/src/core/helpers/LifecycleHelper.js +17 -0
- package/src/core/helpers/ReactiveHelper.js +169 -0
- package/src/core/helpers/RenderHelper.js +15 -0
- package/src/core/helpers/ResourceHelper.js +89 -0
- package/src/core/helpers/TemplateHelper.js +11 -0
- package/src/core/managers/BindingManager.js +671 -0
- package/src/core/managers/ConfigurationManager.js +136 -0
- package/src/core/managers/EventManager.js +309 -0
- package/src/core/managers/LifecycleManager.js +356 -0
- package/src/core/managers/ReactiveManager.js +334 -0
- package/src/core/managers/RenderEngine.js +292 -0
- package/src/core/managers/ResourceManager.js +441 -0
- package/src/core/managers/ViewHierarchyManager.js +258 -0
- package/src/core/managers/ViewTemplateManager.js +127 -0
- package/src/core/reactive/ReactiveComponent.js +592 -0
- package/src/core/services/EventService.js +418 -0
- package/src/core/services/HttpService.js +106 -0
- package/src/core/services/LoggerService.js +57 -0
- package/src/core/services/StateService.js +512 -0
- package/src/core/services/StorageService.js +856 -0
- package/src/core/services/StoreService.js +258 -0
- package/src/core/services/TemplateDetectorService.js +361 -0
- package/src/core/services/Test.js +18 -0
- package/src/helpers/devWarnings.js +205 -0
- package/src/helpers/performance.js +226 -0
- package/src/helpers/utils.js +287 -0
- package/src/init.js +343 -0
- package/src/plugins/auto-plugin.js +34 -0
- package/src/services/Test.js +18 -0
- package/src/types/index.js +193 -0
- package/src/utils/date-helper.js +51 -0
- package/src/utils/helpers.js +39 -0
- package/src/utils/validation.js +32 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Tài Liệu Chi Tiết Triển Khai Optimization Group 2: Stateful Managers (Revised)
|
|
2
|
+
|
|
3
|
+
Tài liệu này cung cấp hướng dẫn kỹ thuật chi tiết, đầy đủ các properties và phương thức cần chuyển đổi cho nhóm Stateful Managers. Mục tiêu là loại bỏ hoàn toàn việc khởi tạo 4 object con (`EventManager`, `ReactiveManager`, `BindingManager`, `ResourceManager`) và thay thế bằng cấu trúc dữ liệu tập trung (Optimization) kết hợp với Static Helper Classes.
|
|
4
|
+
|
|
5
|
+
## 1. Cấu Trúc Dữ Liệu Tập Trung (`_internal`)
|
|
6
|
+
|
|
7
|
+
Trong `ViewController.js`, thay vì khởi tạo các manager `new Manager()`, ta sẽ khởi tạo một object `_internal` chứa toàn bộ state.
|
|
8
|
+
|
|
9
|
+
### Định nghĩa `ViewController.prototype.initializeInternalState`
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
// ViewController.js
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Khởi tạo cấu trúc dữ liệu nội bộ tối ưu hóa bộ nhớ
|
|
16
|
+
*/
|
|
17
|
+
initializeInternalState() {
|
|
18
|
+
this._internal = {
|
|
19
|
+
// =========================================================================
|
|
20
|
+
// 1. EVENT STATE (Thay thế EventManager)
|
|
21
|
+
// =========================================================================
|
|
22
|
+
events: {
|
|
23
|
+
/**
|
|
24
|
+
* Map lưu các hàm unsubscribe cho delegated events
|
|
25
|
+
* Key: `${eventType}:${eventID}:${viewID}`
|
|
26
|
+
* Value: Function unsubscribe()
|
|
27
|
+
*/
|
|
28
|
+
unsubscribers: new Map()
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// =========================================================================
|
|
32
|
+
// 2. REACTIVE STATE (Thay thế ReactiveManager)
|
|
33
|
+
// =========================================================================
|
|
34
|
+
reactive: {
|
|
35
|
+
/** Map<string, ReactiveComponent> lưu instance các component đang active */
|
|
36
|
+
components: new Map(),
|
|
37
|
+
|
|
38
|
+
/** Array<Object> cấu hình component từ việc scan DOM (SSR) */
|
|
39
|
+
config: [],
|
|
40
|
+
|
|
41
|
+
/** Index hiện tại khi render/scan component (dùng cho tuần tự) */
|
|
42
|
+
index: 0,
|
|
43
|
+
scanIndex: 0,
|
|
44
|
+
|
|
45
|
+
/** Danh sách IDs của các component đã render */
|
|
46
|
+
ids: [],
|
|
47
|
+
prerenderIDs: [],
|
|
48
|
+
renderIDs: [],
|
|
49
|
+
|
|
50
|
+
/** Stack theo dõi dependency (Following IDs) */
|
|
51
|
+
followingIDs: [],
|
|
52
|
+
followingRenderIDs: [],
|
|
53
|
+
followingPrerenderIDs: [],
|
|
54
|
+
|
|
55
|
+
/** Reference tới component cha đang watch (nếu có context lồng nhau) */
|
|
56
|
+
parentWatch: null
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// =========================================================================
|
|
60
|
+
// 3. BINDING STATE (Thay thế BindingManager)
|
|
61
|
+
// =========================================================================
|
|
62
|
+
binding: {
|
|
63
|
+
// --- Input (Two-way) Binding ---
|
|
64
|
+
listeners: [], // Array lưu thông tin listener input để dọn dẹp
|
|
65
|
+
isStarted: false, // Flag đánh dấu đã start event listener chưa
|
|
66
|
+
|
|
67
|
+
/** WeakMap tracking cờ update để tránh loop (pushing/syncing) */
|
|
68
|
+
elementFlags: new WeakMap(),
|
|
69
|
+
|
|
70
|
+
/** WeakMap tracking listener đã gắn vào element nào */
|
|
71
|
+
elementMap: new WeakMap(),
|
|
72
|
+
|
|
73
|
+
// --- Attribute Binding ---
|
|
74
|
+
attrConfigs: [], // Configs cho dynamic attributes
|
|
75
|
+
attrListeners: [], // Active listeners cho attributes
|
|
76
|
+
attrIndex: 0, // Index tracking
|
|
77
|
+
|
|
78
|
+
// --- Class Binding ---
|
|
79
|
+
classConfigs: [], // Configs cho dynamic classes
|
|
80
|
+
classListeners: [], // Active listeners cho classes
|
|
81
|
+
isClassReady: false // Flag đánh dấu class binding đã sẵn sàng
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// =========================================================================
|
|
85
|
+
// 4. RESOURCE STATE (Thay thế ResourceManager)
|
|
86
|
+
// =========================================================================
|
|
87
|
+
resources: {
|
|
88
|
+
/** Set<string> lưu các key của resources (css/js) đã insert bởi view này */
|
|
89
|
+
insertedKeys: new Set()
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 2. Chi Tiết Chuyển Đổi Từng Module
|
|
96
|
+
|
|
97
|
+
### A. Event Helper (Thay thế `EventManager.js`)
|
|
98
|
+
|
|
99
|
+
#### Nhiệm vụ
|
|
100
|
+
|
|
101
|
+
Chuyển đổi toàn bộ logic xử lý sự kiện, parse handler, delegation sang static methods.
|
|
102
|
+
|
|
103
|
+
#### File: `core/helpers/EventHelper.js`
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
import EventDelegator from "../EventDelegator.js";
|
|
107
|
+
import logger from "../services/LoggerService.js";
|
|
108
|
+
|
|
109
|
+
export default class EventHelper {
|
|
110
|
+
/**
|
|
111
|
+
* Start event listeners using delegation
|
|
112
|
+
* @param {ViewController} controller
|
|
113
|
+
*/
|
|
114
|
+
static startEventListener(controller) {
|
|
115
|
+
const state = controller._internal.events; // Truy cập state mới
|
|
116
|
+
const needDeleted = [];
|
|
117
|
+
|
|
118
|
+
// Logic cũ nhưng thay this.delegatedUnsubscribers bằng state.unsubscribers
|
|
119
|
+
Object.entries(controller.events).forEach(([eventType, eventMap]) => {
|
|
120
|
+
Object.entries(eventMap).forEach(([eventID, handlers]) => {
|
|
121
|
+
const selector = `[data-${eventType}-id="${eventID}"]`;
|
|
122
|
+
const delegateKey = `${eventType}:${eventID}:${controller.id}`;
|
|
123
|
+
|
|
124
|
+
// Parse Handlers (Gọi hàm static nội bộ)
|
|
125
|
+
const parsedHandlers = EventHelper.parseEventHandlerFunctions(
|
|
126
|
+
controller,
|
|
127
|
+
handlers
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (parsedHandlers.length > 0) {
|
|
131
|
+
// Cleanup old if exists
|
|
132
|
+
if (state.unsubscribers.has(delegateKey)) {
|
|
133
|
+
state.unsubscribers.get(delegateKey)();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Attach via Delegator
|
|
137
|
+
const unsubscribe = EventDelegator.on(
|
|
138
|
+
eventType,
|
|
139
|
+
selector,
|
|
140
|
+
(event) => {
|
|
141
|
+
// ... logic thực thi handlers (giữ nguyên logic cũ) ...
|
|
142
|
+
// Lưu ý: bind this context cho handler là controller.view
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
state.unsubscribers.set(delegateKey, unsubscribe);
|
|
147
|
+
} else {
|
|
148
|
+
needDeleted.push({ eventType, eventID });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ... cleanup needDeleted logic ...
|
|
154
|
+
controller.eventListenerStatus = true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Stop all listeners
|
|
159
|
+
* @param {ViewController} controller
|
|
160
|
+
*/
|
|
161
|
+
static stopEventListener(controller) {
|
|
162
|
+
const state = controller._internal.events;
|
|
163
|
+
|
|
164
|
+
// Clear delegated listeners
|
|
165
|
+
state.unsubscribers.forEach((unsubscribe) => unsubscribe());
|
|
166
|
+
state.unsubscribers.clear();
|
|
167
|
+
|
|
168
|
+
// Clear legacy listeners if any
|
|
169
|
+
if (controller.eventListeners && controller.eventListeners.length > 0) {
|
|
170
|
+
controller.eventListeners.forEach(({ element, eventType, handler }) => {
|
|
171
|
+
element.removeEventListener(eventType, handler);
|
|
172
|
+
});
|
|
173
|
+
controller.eventListeners = [];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
controller.eventListenerStatus = false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parse array of handler definitions
|
|
181
|
+
*/
|
|
182
|
+
static parseEventHandlerFunctions(controller, handlers) {
|
|
183
|
+
// Logic cũ chuyển sang context static
|
|
184
|
+
// Thay this.view bằng controller.view
|
|
185
|
+
// Gọi EventHelper.parseHandlerFunction(controller, ...)
|
|
186
|
+
// ...
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### B. Reactive Helper (Thay thế `ReactiveManager.js`)
|
|
192
|
+
|
|
193
|
+
#### Nhiệm vụ
|
|
194
|
+
|
|
195
|
+
Đây là phần phức tạp nhất. Cần xử lý việc tạo `ReactiveComponent` và quản lý lifecycle của chúng thông qua `_internal.reactive`.
|
|
196
|
+
|
|
197
|
+
#### File: `core/helpers/ReactiveHelper.js`
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
import { ReactiveComponent } from "../reactive/ReactiveComponent.js";
|
|
201
|
+
|
|
202
|
+
export default class ReactiveHelper {
|
|
203
|
+
/**
|
|
204
|
+
* Render Output Component (Thay thế renderOutputComponent)
|
|
205
|
+
*/
|
|
206
|
+
static renderOutputComponent(
|
|
207
|
+
controller,
|
|
208
|
+
stateKeys = [],
|
|
209
|
+
renderBlock = () => ""
|
|
210
|
+
) {
|
|
211
|
+
const state = controller._internal.reactive;
|
|
212
|
+
|
|
213
|
+
// Logic Virtual Rendering
|
|
214
|
+
if (controller.isVirtualRendering) {
|
|
215
|
+
controller.isVirtualRendering = false;
|
|
216
|
+
let result = ReactiveHelper.renderOutputComponentScan(
|
|
217
|
+
controller,
|
|
218
|
+
stateKeys,
|
|
219
|
+
renderBlock
|
|
220
|
+
); // Gọi hàm static scan
|
|
221
|
+
controller.isVirtualRendering = true;
|
|
222
|
+
if (result !== false) return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Tạo instance ReactiveComponent
|
|
226
|
+
// Lưu ý: ReactiveComponent vẫn cần refer ngược lại controller để access App/State
|
|
227
|
+
const rc = new ReactiveComponent({
|
|
228
|
+
stateKeys,
|
|
229
|
+
renderBlock,
|
|
230
|
+
controller: controller, // Truyền controller vào
|
|
231
|
+
App: controller.App,
|
|
232
|
+
type: "output",
|
|
233
|
+
escapeHTML: false,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Store active component vào state mới
|
|
237
|
+
state.ids.push(rc.id);
|
|
238
|
+
state.components.set(rc.id, rc);
|
|
239
|
+
|
|
240
|
+
return rc.render();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static renderOutputComponentScan(
|
|
244
|
+
controller,
|
|
245
|
+
stateKeys,
|
|
246
|
+
renderBlock,
|
|
247
|
+
escapeHTML = false
|
|
248
|
+
) {
|
|
249
|
+
const state = controller._internal.reactive;
|
|
250
|
+
|
|
251
|
+
// Truy cập state.scanIndex thay vì this.reactiveComponentScanIndex
|
|
252
|
+
let reactiveComponentIndex = state.scanIndex;
|
|
253
|
+
let reactiveComponentConfig = state.config[reactiveComponentIndex];
|
|
254
|
+
|
|
255
|
+
// ... logic validate config ...
|
|
256
|
+
|
|
257
|
+
const { id: renderID } = reactiveComponentConfig;
|
|
258
|
+
const rc = new ReactiveComponent({
|
|
259
|
+
renderID,
|
|
260
|
+
stateKeys,
|
|
261
|
+
renderBlock,
|
|
262
|
+
controller: controller,
|
|
263
|
+
App: controller.App,
|
|
264
|
+
type: "output",
|
|
265
|
+
escapeHTML,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
state.ids.push(rc.id);
|
|
269
|
+
state.components.set(rc.id, rc);
|
|
270
|
+
state.scanIndex++; // Tăng index
|
|
271
|
+
|
|
272
|
+
return rc.render();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
static clearForRefresh(controller) {
|
|
276
|
+
const state = controller._internal.reactive;
|
|
277
|
+
if (state.components.size > 0) {
|
|
278
|
+
state.components.forEach((c) => c.destroy());
|
|
279
|
+
state.components.clear();
|
|
280
|
+
}
|
|
281
|
+
state.ids = [];
|
|
282
|
+
state.scanIndex = 0;
|
|
283
|
+
state.followingIDs = [];
|
|
284
|
+
// ... reset other arrays ...
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Các hàm renderWatchComponent, renderBindingAttribute chuyển tương tự...
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### C. Binding Helper (Thay thế `BindingManager.js`)
|
|
292
|
+
|
|
293
|
+
#### Nhiệm vụ
|
|
294
|
+
|
|
295
|
+
Quản lý Two-way binding và Attribute/Class binding. Chú ý sử dụng `WeakMap` từ `_internal.binding`.
|
|
296
|
+
|
|
297
|
+
#### File: `core/helpers/BindingHelper.js`
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
import OneDOM from "../OneDOM.js";
|
|
301
|
+
import DOMBatcher from "../DOMBatcher.js";
|
|
302
|
+
|
|
303
|
+
export default class BindingHelper {
|
|
304
|
+
static startBindingEventListener(controller) {
|
|
305
|
+
const state = controller._internal.binding;
|
|
306
|
+
const selector = `[data-binding][data-view-id="${controller.id}"]`;
|
|
307
|
+
const inputs = document.querySelectorAll(selector);
|
|
308
|
+
|
|
309
|
+
inputs.forEach((input) => {
|
|
310
|
+
if (!input.isConnected) return;
|
|
311
|
+
|
|
312
|
+
// Check WeakMap from state
|
|
313
|
+
if (state.elementMap.has(input)) return;
|
|
314
|
+
|
|
315
|
+
const stateKey = input.getAttribute("data-binding");
|
|
316
|
+
// ... logic validate ...
|
|
317
|
+
|
|
318
|
+
// Sync initial state
|
|
319
|
+
BindingHelper.syncStateToElement(controller, input, stateKey);
|
|
320
|
+
|
|
321
|
+
const handler = (event) =>
|
|
322
|
+
BindingHelper.pushElementToState(controller, input, stateKey);
|
|
323
|
+
|
|
324
|
+
// Attach event
|
|
325
|
+
const eventType =
|
|
326
|
+
input.tagName.toLowerCase() === "select" ? "change" : "input";
|
|
327
|
+
input.addEventListener(eventType, handler);
|
|
328
|
+
|
|
329
|
+
// Store in WeakMap
|
|
330
|
+
state.elementMap.set(input, { eventType, handler, stateKey });
|
|
331
|
+
|
|
332
|
+
// Store in array for destroy cleanup
|
|
333
|
+
state.listeners.push({ element: input, eventType, handler /* ... */ });
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
state.isStarted = true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
static pushElementToState(controller, element, stateKey) {
|
|
340
|
+
const state = controller._internal.binding;
|
|
341
|
+
|
|
342
|
+
// Get flags from WeakMap
|
|
343
|
+
let flags = state.elementFlags.get(element);
|
|
344
|
+
if (!flags) {
|
|
345
|
+
flags = { pushing: false, syncing: false };
|
|
346
|
+
state.elementFlags.set(element, flags);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (flags.syncing || flags.pushing) return;
|
|
350
|
+
flags.pushing = true;
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const value = OneDOM.getInputValue(element);
|
|
354
|
+
// Call updateStateAddressKey on controller
|
|
355
|
+
controller.states.__.updateStateAddressKey(stateKey, value);
|
|
356
|
+
} finally {
|
|
357
|
+
Promise.resolve().then(() => {
|
|
358
|
+
const currentFlags = state.elementFlags.get(element);
|
|
359
|
+
if (currentFlags) currentFlags.pushing = false;
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Các hàm syncStateToElement, notifyStateChanges... thực hiện tương tự.
|
|
365
|
+
// Lưu ý thay this.* bằng thao tác trên state.*
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### D. Resource Helper (Thay thế `ResourceManager.js`)
|
|
370
|
+
|
|
371
|
+
#### Nhiệm vụ
|
|
372
|
+
|
|
373
|
+
Đơn giản hóa việc quản lý resource, trạng thái lưu tại `_internal.resources.insertedKeys`.
|
|
374
|
+
|
|
375
|
+
#### File: `core/helpers/ResourceHelper.js`
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
export default class ResourceHelper {
|
|
379
|
+
static insertResources(controller) {
|
|
380
|
+
ResourceHelper.insertStyles(controller);
|
|
381
|
+
ResourceHelper.insertScripts(controller);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
static insertStyles(controller) {
|
|
385
|
+
if (!controller.styles || controller.styles.length === 0) return;
|
|
386
|
+
|
|
387
|
+
const state = controller._internal.resources;
|
|
388
|
+
|
|
389
|
+
controller.styles.forEach((style) => {
|
|
390
|
+
// ... logic gen key ...
|
|
391
|
+
// controller.insertedResourceKeys.add(...) -> state.insertedKeys.add(...)
|
|
392
|
+
// Logic check Registry toàn cục (App.View.Engine.resourceRegistry) vẫn giữ nguyên
|
|
393
|
+
// vì Helper không lưu registry này, nó là static của View Engine.
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ... removeStyles, insertScripts ...
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## 3. Tích Hợp Vào `ViewController`
|
|
402
|
+
|
|
403
|
+
Sau khi tạo các Helpers, ta sửa `ViewController.js` để gọi chúng.
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
/* ViewController.js */
|
|
407
|
+
|
|
408
|
+
import EventHelper from './helpers/EventHelper.js';
|
|
409
|
+
import ReactiveHelper from './helpers/ReactiveHelper.js';
|
|
410
|
+
import BindingHelper from './helpers/BindingHelper.js';
|
|
411
|
+
import ResourceHelper from './helpers/ResourceHelper.js';
|
|
412
|
+
|
|
413
|
+
export class ViewController {
|
|
414
|
+
constructor(...) {
|
|
415
|
+
// ...
|
|
416
|
+
// Thay vì new Manager(), khởi tạo state rỗng
|
|
417
|
+
this.initializeInternalState();
|
|
418
|
+
|
|
419
|
+
// KHÔNG còn this._eventManager = ...
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Wrapper method cho View gọi (Backward Compatibility)
|
|
423
|
+
__reactive(id, keys, renderFn) {
|
|
424
|
+
// Forward sang Helper
|
|
425
|
+
return ReactiveHelper.renderOutputComponent(this, keys, renderFn);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Lifecycle hooks
|
|
429
|
+
mounted() {
|
|
430
|
+
// Thay thế this._eventManager.startEventListener()
|
|
431
|
+
EventHelper.startEventListener(this);
|
|
432
|
+
BindingHelper.startBindingEventListener(this);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
destroy() {
|
|
436
|
+
EventHelper.stopEventListener(this);
|
|
437
|
+
BindingHelper.destroy(this);
|
|
438
|
+
ReactiveHelper.destroy(this);
|
|
439
|
+
ResourceHelper.removeResources(this);
|
|
440
|
+
|
|
441
|
+
// Clear internal state
|
|
442
|
+
this._internal = null;
|
|
443
|
+
super.destroy(); // nếu có
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## 4. Các Vấn Đề Cần Lưu Ý (Edge Cases)
|
|
449
|
+
|
|
450
|
+
1. **Scope của `this`**: Trong các Helper, `this` không còn là instance controller. Luôn phải dùng biến `controller` được truyền vào.
|
|
451
|
+
2. **Circular References trong ReactiveComponent**:
|
|
452
|
+
- `ReactiveComponent` hiện đang giữ tham chiếu `this.controller`. Điều này vẫn ổn, nhưng khi destroy `controller`, ta phải chắc chắn gọi `ReactiveHelper.destroy(this)` để loop qua tất cả components và gọi `component.destroy()`, setup `component.controller = null`.
|
|
453
|
+
3. **WeakMap GC**: Trong `BindingHelper`, vì `_internal` là property của `controller`, khi `controller` bị GC thì `_internal` cũng bị GC theo -> `WeakMap` cũng đi theo. Đây là hành vi đúng và an toàn bộ nhớ.
|
|
454
|
+
4. **Legacy Proxy**: Nếu có code cũ truy cập trực tiếp `this._eventManager`, code đó sẽ gãy. Cần search toàn project để đảm bảo không có chỗ nào chọc trực tiếp vào manager property. Nếu cần thiết, có thể tạo getter `get _eventManager()` trả về object giả lập (proxy) để warn deprecation.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
**Kết luận**: Việc chuyển đổi này tuy tốn công sức ban đầu (refactor copy-paste logic) nhưng cấu trúc dữ liệu `_internal` rất rõ ràng, minh bạch, và loại bỏ hoàn toàn overhead của các class wrapper.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Kế Hoạch Tối Ưu Hóa OneJS: Chuyển Đổi Manager Sang Helper Pattern
|
|
2
|
+
|
|
3
|
+
Tài liệu này phân tích, đánh giá và lập kế hoạch chuyển đổi kiến trúc `ViewController` từ việc sử dụng các Object Manager (OOP) sang dạng Helper Functions (Functional/Stateless) để tối ưu hóa bộ nhớ và hiệu suất.
|
|
4
|
+
|
|
5
|
+
## 1. Phân Tích Hiện Trạng
|
|
6
|
+
|
|
7
|
+
### Kiến Trúc Hiện Tại
|
|
8
|
+
|
|
9
|
+
Trong `ViewController.js`, mỗi khi một View được khởi tạo, nó sẽ tạo ra hàng hoạt instance của các class quản lý (Manager):
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
this._resourceManager = new ResourceManager(this);
|
|
13
|
+
this._eventManager = new EventManager(this);
|
|
14
|
+
this._renderEngine = new RenderEngine(this);
|
|
15
|
+
this._lifecycleManager = new LifecycleManager(this);
|
|
16
|
+
this._reactiveManager = new ReactiveManager(this);
|
|
17
|
+
this._bindingManager = new BindingManager(this);
|
|
18
|
+
this._templateManager = new ViewTemplateManager(this);
|
|
19
|
+
this._configManager = new ConfigurationManager(this);
|
|
20
|
+
this._hierarchyManager = new ViewHierarchyManager(this);
|
|
21
|
+
this._childrenRegistry = new ChildrenRegistry(this);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Vấn Đề
|
|
25
|
+
|
|
26
|
+
- **Overhead Bộ Nhớ**: Với mỗi View Instance (ví dụ: một item trong danh sách 1000 phần tử), ta đang tạo thêm 10 object con. Tổng cộng 1000 views = 11,000 objects. Điều này gây áp lực lớn lên Garbage Collector (GC) của trình duyệt.
|
|
27
|
+
- **Initialization Time**: Việc khởi tạo `new Class()` và gán context (`this`) tốn thời gian CPU, dù nhỏ nhưng sẽ cộng dồn khi render danh sách lớn.
|
|
28
|
+
- **Deep Nesting**: Việc truy cập `this._renderEngine.controller` tạo các tham chiếu vòng (circular references), có thể gây leak memory nếu không dọn dẹp kỹ.
|
|
29
|
+
|
|
30
|
+
## 2. Giải Pháp Đề Xuất: Helper Pattern
|
|
31
|
+
|
|
32
|
+
Thay vì khởi tạo object, ta tách logic ra thành các **Static Helper Modules**. `ViewController` sẽ chỉ lưu data (state), còn logic sẽ được gọi thông qua hàm static.
|
|
33
|
+
|
|
34
|
+
**Ví dụ chuyển đổi:**
|
|
35
|
+
|
|
36
|
+
_Trước (Object-based):_
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
// ViewController.js
|
|
40
|
+
constructor() {
|
|
41
|
+
this._renderEngine = new RenderEngine(this);
|
|
42
|
+
}
|
|
43
|
+
render() {
|
|
44
|
+
return this._renderEngine.render();
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
_Sau (Helper-based):_
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
// ViewController.js
|
|
52
|
+
import RenderHelper from './helpers/RenderHelper.js';
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
return RenderHelper.render(this);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Đánh Giá Tác Động
|
|
60
|
+
|
|
61
|
+
#### A. Ưu Điểm (Performance & Memory)
|
|
62
|
+
|
|
63
|
+
1. **Giảm Object Count**: Loại bỏ hoàn toàn 10 object wrapper cho mỗi view. `ViewController` chỉ là một object chứa dữ liệu.
|
|
64
|
+
2. **Tăng Tốc Khởi Tạo**: Loại bỏ overhead của `new`, chỉ còn khai báo properties.
|
|
65
|
+
3. **Memory Layout**: Dữ liệu tập trung tại `ViewController`, giúp JS Engine tối ưu hóa hidden classes tốt hơn so với việc phân mảnh dữ liệu ra các object con.
|
|
66
|
+
|
|
67
|
+
#### B. Thách Thức (Refactoring)
|
|
68
|
+
|
|
69
|
+
1. **Quản Lý State**: Các Manager hiện tại (như `EventManager`, `ReactiveManager`) đang lưu state riêng (ví dụ `delegatedUnsubscribers`, `reactiveComponents`).
|
|
70
|
+
- _Giải pháp_: Chuyển toàn bộ state này về `ViewController` (hoặc một property `_internalState` trong Controller) và truyền nó vào Helper khi gọi hàm.
|
|
71
|
+
2. **Encapsulation**: `ViewController` sẽ lộ nhiều properties hơn (public hoặc internal) để Helpers có thể truy cập.
|
|
72
|
+
|
|
73
|
+
## 3. Phân Loại & Chiến Lược Chuyển Đổi
|
|
74
|
+
|
|
75
|
+
Chúng ta sẽ chia các Manager thành 2 nhóm để xử lý:
|
|
76
|
+
|
|
77
|
+
### Nhóm 1: Stateless / Logic-Heavy Managers (Dễ chuyển đổi)
|
|
78
|
+
|
|
79
|
+
Các manager này chủ yếu chứa logic xử lý, ít lưu state riêng.
|
|
80
|
+
|
|
81
|
+
- **`RenderEngine`**: Chuyển thành `RenderHelper`.
|
|
82
|
+
- **`ConfigurationManager`**: Chuyển thành `ConfigHelper`.
|
|
83
|
+
- **`ViewTemplateManager`**: Chuyển thành `TemplateHelper`.
|
|
84
|
+
- **`LifecycleManager`**: Chuyển thành `LifecycleHelper`.
|
|
85
|
+
|
|
86
|
+
### Nhóm 2: Stateful Managers (Cần quy hoạch State)
|
|
87
|
+
|
|
88
|
+
Các manager này lưu trữ dữ liệu quan trọng. Cần di chuyển dữ liệu vào `ViewController`.
|
|
89
|
+
|
|
90
|
+
- **`EventManager`**:
|
|
91
|
+
- State cần chuyển: `delegatedUnsubscribers`.
|
|
92
|
+
- Vị trí mới: `controller._eventState.unsubscribers`.
|
|
93
|
+
- **`ReactiveManager`**:
|
|
94
|
+
- State cần chuyển: `reactiveComponents`, `reactiveComponentIDs`, `followingIDs`...
|
|
95
|
+
- Vị trí mới: `controller._reactiveState = { components: Map(), ids: [] ... }`.
|
|
96
|
+
- **`BindingManager`**: Tương tự ReactiveManager.
|
|
97
|
+
- **`ResourceManager`**:
|
|
98
|
+
- State cần chuyển: `insertedResourceKeys`.
|
|
99
|
+
|
|
100
|
+
## 4. Kế Hoạch Triển Khai
|
|
101
|
+
|
|
102
|
+
### Giai Đoạn 1: Refactor Structure (Chuẩn bị)
|
|
103
|
+
|
|
104
|
+
1. Tạo thư mục `core/helpers/`.
|
|
105
|
+
2. Xác định cấu trúc dữ liệu `initialState` chuẩn trong `ViewController`.
|
|
106
|
+
|
|
107
|
+
### Giai Đoạn 2: Chuyển Đổi Nhóm Stateless
|
|
108
|
+
|
|
109
|
+
1. Convert `RenderEngine.js` -> `helpers/RenderHelper.js`.
|
|
110
|
+
- Sửa các hàm `this.controller` thành tham số `controller`.
|
|
111
|
+
2. Convert `LifecycleManager.js` -> `helpers/LifecycleHelper.js`.
|
|
112
|
+
3. Cập nhật `ViewController` để gọi Helper thay vì `this.manager.method()`.
|
|
113
|
+
|
|
114
|
+
### Giai Đoạn 3: Chuyển Đổi Nhóm Stateful (Phức tạp)
|
|
115
|
+
|
|
116
|
+
1. **Refactor Reactivity**: Gom nhóm tất cả state của `ReactiveManager` vào cấu trúc `_reactiveState` trong `ViewController`.
|
|
117
|
+
2. Convert `ReactiveManager.js` -> `helpers/ReactiveHelper.js`. Các hàm sẽ nhận `controller` và thao tác trên `controller._reactiveState`.
|
|
118
|
+
3. Làm tương tự với `EventHelper` và `BindingHelper`.
|
|
119
|
+
|
|
120
|
+
### Giai Đoạn 4: Cleanup & Testing
|
|
121
|
+
|
|
122
|
+
1. Xóa các file Manager cũ.
|
|
123
|
+
2. Kiểm tra Memory Leak (đảm bảo `_reactiveState` được clear khi destroy view).
|
|
124
|
+
3. Benchmark so sánh performance trước và sau.
|
|
125
|
+
|
|
126
|
+
## 5. Kết Luận
|
|
127
|
+
|
|
128
|
+
Việc chuyển đổi sang Helper Pattern là **hoàn toàn khả thi và nên làm** đối với OneJS. Nó sẽ mang lại hiệu năng cao hơn đáng kể cho các ứng dụng có danh sách lớn, giảm Memory Footprint và đơn giản hóa việc quản lý vòng đời object (do ít object con hơn).
|
|
129
|
+
|
|
130
|
+
Tuy nhiên, code của `ViewController` sẽ cần "mở" cấu trúc dữ liệu của nó cho các Helpers, đòi hỏi quy ước đặt tên (naming conventions) chặt chẽ để tránh xung đột dữ liệu.
|
package/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// OneJS Framework Core Export
|
|
2
|
+
import { App } from './src/app.js';
|
|
3
|
+
import { viewLoader } from './src/core/ViewLoader.js';
|
|
4
|
+
import { EventService } from './src/core/services/EventService.js';
|
|
5
|
+
import initApp from './src/init.js';
|
|
6
|
+
|
|
7
|
+
// Export Core Components
|
|
8
|
+
export {
|
|
9
|
+
App,
|
|
10
|
+
viewLoader,
|
|
11
|
+
EventService,
|
|
12
|
+
initApp
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Default export
|
|
16
|
+
export default App;
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "onelaraveljs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OneLaravel JS Framework Core",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["framework", "laravel", "spa"],
|
|
11
|
+
"author": "OneLaravel",
|
|
12
|
+
"license": "MIT"
|
|
13
|
+
}
|
package/src/app.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Core Module
|
|
3
|
+
* ES6 Module for Blade Compiler
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { HttpService } from "./core/services/HttpService.js";
|
|
7
|
+
import { Router } from "./core/Router.js";
|
|
8
|
+
import { ViewManager } from "./core/ViewManager.js";
|
|
9
|
+
import { API } from "./core/API.js";
|
|
10
|
+
import { Helper } from "./core/Helper.js";
|
|
11
|
+
import initApp from "./init.js";
|
|
12
|
+
import OneMarkup from "./core/OneMarkup.js";
|
|
13
|
+
import { StoreService } from "./core/services/StoreService.js";
|
|
14
|
+
import { EventService } from "./core/services/EventService.js";
|
|
15
|
+
|
|
16
|
+
export class Application {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.name = 'OneApp';
|
|
19
|
+
/**
|
|
20
|
+
* @type {StoreService}
|
|
21
|
+
*/
|
|
22
|
+
this.StoreService = StoreService;
|
|
23
|
+
/**
|
|
24
|
+
* @type {StoreService}
|
|
25
|
+
*/
|
|
26
|
+
this.Store = StoreService.getInstance();
|
|
27
|
+
this.EventService = EventService;
|
|
28
|
+
/**
|
|
29
|
+
* @type {EventService}
|
|
30
|
+
*/
|
|
31
|
+
this.Event = EventService.getInstance();
|
|
32
|
+
this.Helper = new Helper(this);
|
|
33
|
+
this.Router = new Router(this);
|
|
34
|
+
this.View = new ViewManager(this);
|
|
35
|
+
this.HttpService = HttpService;
|
|
36
|
+
this.Http = new HttpService();
|
|
37
|
+
this.OneMarkup = OneMarkup;
|
|
38
|
+
this.Api = API;
|
|
39
|
+
this.mode = 'development';
|
|
40
|
+
this.isInitialized = false;
|
|
41
|
+
this.env = {
|
|
42
|
+
mode: 'web',
|
|
43
|
+
debug: false,
|
|
44
|
+
base_url: '/',
|
|
45
|
+
csrf_token: '',
|
|
46
|
+
router_mode: 'history',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
init() {
|
|
51
|
+
if (this.isInitialized) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
initApp(this);
|
|
55
|
+
this.isInitialized = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const App = new Application();
|
|
60
|
+
|
|
61
|
+
export default App;
|