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.
Files changed (67) hide show
  1. package/README.md +87 -0
  2. package/docs/integration_analysis.md +116 -0
  3. package/docs/onejs_analysis.md +108 -0
  4. package/docs/optimization_implementation_group2.md +458 -0
  5. package/docs/optimization_plan.md +130 -0
  6. package/index.js +16 -0
  7. package/package.json +13 -0
  8. package/src/app.js +61 -0
  9. package/src/core/API.js +72 -0
  10. package/src/core/ChildrenRegistry.js +410 -0
  11. package/src/core/DOMBatcher.js +207 -0
  12. package/src/core/ErrorBoundary.js +226 -0
  13. package/src/core/EventDelegator.js +416 -0
  14. package/src/core/Helper.js +817 -0
  15. package/src/core/LoopContext.js +97 -0
  16. package/src/core/OneDOM.js +246 -0
  17. package/src/core/OneMarkup.js +444 -0
  18. package/src/core/Router.js +996 -0
  19. package/src/core/SEOConfig.js +321 -0
  20. package/src/core/SectionEngine.js +75 -0
  21. package/src/core/TemplateEngine.js +83 -0
  22. package/src/core/View.js +273 -0
  23. package/src/core/ViewConfig.js +229 -0
  24. package/src/core/ViewController.js +1410 -0
  25. package/src/core/ViewControllerOptimized.js +164 -0
  26. package/src/core/ViewIdentifier.js +361 -0
  27. package/src/core/ViewLoader.js +272 -0
  28. package/src/core/ViewManager.js +1962 -0
  29. package/src/core/ViewState.js +761 -0
  30. package/src/core/ViewSystem.js +301 -0
  31. package/src/core/ViewTemplate.js +4 -0
  32. package/src/core/helpers/BindingHelper.js +239 -0
  33. package/src/core/helpers/ConfigHelper.js +37 -0
  34. package/src/core/helpers/EventHelper.js +172 -0
  35. package/src/core/helpers/LifecycleHelper.js +17 -0
  36. package/src/core/helpers/ReactiveHelper.js +169 -0
  37. package/src/core/helpers/RenderHelper.js +15 -0
  38. package/src/core/helpers/ResourceHelper.js +89 -0
  39. package/src/core/helpers/TemplateHelper.js +11 -0
  40. package/src/core/managers/BindingManager.js +671 -0
  41. package/src/core/managers/ConfigurationManager.js +136 -0
  42. package/src/core/managers/EventManager.js +309 -0
  43. package/src/core/managers/LifecycleManager.js +356 -0
  44. package/src/core/managers/ReactiveManager.js +334 -0
  45. package/src/core/managers/RenderEngine.js +292 -0
  46. package/src/core/managers/ResourceManager.js +441 -0
  47. package/src/core/managers/ViewHierarchyManager.js +258 -0
  48. package/src/core/managers/ViewTemplateManager.js +127 -0
  49. package/src/core/reactive/ReactiveComponent.js +592 -0
  50. package/src/core/services/EventService.js +418 -0
  51. package/src/core/services/HttpService.js +106 -0
  52. package/src/core/services/LoggerService.js +57 -0
  53. package/src/core/services/StateService.js +512 -0
  54. package/src/core/services/StorageService.js +856 -0
  55. package/src/core/services/StoreService.js +258 -0
  56. package/src/core/services/TemplateDetectorService.js +361 -0
  57. package/src/core/services/Test.js +18 -0
  58. package/src/helpers/devWarnings.js +205 -0
  59. package/src/helpers/performance.js +226 -0
  60. package/src/helpers/utils.js +287 -0
  61. package/src/init.js +343 -0
  62. package/src/plugins/auto-plugin.js +34 -0
  63. package/src/services/Test.js +18 -0
  64. package/src/types/index.js +193 -0
  65. package/src/utils/date-helper.js +51 -0
  66. package/src/utils/helpers.js +39 -0
  67. package/src/utils/validation.js +32 -0
@@ -0,0 +1,418 @@
1
+ export class EventService {
2
+ static instances = new Map();
3
+ static getInstance(name = null) {
4
+ if (!name || name === '' || ame === undefined) {
5
+ name = 'default';
6
+ }
7
+ if (!EventService.instances.has(name)) {
8
+ EventService.instances.set(name, new EventService());
9
+ }
10
+ return EventService.instances.get(name);
11
+ }
12
+
13
+ static removeInstance(name) {
14
+ if (EventService.instances.has(name)) {
15
+ EventService.instances.delete(name);
16
+ }
17
+ }
18
+
19
+ static instance(name = null) {
20
+ return EventService.getInstance(name);
21
+ }
22
+
23
+ static clearInstances() {
24
+ EventService.instances.clear();
25
+ }
26
+
27
+ constructor() {
28
+ /**
29
+ * @type {Map<string, Array<{callback: Function, once: boolean}>>}
30
+ * @private
31
+ */
32
+ this.listeners = new Map();
33
+
34
+ /**
35
+ * Multi-event listeners: Khi subscribe nhiều events với 1 callback
36
+ * @type {Array<{events: Set<string>, callback: Function, once: boolean, called: boolean}>}
37
+ * @private
38
+ */
39
+ this.multiEventListeners = [];
40
+
41
+ /**
42
+ * Track xem có pending flush không (để batch calls)
43
+ * @private
44
+ */
45
+ this.hasPendingFlush = false;
46
+
47
+ /**
48
+ * Set callbacks đã được gọi trong batch hiện tại
49
+ * @private
50
+ */
51
+ this.calledInBatch = new Set();
52
+ }
53
+
54
+ /**
55
+ * Đăng ký lắng nghe một event
56
+ *
57
+ * @param {string|array|object} eventName - Tên event hoặc array các tên event hoặc object {event: callback}
58
+ * @param {function} callback - Hàm callback khi event được trigger
59
+ * @param {boolean} once - Chỉ chạy 1 lần rồi tự động unsubscribe
60
+ * @return {function} Hàm hủy đăng ký
61
+ */
62
+ on(eventName, callback, once = false) {
63
+ // Hỗ trợ object syntax: on({event1: fn1, event2: fn2})
64
+ if(typeof eventName === 'object' && !Array.isArray(eventName)){
65
+ const unsubscribers = [];
66
+ for(const [event, fn] of Object.entries(eventName)){
67
+ if(typeof fn === 'function'){
68
+ unsubscribers.push(this.on(event, fn, callback === true));
69
+ }
70
+ }
71
+ // Return combined unsubscribe function
72
+ return () => {
73
+ unsubscribers.forEach(unsub => unsub());
74
+ };
75
+ }
76
+
77
+ // Hỗ trợ string với spaces: "event1 event2 event3"
78
+ if(typeof eventName === 'string' && eventName.includes(' ')){
79
+ eventName = eventName.split(/\s+/).filter(e => e);
80
+ }
81
+
82
+ // Hỗ trợ subscribe nhiều events cùng lúc với 1 callback
83
+ // Callback chỉ được gọi 1 lần trong cùng batch dù nhiều events được emit
84
+ if(Array.isArray(eventName)){
85
+ const events = new Set(eventName.filter(e => typeof e === 'string'));
86
+ if(events.size === 0){
87
+ return () => {};
88
+ }
89
+
90
+ const listener = { events, callback, once, called: false };
91
+ this.multiEventListeners.push(listener);
92
+
93
+ // Return unsubscribe function
94
+ return () => {
95
+ const index = this.multiEventListeners.indexOf(listener);
96
+ if(index !== -1){
97
+ this.multiEventListeners.splice(index, 1);
98
+ }
99
+ };
100
+ }
101
+
102
+ if(typeof eventName !== 'string' || typeof callback !== 'function'){
103
+ return () => {};
104
+ }
105
+
106
+ if(!this.listeners.has(eventName)){
107
+ this.listeners.set(eventName, []);
108
+ }
109
+
110
+ const listener = { callback, once };
111
+ this.listeners.get(eventName).push(listener);
112
+
113
+ // Return unsubscribe function
114
+ return () => this.off(eventName, callback);
115
+ }
116
+
117
+ /**
118
+ * Đăng ký lắng nghe event chỉ chạy 1 lần
119
+ *
120
+ * @param {string|array} eventName - Tên event hoặc array các tên event
121
+ * @param {function} callback - Hàm callback
122
+ * @return {function} Hàm hủy đăng ký
123
+ */
124
+ once(eventName, callback) {
125
+ return this.on(eventName, callback, true);
126
+ }
127
+
128
+ /**
129
+ * Hủy đăng ký lắng nghe event
130
+ *
131
+ * @param {string|array|object} eventName - Tên event hoặc array các tên event hoặc "event1 event2..." hoặc object {event: callback}
132
+ * @param {function|null} callback - Callback cụ thể (null = xóa tất cả)
133
+ */
134
+ off(eventName, callback = null) {
135
+ // Hỗ trợ object syntax: off({event1: fn1, event2: fn2})
136
+ if(typeof eventName === 'object' && !Array.isArray(eventName)){
137
+ for(const [event, fn] of Object.entries(eventName)){
138
+ this.off(event, fn);
139
+ }
140
+ return;
141
+ }
142
+
143
+ // Hỗ trợ string với spaces: "event1 event2 event3"
144
+ if(typeof eventName === 'string' && eventName.includes(' ')){
145
+ eventName = eventName.split(/\s+/).filter(e => e);
146
+ }
147
+
148
+ // Hỗ trợ unsubscribe nhiều events
149
+ if(Array.isArray(eventName)){
150
+ const eventSet = new Set(eventName);
151
+
152
+ const areSetsEqual = (set1, set2) => {
153
+ if(set1.size !== set2.size) return false;
154
+ for(const e of set1){
155
+ if(!set2.has(e)) return false;
156
+ }
157
+ return true;
158
+ };
159
+
160
+ if(!callback){
161
+ // Xóa TẤT CẢ multi-event listeners có cùng set events
162
+ for(let i = this.multiEventListeners.length - 1; i >= 0; i--){
163
+ if(areSetsEqual(this.multiEventListeners[i].events, eventSet)){
164
+ this.multiEventListeners.splice(i, 1);
165
+ }
166
+ }
167
+
168
+ // Xóa tất cả single-event listeners của các events này
169
+ eventName.forEach(name => {
170
+ if(this.listeners.has(name)){
171
+ this.listeners.delete(name);
172
+ }
173
+ });
174
+ return;
175
+ }
176
+
177
+ // Xóa listener cụ thể từ multi-event listeners
178
+ const index = this.multiEventListeners.findIndex(listener => {
179
+ return listener.callback === callback && areSetsEqual(listener.events, eventSet);
180
+ });
181
+
182
+ if(index !== -1){
183
+ this.multiEventListeners.splice(index, 1);
184
+ }
185
+
186
+ // Xóa callback từ tất cả single-event listeners của các events này
187
+ eventName.forEach(name => {
188
+ if(this.listeners.has(name)){
189
+ const listeners = this.listeners.get(name);
190
+ const idx = listeners.findIndex(listener => listener.callback === callback);
191
+
192
+ if(idx !== -1){
193
+ listeners.splice(idx, 1);
194
+ }
195
+
196
+ if(listeners.length === 0){
197
+ this.listeners.delete(name);
198
+ }
199
+ }
200
+ });
201
+ return;
202
+ }
203
+
204
+ if(typeof eventName !== 'string'){
205
+ return;
206
+ }
207
+
208
+ if(!this.listeners.has(eventName)){
209
+ return;
210
+ }
211
+
212
+ // Xóa tất cả listeners của event
213
+ if(!callback){
214
+ this.listeners.delete(eventName);
215
+ return;
216
+ }
217
+
218
+ // Xóa listener cụ thể
219
+ const listeners = this.listeners.get(eventName);
220
+ const index = listeners.findIndex(listener => listener.callback === callback);
221
+
222
+ if(index !== -1){
223
+ listeners.splice(index, 1);
224
+ }
225
+
226
+ // Cleanup nếu không còn listener nào
227
+ if(listeners.length === 0){
228
+ this.listeners.delete(eventName);
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Trigger một event
234
+ *
235
+ * @param {string} eventName - Tên event
236
+ * @param {...any} args - Arguments truyền cho callback
237
+ */
238
+ emit(eventName, ...args) {
239
+ if(typeof eventName !== 'string'){
240
+ return;
241
+ }
242
+
243
+ // Schedule flush nếu chưa có
244
+ if(!this.hasPendingFlush){
245
+ this.hasPendingFlush = true;
246
+ Promise.resolve().then(() => {
247
+ this.flushBatch();
248
+ });
249
+ }
250
+
251
+ // Single-event listeners
252
+ if(this.listeners.has(eventName)){
253
+ const listeners = this.listeners.get(eventName);
254
+ const toRemove = [];
255
+
256
+ listeners.forEach((listener, index) => {
257
+ // Batch deduplication: Chỉ gọi callback 1 lần trong batch
258
+ if(!this.calledInBatch.has(listener.callback)){
259
+ this.calledInBatch.add(listener.callback);
260
+
261
+ try {
262
+ listener.callback(...args);
263
+ } catch(error) {
264
+ console.error(`Error in event listener for "${eventName}":`, error);
265
+ }
266
+ }
267
+
268
+ if(listener.once){
269
+ toRemove.push(index);
270
+ }
271
+ });
272
+
273
+ // Remove once listeners
274
+ for(let i = toRemove.length - 1; i >= 0; i--){
275
+ listeners.splice(toRemove[i], 1);
276
+ }
277
+
278
+ if(listeners.length === 0){
279
+ this.listeners.delete(eventName);
280
+ }
281
+ }
282
+
283
+ // Multi-event listeners
284
+ this.multiEventListeners.forEach(listener => {
285
+ if(!listener.called && listener.events.has(eventName)){
286
+ listener.called = true;
287
+
288
+ try {
289
+ listener.callback(...args);
290
+ } catch(error) {
291
+ console.error(`Error in multi-event listener for "${eventName}":`, error);
292
+ }
293
+ }
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Flush batch và reset flags
299
+ * @private
300
+ */
301
+ flushBatch() {
302
+ // Reset multi-event listeners called flag
303
+ this.multiEventListeners.forEach(listener => {
304
+ listener.called = false;
305
+ });
306
+
307
+ // Remove once multi-event listeners
308
+ for(let i = this.multiEventListeners.length - 1; i >= 0; i--){
309
+ if(this.multiEventListeners[i].once && this.multiEventListeners[i].called){
310
+ this.multiEventListeners.splice(i, 1);
311
+ }
312
+ }
313
+
314
+ // Clear batch tracking
315
+ this.calledInBatch.clear();
316
+ this.hasPendingFlush = false;
317
+ }
318
+
319
+ /**
320
+ * Trigger event bất đồng bộ (async)
321
+ *
322
+ * @param {string} eventName - Tên event
323
+ * @param {...any} args - Arguments truyền cho callback
324
+ * @return {Promise<void>}
325
+ */
326
+ async emitAsync(eventName, ...args) {
327
+ if(typeof eventName !== 'string'){
328
+ return;
329
+ }
330
+
331
+ if(!this.listeners.has(eventName)){
332
+ return;
333
+ }
334
+
335
+ const listeners = this.listeners.get(eventName);
336
+ const toRemove = [];
337
+
338
+ // Gọi tất cả listeners và chờ nếu là async
339
+ for(let i = 0; i < listeners.length; i++){
340
+ const listener = listeners[i];
341
+ try {
342
+ await listener.callback(...args);
343
+ } catch(error) {
344
+ console.error(`Error in async event listener for "${eventName}":`, error);
345
+ }
346
+
347
+ if(listener.once){
348
+ toRemove.push(i);
349
+ }
350
+ }
351
+
352
+ // Remove once listeners
353
+ for(let i = toRemove.length - 1; i >= 0; i--){
354
+ listeners.splice(toRemove[i], 1);
355
+ }
356
+
357
+ if(listeners.length === 0){
358
+ this.listeners.delete(eventName);
359
+ }
360
+ }
361
+
362
+ /**
363
+ * Kiểm tra có listener nào cho event không
364
+ *
365
+ * @param {string} eventName - Tên event
366
+ * @return {boolean}
367
+ */
368
+ hasListeners(eventName) {
369
+ return this.listeners.has(eventName) && this.listeners.get(eventName).length > 0;
370
+ }
371
+
372
+ /**
373
+ * Lấy số lượng listeners của event
374
+ *
375
+ * @param {string} eventName - Tên event
376
+ * @return {number}
377
+ */
378
+ listenerCount(eventName) {
379
+ if(!this.listeners.has(eventName)){
380
+ return 0;
381
+ }
382
+ return this.listeners.get(eventName).length;
383
+ }
384
+
385
+ /**
386
+ * Lấy danh sách tất cả event names
387
+ *
388
+ * @return {Array<string>}
389
+ */
390
+ eventNames() {
391
+ return Array.from(this.listeners.keys());
392
+ }
393
+
394
+ /**
395
+ * Xóa tất cả listeners của tất cả events
396
+ */
397
+ clear() {
398
+ this.listeners.clear();
399
+ }
400
+
401
+ createManager(packetName) {
402
+ return EventService.getInstance(packetName);
403
+ }
404
+
405
+ addEventListener(eventName, callback) {
406
+ return this.on(eventName, callback);
407
+ }
408
+
409
+ removeEventListener(eventName, callback = null) {
410
+ this.off(eventName, callback);
411
+ }
412
+
413
+ dispatchEvent(eventName, ...args) {
414
+ this.emit(eventName, ...args);
415
+ }
416
+ }
417
+
418
+ export default EventService.getInstance();
@@ -0,0 +1,106 @@
1
+ /**
2
+ * HTTP Service Module
3
+ * ES6 Module cho Blade Compiler
4
+ */
5
+
6
+
7
+ class HttpService {
8
+
9
+ constructor() {
10
+ this.baseUrl = '';
11
+ this.timeout = 10000;
12
+ this.defaultHeaders = {};
13
+ }
14
+
15
+ setBaseUrl(url) {
16
+ this.baseUrl = url;
17
+ }
18
+
19
+ setTimeout(timeout) {
20
+ this.timeout = timeout;
21
+ }
22
+
23
+ setDefaultHeaders(headers) {
24
+ this.defaultHeaders = { ...this.defaultHeaders, ...headers };
25
+ }
26
+
27
+ setHeader(name, value) {
28
+ this.defaultHeaders[name] = value;
29
+ }
30
+
31
+ async request(method, url, data = null, options = {}) {
32
+ let fullUrl = url.startsWith('http') ? url : this.baseUrl + url;
33
+
34
+ const config = {
35
+ method: method.toUpperCase(),
36
+ headers: {
37
+ ...this.defaultHeaders,
38
+ ...options.headers
39
+ },
40
+ ...options
41
+ };
42
+
43
+ if (data && (method.toUpperCase() === 'POST' || method.toUpperCase() === 'PUT' || method.toUpperCase() === 'PATCH')) {
44
+ if (config.headers['Content-Type'] === 'application/json') {
45
+ config.body = JSON.stringify(data);
46
+ } else {
47
+ config.body = data;
48
+ }
49
+ }
50
+ if (data && method.toUpperCase() === 'GET') {
51
+ const urlObj = new URL(fullUrl, window.location.origin);
52
+ Object.keys(data).forEach(key => urlObj.searchParams.append(key, data[key]));
53
+ fullUrl = urlObj.toString();
54
+ }
55
+
56
+ try {
57
+ const controller = new AbortController();
58
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
59
+
60
+ const response = await fetch(fullUrl, {
61
+ ...config,
62
+ signal: controller.signal
63
+ });
64
+
65
+ clearTimeout(timeoutId);
66
+
67
+ const responseData = await response.json().catch(() => ({}));
68
+
69
+ return {
70
+ status: response.ok,
71
+ statusCode: response.status,
72
+ data: responseData,
73
+ headers: response.headers
74
+ };
75
+ } catch (error) {
76
+ if (error.name === 'AbortError') {
77
+ throw new Error('Request timeout');
78
+ }
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ async get(url, params, options = {}) {
84
+ return this.request('GET', url, params, options);
85
+ }
86
+
87
+ async post(url, data = null, options = {}) {
88
+ return this.request('POST', url, data, options);
89
+ }
90
+
91
+ async put(url, data = null, options = {}) {
92
+ return this.request('PUT', url, data, options);
93
+ }
94
+
95
+ async patch(url, data = null, options = {}) {
96
+ return this.request('PATCH', url, data, options);
97
+ }
98
+
99
+ async delete(url, options = {}) {
100
+ return this.request('DELETE', url, null, options);
101
+ }
102
+ }
103
+
104
+
105
+ // Export cho ES6 modules
106
+ export { HttpService };
@@ -0,0 +1,57 @@
1
+ export class LoggerService {
2
+ constructor(config) {
3
+ this.config = {
4
+ level: 'info',
5
+ enabled: false,
6
+ console: true,
7
+ remote: false
8
+ };
9
+
10
+ if (config) {
11
+ this.setConfig(config);
12
+ }
13
+
14
+ this.logs = [];
15
+ }
16
+
17
+ setConfig(config) {
18
+ this.config = { ...this.config, ...config };
19
+ }
20
+
21
+ getConfig() {
22
+ return this.config;
23
+ }
24
+
25
+ consoleLog(method, ...args) {
26
+ this.logs.push(...args)
27
+ if (this.config.console && this.config.enabled) {
28
+ return console[method].apply(console, args);
29
+ }
30
+ }
31
+
32
+ log(...args) {
33
+ return this.consoleLog('log', ...args);
34
+ }
35
+
36
+ warn(...args) {
37
+ return this.consoleLog('warn', ...args);
38
+ }
39
+
40
+ error(...args) {
41
+ return this.consoleLog('error', ...args);
42
+ }
43
+
44
+ info(...args) {
45
+ return this.consoleLog('info', ...args);
46
+ }
47
+
48
+ debug(...args) {
49
+ return this.consoleLog('debug', ...args);
50
+ }
51
+
52
+ }
53
+
54
+
55
+ export const logger = new LoggerService({level: 'info', enabled: true, console: true, remote: false});
56
+ export default logger;
57
+