neo.mjs 10.0.0-beta.1 → 10.0.0-beta.2

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 (138) hide show
  1. package/ServiceWorker.mjs +2 -2
  2. package/apps/colors/view/GridContainer.mjs +1 -1
  3. package/apps/covid/view/AttributionComponent.mjs +1 -1
  4. package/apps/covid/view/HeaderContainer.mjs +6 -6
  5. package/apps/covid/view/MainContainerController.mjs +5 -5
  6. package/apps/covid/view/TableContainerController.mjs +1 -1
  7. package/apps/covid/view/country/Gallery.mjs +13 -13
  8. package/apps/covid/view/country/Helix.mjs +13 -13
  9. package/apps/covid/view/country/HistoricalDataTable.mjs +1 -1
  10. package/apps/email/view/Viewport.mjs +2 -2
  11. package/apps/form/view/SideNavList.mjs +1 -1
  12. package/apps/portal/index.html +1 -1
  13. package/apps/portal/resources/data/examples_dist_esm.json +1 -1
  14. package/apps/portal/resources/data/examples_dist_prod.json +2 -2
  15. package/apps/portal/view/HeaderToolbar.mjs +3 -3
  16. package/apps/portal/view/about/Container.mjs +2 -2
  17. package/apps/portal/view/about/MemberContainer.mjs +3 -3
  18. package/apps/portal/view/blog/List.mjs +7 -7
  19. package/apps/portal/view/examples/List.mjs +4 -4
  20. package/apps/portal/view/home/ContentBox.mjs +2 -2
  21. package/apps/portal/view/home/FeatureSection.mjs +3 -3
  22. package/apps/portal/view/home/FooterContainer.mjs +7 -7
  23. package/apps/portal/view/home/parts/AfterMath.mjs +3 -3
  24. package/apps/portal/view/home/parts/MainNeo.mjs +3 -3
  25. package/apps/portal/view/home/parts/References.mjs +6 -6
  26. package/apps/portal/view/learn/ContentComponent.mjs +3 -3
  27. package/apps/portal/view/learn/PageSectionsContainer.mjs +1 -1
  28. package/apps/portal/view/learn/PageSectionsList.mjs +2 -2
  29. package/apps/portal/view/services/Component.mjs +16 -16
  30. package/apps/realworld/view/FooterComponent.mjs +1 -1
  31. package/apps/realworld/view/HeaderComponent.mjs +8 -8
  32. package/apps/realworld/view/HomeComponent.mjs +6 -6
  33. package/apps/realworld/view/article/CommentComponent.mjs +4 -4
  34. package/apps/realworld/view/article/Component.mjs +14 -14
  35. package/apps/realworld/view/article/CreateCommentComponent.mjs +3 -3
  36. package/apps/realworld/view/article/CreateComponent.mjs +3 -3
  37. package/apps/realworld/view/article/PreviewComponent.mjs +1 -1
  38. package/apps/realworld/view/article/TagListComponent.mjs +2 -2
  39. package/apps/realworld/view/user/ProfileComponent.mjs +8 -8
  40. package/apps/realworld/view/user/SettingsComponent.mjs +4 -4
  41. package/apps/realworld/view/user/SignUpComponent.mjs +4 -4
  42. package/apps/realworld2/view/FooterComponent.mjs +1 -1
  43. package/apps/realworld2/view/HomeContainer.mjs +3 -3
  44. package/apps/realworld2/view/article/DetailsContainer.mjs +1 -1
  45. package/apps/realworld2/view/article/PreviewComponent.mjs +7 -7
  46. package/apps/realworld2/view/article/TagListComponent.mjs +2 -2
  47. package/apps/realworld2/view/user/ProfileContainer.mjs +1 -1
  48. package/apps/route/view/center/CardAdministration.mjs +2 -2
  49. package/apps/route/view/center/CardAdministrationDenied.mjs +1 -1
  50. package/apps/route/view/center/CardContact.mjs +2 -2
  51. package/apps/route/view/center/CardHome.mjs +1 -1
  52. package/apps/route/view/center/CardSection1.mjs +1 -1
  53. package/apps/route/view/center/CardSection2.mjs +1 -1
  54. package/apps/sharedcovid/view/AttributionComponent.mjs +1 -1
  55. package/apps/sharedcovid/view/HeaderContainer.mjs +6 -6
  56. package/apps/sharedcovid/view/MainContainerController.mjs +5 -5
  57. package/apps/sharedcovid/view/TableContainerController.mjs +1 -1
  58. package/apps/sharedcovid/view/country/Gallery.mjs +13 -13
  59. package/apps/sharedcovid/view/country/Helix.mjs +13 -13
  60. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -1
  61. package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +1 -1
  62. package/apps/shareddialog/view/MainContainer.mjs +1 -1
  63. package/buildScripts/createApp.mjs +2 -2
  64. package/learn/Glossary.md +261 -0
  65. package/learn/benefits/ConfigSystem.md +536 -26
  66. package/learn/benefits/Effort.md +47 -2
  67. package/learn/benefits/Features.md +50 -32
  68. package/learn/benefits/FormsEngine.md +54 -24
  69. package/learn/benefits/MultiWindow.md +31 -5
  70. package/learn/benefits/Quick.md +45 -12
  71. package/learn/benefits/RPCLayer.md +75 -0
  72. package/learn/benefits/Speed.md +17 -12
  73. package/learn/guides/ConfigSystemDeepDive.md +280 -0
  74. package/learn/guides/DeclarativeComponentTreesVsImperativeVdom.md +17 -17
  75. package/learn/guides/InstanceLifecycle.md +295 -1
  76. package/learn/guides/MainThreadAddons.md +475 -0
  77. package/learn/guides/WorkingWithVDom.md +14 -14
  78. package/learn/tree.json +52 -51
  79. package/package.json +2 -2
  80. package/src/DefaultConfig.mjs +2 -2
  81. package/src/Main.mjs +8 -7
  82. package/src/Neo.mjs +16 -2
  83. package/src/button/Base.mjs +2 -2
  84. package/src/calendar/view/SettingsContainer.mjs +2 -2
  85. package/src/calendar/view/YearComponent.mjs +9 -9
  86. package/src/calendar/view/calendars/ColorsList.mjs +1 -1
  87. package/src/calendar/view/calendars/List.mjs +1 -1
  88. package/src/calendar/view/month/Component.mjs +15 -15
  89. package/src/calendar/view/week/Component.mjs +12 -12
  90. package/src/calendar/view/week/EventDragZone.mjs +4 -4
  91. package/src/calendar/view/week/TimeAxisComponent.mjs +3 -3
  92. package/src/component/Base.mjs +17 -2
  93. package/src/component/Carousel.mjs +2 -2
  94. package/src/component/Chip.mjs +3 -3
  95. package/src/component/Circle.mjs +2 -2
  96. package/src/component/DateSelector.mjs +8 -8
  97. package/src/component/Helix.mjs +1 -1
  98. package/src/component/Label.mjs +3 -18
  99. package/src/component/Legend.mjs +3 -3
  100. package/src/component/MagicMoveText.mjs +6 -14
  101. package/src/component/Process.mjs +3 -3
  102. package/src/component/Progress.mjs +1 -1
  103. package/src/component/StatusBadge.mjs +2 -2
  104. package/src/component/Timer.mjs +2 -2
  105. package/src/component/Toast.mjs +5 -3
  106. package/src/container/AccordionItem.mjs +2 -2
  107. package/src/container/Base.mjs +1 -1
  108. package/src/core/Base.mjs +18 -2
  109. package/src/date/DayViewComponent.mjs +2 -2
  110. package/src/date/SelectorContainer.mjs +1 -1
  111. package/src/form/field/CheckBox.mjs +4 -4
  112. package/src/form/field/FileUpload.mjs +25 -39
  113. package/src/form/field/Range.mjs +1 -1
  114. package/src/form/field/Text.mjs +3 -3
  115. package/src/form/field/TextArea.mjs +2 -3
  116. package/src/grid/Body.mjs +6 -2
  117. package/src/list/Color.mjs +2 -2
  118. package/src/main/DeltaUpdates.mjs +157 -98
  119. package/src/main/addon/AmCharts.mjs +53 -73
  120. package/src/main/addon/Base.mjs +11 -0
  121. package/src/main/addon/MonacoEditor.mjs +31 -58
  122. package/src/manager/ClassHierarchy.mjs +114 -0
  123. package/src/menu/List.mjs +1 -1
  124. package/src/plugin/Popover.mjs +2 -2
  125. package/src/sitemap/Component.mjs +1 -1
  126. package/src/table/Body.mjs +6 -2
  127. package/src/tooltip/Base.mjs +1 -6
  128. package/src/tree/Accordion.mjs +3 -3
  129. package/src/vdom/Helper.mjs +19 -19
  130. package/src/worker/App.mjs +1 -2
  131. package/src/worker/Base.mjs +6 -4
  132. package/src/worker/Canvas.mjs +2 -3
  133. package/src/worker/Data.mjs +5 -7
  134. package/src/worker/Task.mjs +2 -3
  135. package/src/worker/VDom.mjs +3 -4
  136. package/src/worker/mixin/RemoteMethodAccess.mjs +4 -1
  137. package/learn/guides/MainThreadAddonExample.md +0 -15
  138. package/learn/guides/MainThreadAddonIntro.md +0 -44
@@ -0,0 +1,475 @@
1
+ Neo.mjs is multi-threaded. There are worker threads that handle data access, application logic, and
2
+ keeping track of DOM updates. Practically all your application logic is run in parallel in these
3
+ threads. However, anything that needs to actually reference or update the DOM (`window.document`),
4
+ or just use the `window` object, must be done in the main application thread.
5
+
6
+ That's the purpose of main thread addons. These are classes whose methods can be accessed from other
7
+ web workers, but are actually executed in the main thread.
8
+
9
+ For example, what if you needed to read the browser's URL? That information is in `window.location`.
10
+ But `window` is a main thread variable! To access that from a web-worker our code has to say "hey
11
+ main thread, please return a specified `window` property." Neo.mjs lets you do that via
12
+ `Neo.Main.getByPath()`. For example, the following statement logs the URL query string.
13
+
14
+ ```javascript readonly
15
+ const search = await Neo.Main.getByPath({path: 'window.location.search'});
16
+ console.log(search); // Logs the search string
17
+ ```
18
+
19
+ `Neo.Main` & `Neo.main.DomAccess` provide some basic methods for accessing the main thread, but in
20
+ case you want to use a third party library which relies on directly working with the DOM, you'd use
21
+ a main thread addon.
22
+
23
+ Google Maps is a good example of this. In Neo.mjs, most views are responsible for updating their own
24
+ vdom, but the responsibility for rendering maps and markers is handled by Google Maps itself — we
25
+ _ask_ Google Maps to do certain things via the Google Maps API. Therefore, in Neo.mjs, Google Maps
26
+ is implemented as a main thread addon which loads the libraries and exposes whatever methods we'll
27
+ need to run from the other Neo.mjs threads. In addition, in a Neo.mjs application we want to use
28
+ Google Maps like any other component, so Neo.mjs also provides a component wrapper. In summary:
29
+ - The main-thread addon contains the code run in the main thread, and exposes what methods can be
30
+ run by other web-workers (remote method access)
31
+ - The component wrapper lets you use it like any other component, internally calling the main thread
32
+ methods as needed.
33
+
34
+ ## How it Works: The Round Trip of a Remote Call
35
+
36
+ When your code in the App Worker calls an addon method, a sophisticated, promise-based communication
37
+ happens automatically behind the scenes.
38
+
39
+ Let's trace the journey of a single call:
40
+
41
+ ```javascript readonly
42
+ // Inside a component in the App Worker
43
+ async function getMySetting() {
44
+ let data = await Neo.main.addon.LocalStorage.readLocalStorageItem({key: 'my-setting'});
45
+ console.log(data.value);
46
+ }
47
+ ```
48
+
49
+ Here's what happens when `getMySetting()` is executed:
50
+
51
+ ```text
52
+ +------------------------------------------------+ +------------------------------------------------+
53
+ | App Worker | | Main Thread |
54
+ +------------------------------------------------+ +------------------------------------------------+
55
+ | | | |
56
+ | 1. Your code calls a proxy method. | | |
57
+ | e.g., `addon.readLocalStorageItem()` | | |
58
+ | | | |
59
+ | This immediately returns a `Promise`. | | |
60
+ | | | |
61
+ |------------------------------------------------| | |
62
+ | | | |
63
+ | 2. A message is sent to the Main Thread | ----> | 3. The message is received. The framework |
64
+ | containing the target & arguments. | | finds the addon instance and calls the |
65
+ | | | *real* method with the arguments. |
66
+ | | | |
67
+ |------------------------------------------------| |------------------------------------------------|
68
+ | | | |
69
+ | 5. The Promise from Step 1 is resolved with | <---- | 4. The method returns a value. The framework |
70
+ | the value from the reply message. | | packages this value in a reply message |
71
+ | | | and sends it back to the App Worker. |
72
+ | The `await` keyword gets the final value. | | |
73
+ | | | |
74
+ +------------------------------------------------+ +------------------------------------------------+
75
+ ```
76
+
77
+ 1. **The Call (App Worker)**: Your code calls what looks like a normal static method. However, this
78
+ `readLocalStorageItem` function is actually a "proxy" or "stub" created by the framework.
79
+ 2. **The Message (App Worker -> Main Thread)**: The proxy function immediately returns a `Promise`
80
+ and sends a message to the main thread containing the addon's class name
81
+ (`Neo.main.addon.LocalStorage`), the method name (`readLocalStorageItem`), and the arguments
82
+ (`{key: 'my-setting'}`).
83
+ 3. **The Execution (Main Thread)**: The main thread receives the message, finds the `LocalStorage`
84
+ addon instance, and calls the real `readLocalStorageItem` method with the provided arguments.
85
+ 4. **The Return (Main Thread -> App Worker)**: The method returns the value from `localStorage`. The
86
+ main thread packages this return value into a "reply" message and sends it back to the App
87
+ Worker.
88
+ 5. **Promise Resolution (App Worker)**: The App Worker receives the reply and uses it to resolve the
89
+ Promise from Step 2. The `await` is now complete, and the `data` variable receives the value.
90
+
91
+ This entire round trip is completely managed by the framework. As a developer, you only need to
92
+ `await` the result, just like any other asynchronous function.
93
+
94
+ ## Anatomy of an Addon: `LocalStorage` and `Cookie` Examples
95
+
96
+ Addons are standard Neo.mjs classes that extend `Neo.main.addon.Base`. They define their public API
97
+ through the `remote` config.
98
+
99
+ ### The `LocalStorage` Addon
100
+
101
+ The `LocalStorage` addon provides basic CRUD (Create, Read, Update, Delete) operations for the
102
+ browser's `window.localStorage`.
103
+
104
+ Let's look at its source code
105
+ ([src/main/addon/LocalStorage.mjs](https://github.com/neomjs/neo/blob/dev/src/main/addon/LocalStorage.mjs)):
106
+
107
+ ```javascript readonly
108
+ import Base from './Base.mjs';
109
+
110
+ /**
111
+ * Basic CRUD support for window.localStorage
112
+ * @class Neo.main.addon.LocalStorage
113
+ * @extends Neo.main.addon.Base
114
+ */
115
+ class LocalStorage extends Base {
116
+ static config = {
117
+ className: 'Neo.main.addon.LocalStorage',
118
+ remote: {
119
+ app: [
120
+ 'createLocalStorageItem',
121
+ 'destroyLocalStorageItem',
122
+ 'readLocalStorageItem',
123
+ 'updateLocalStorageItem'
124
+ ]
125
+ }
126
+ }
127
+
128
+ readLocalStorageItem(opts) {
129
+ return {
130
+ key : opts.key,
131
+ value: window.localStorage.getItem(opts.key)
132
+ }
133
+ }
134
+
135
+ updateLocalStorageItem(opts) {
136
+ window.localStorage.setItem(opts.key, opts.value)
137
+ }
138
+ // ... other methods
139
+ }
140
+
141
+ export default Neo.setupClass(LocalStorage);
142
+ ```
143
+
144
+ ### The `Cookie` Addon
145
+
146
+ The framework provides another great example of an addon for interacting with a browser API: the
147
+ `Cookie` addon. It provides methods to read and write to `document.cookie`.
148
+
149
+ Let's analyze its source code
150
+ ([src/main/addon/Cookie.mjs](https://github.com/neomjs/neo/blob/dev/src/main/addon/Cookie.mjs)):
151
+
152
+ ```javascript readonly
153
+ import Base from './Base.mjs';
154
+
155
+ class Cookie extends Base {
156
+ static config = {
157
+ className: 'Neo.main.addon.Cookie',
158
+ remote: {
159
+ app: [
160
+ 'getCookie',
161
+ 'getCookies',
162
+ 'setCookie'
163
+ ]
164
+ }
165
+ }
166
+
167
+ getCookie(name) {
168
+ let {cookie} = document
169
+ .split('; ')
170
+ .find(row => row.startsWith(name));
171
+
172
+ return cookie ? cookie.split('=')[1] : null
173
+ }
174
+
175
+ setCookie(value) {
176
+ document.cookie = value
177
+ }
178
+ // ...
179
+ }
180
+
181
+ export default Neo.setupClass(Cookie);
182
+ ```
183
+
184
+ Both addons follow the same pattern:
185
+ 1. They extend `Neo.main.addon.Base`.
186
+ 2. They define their `remote` config to expose methods to the `app` worker.
187
+ 3. Their methods directly interact with browser-specific APIs (`window.localStorage`,
188
+ `document.cookie`) that are only available on the main thread.
189
+
190
+ ## Managing Addons: The Full Lifecycle
191
+
192
+ There are two primary ways to bring an addon to life within your application, each serving
193
+ different needs.
194
+
195
+ ### A. Eager Loading: The Standard Approach
196
+
197
+ For addons that are essential for your application's initial operation (e.g., `LocalStorage`,
198
+ `Stylesheet`), they are typically instantiated at application startup.
199
+
200
+ 1. **Configuration:** You can specify addons in your application's `neo-config.json` file.
201
+ 2. **Instantiation:** `src/Main.mjs` (the main thread's entry point) is responsible for
202
+ instantiating these configured addons when the application starts. This ensures they are ready
203
+ for use as soon as possible.
204
+
205
+ ### B. Lazy Loading: The Performance-Oriented Approach
206
+
207
+ For addons that are not needed immediately at startup, or for features that are only used
208
+ conditionally, you can lazy-load them on demand. This improves initial application load performance.
209
+
210
+ You can use `Neo.worker.App.getAddon()` to dynamically load and instantiate an addon:
211
+
212
+ ```javascript readonly
213
+ // Example: Only load a complex charting addon when a user clicks a button
214
+ async function showChart() {
215
+ // getAddon will ensure the addon is instantiated and ready
216
+ const chartingAddon = await Neo.worker.App.getAddon('Neo.main.addon.ChartingLibrary');
217
+ chartingAddon.createChart({ /* ... config ... */ });
218
+ }
219
+ ```
220
+
221
+ ### C. The "Semi-Singleton" Design: Why Addons are Extensible
222
+
223
+ Addons *behave* like singletons within the main thread (meaning there's typically only one instance
224
+ of a given addon class). However, they are deliberately *not defined* with `singleton: true` in
225
+ their `static config`. This is a crucial architectural decision that enables powerful extensibility:
226
+
227
+ * **Customization:** Developers can extend a framework addon (e.g., `class MyLocalStorage extends
228
+ Neo.main.addon.LocalStorage`), override its methods, and then configure their `neo-config.json`
229
+ to load *their* custom version.
230
+ * **Flexibility:** If the base class were a true singleton, this kind of runtime extension and
231
+ override would be impossible. By making them "semi-singletons" that `Main.mjs` or
232
+ `Neo.worker.App.getAddon()` manages as single instances, the framework provides both the
233
+ convenience of a singleton and the power of class-based extension.
234
+
235
+ ### Example: Customizing `LocalStorage`
236
+
237
+ Let's say you want to add a custom prefix to all keys stored in `localStorage` for your application.
238
+ You can extend the `Neo.main.addon.LocalStorage` and override its `readLocalStorageItem` and
239
+ `updateLocalStorageItem` methods.
240
+
241
+ First, create your custom addon (e.g., `workspace/src/addon/CustomLocalStorage.mjs`):
242
+
243
+ ```javascript readonly
244
+ // workspace/src/addon/CustomLocalStorage.mjs
245
+ import LocalStorage from '../../../node_modules/neo.mjs/src/main/addon/LocalStorage.mjs';
246
+
247
+ class CustomLocalStorage extends LocalStorage {
248
+ static config = {
249
+ className: 'MyApp.main.addon.CustomLocalStorage',
250
+ // This is optional, Neo.Main will always convert main thread addons into singletons.
251
+ // If you want to keep your class open to further extensions, you can use the "semi-singleton" pattern too.
252
+ singleton: true,
253
+ // No need to redefine remote config, it's inherited
254
+ }
255
+
256
+ readLocalStorageItem(opts) {
257
+ opts.key = 'myApp_' + opts.key; // Add your custom prefix
258
+ return super.readLocalStorageItem(opts);
259
+ }
260
+
261
+ updateLocalStorageItem(opts) {
262
+ opts.key = 'myApp_' + opts.key; // Add your custom prefix
263
+ super.updateLocalStorageItem(opts);
264
+ }
265
+ }
266
+
267
+ export default Neo.setupClass(CustomLocalStorage);
268
+ ```
269
+
270
+ Next, configure your `neo-config.json` to use your custom addon instead of the framework's default.
271
+ This is done by mapping your custom class to the framework's original class name using the `WS/` prefix.
272
+ The `WS/` prefix (which stands for "workspace") tells the framework to look for your addon within the `src/main/addon`
273
+ directory of your workspace (the output of `npx neo-app`).
274
+
275
+ [Side Note]: If you add a new addon to the framework repo, the `WS/` prefix is not needed.
276
+
277
+ ```json
278
+ // neo-config.json
279
+ {
280
+ "mainThreadAddons": [
281
+ // ...
282
+ "WS/CustomLocalStorage"
283
+ ]
284
+ }
285
+ ```
286
+
287
+ Now, any call to `Neo.main.addon.CustomLocalStorage.readLocalStorageItem()` or `updateLocalStorageItem()`
288
+ from your app worker will actually be routed to your `CustomLocalStorage` instance on the main thread,
289
+ automatically applying your custom key prefix. This demonstrates how easily you can swap out or extend
290
+ framework-provided functionality with your own custom implementations.
291
+
292
+ ## Asynchronous Initialization: `initAsync` and the `isReady` config
293
+
294
+ The multi-threaded nature of Neo.mjs introduces a subtle but important challenge: ensuring that an
295
+ addon is fully initialized and its environment is ready before it's used. This is solved by the
296
+ `initAsync` lifecycle method and the `isReady` config, managed by `Neo.core.Base`.
297
+
298
+ ### The Problem: A Race Condition
299
+
300
+ Consider a scenario where a Main thread addon needs to register itself with another core framework
301
+ service (like `Neo.worker.Manager` or `Neo.manager.Instance`). These services are also instantiated
302
+ on the Main thread. If the addon's `initAsync()` (or any logic called from it) tries to interact
303
+ with such a service *during* that service's synchronous construction phase, it might try to access
304
+ properties or methods before they are fully initialized, leading to a race condition.
305
+
306
+ ### The Solution: Microtasks and `isReady`
307
+
308
+ `Neo.core.Base` addresses this by scheduling the `initAsync()` method in the JavaScript microtask
309
+ queue.
310
+
311
+ 1. **Synchronous Construction Completes:** The entire synchronous `construct()` method of a class
312
+ (including the worker's `construct()` that sets `Neo.currentWorker`) runs to completion first.
313
+ 2. **`initAsync()` Executes:** Only *after* the current synchronous block finishes, the microtask
314
+ queue is processed, and `initAsync()` is called on all newly created instances.
315
+ 3. **`isReady` Signal:** Once `initAsync()` (and any `await`ed operations within it, like
316
+ `loadFiles()`) completes, the addon's `isReady` flag is set to `true`. This is the definitive
317
+ signal that the addon is fully initialized and safe to interact with.
318
+ 4. **`afterSetIsReady()`:** If needed, you can listen to changes of the `isReady` config value,
319
+ using the provided hook.
320
+
321
+ ### The `cacheMethodCall()` Safety Net
322
+
323
+ The `Neo.main.addon.Base` class provides a crucial utility, `cacheMethodCall()`, for managing remote
324
+ method calls that arrive before an addon is fully `isReady`. Thanks to a generic interception
325
+ mechanism in `Neo.worker.mixin.RemoteMethodAccess`, if a remote call for a method listed in the
326
+ addon's `interceptRemotes` config arrives while `isReady` is `false`, the call is automatically
327
+ queued. Once `isReady` becomes `true`, all cached calls are processed in order.
328
+
329
+ Here's how `onInterceptRemotes()` in `Neo.main.addon.Base` handles this:
330
+
331
+ ```javascript readonly
332
+ onInterceptRemotes(msg) {
333
+ return this.cacheMethodCall({fn: msg.remoteMethod, data: msg.data})
334
+ }
335
+ ```
336
+
337
+ This significantly simplifies addon development by centralizing the queuing logic.
338
+
339
+ ## The Component Wrapper Pattern: Putting It All Together
340
+
341
+ While you can call addon methods directly (e.g.,
342
+ `await Neo.main.addon.LocalStorage.readLocalStorageItem()`), the best practice for integrating
343
+ addons into your application's UI is to create a **component wrapper**.
344
+
345
+ A component wrapper is a standard Neo.mjs component (running in the App Worker) that encapsulates
346
+ the interaction with a main thread addon. It exposes a clean, declarative API to your application,
347
+ while internally handling the remote method calls to the addon.
348
+
349
+ The `Neo.component.wrapper.MonacoEditor` is a perfect real-world example of this pattern.
350
+
351
+ ### Case Study: `Neo.component.wrapper.MonacoEditor`
352
+
353
+ The `MonacoEditor` component allows you to embed the powerful Monaco Editor (the code editor from VS
354
+ Code) into your Neo.mjs application. The Monaco Editor itself is a large, DOM-heavy library that
355
+ *must* run on the main thread.
356
+
357
+ Here's how the `MonacoEditor` component acts as a wrapper:
358
+
359
+ 1. **Encapsulation:** The component's `static config` exposes properties like `value`, `language`,
360
+ `readOnly`, and `editorTheme`. These are the properties a developer interacts with, not the
361
+ low-level Monaco Editor options.
362
+ 2. **Remote Method Calls:** Internally, the component's `afterSet` methods (e.g., `afterSetValue`,
363
+ `afterSetLanguage`) don't directly manipulate the editor. Instead, they make remote calls to the
364
+ `MonacoEditor` addon on the main thread:
365
+
366
+ ```javascript readonly
367
+ // Inside Neo.component.wrapper.MonacoEditor.mjs
368
+ afterSetValue(value, oldValue) {
369
+ let me = this;
370
+ if (me.mounted) { // Defensive check, though addon.Base handles queuing
371
+ Neo.main.addon.MonacoEditor.setValue({
372
+ id : me.id,
373
+ value : me.stringifyValue(me.value),
374
+ windowId: me.windowId
375
+ })
376
+ }
377
+ }
378
+ ```
379
+ 3. **Lifecycle Management:** The wrapper component also manages the addon's lifecycle from the
380
+ worker's perspective. For example, when the component is `mounted` (meaning its DOM element is
381
+ in the document), it tells the addon to create the editor instance:
382
+
383
+ ```javascript readonly
384
+ // Inside Neo.component.wrapper.MonacoEditor.mjs
385
+ afterSetMounted(value, oldValue) {
386
+ super.afterSetMounted(value, oldValue);
387
+ let me = this;
388
+ value && me.timeout(150).then(() => {
389
+ // This call will trigger the addon to create the Monaco Editor instance on the main thread
390
+ Neo.main.addon.MonacoEditor.createInstance(me.getInitialOptions()).then(() => {
391
+ me.onEditorMounted?.()
392
+ })
393
+ })
394
+ }
395
+ ```
396
+ 4. **Cleanup:** When the component is destroyed, it tells the addon to destroy the corresponding
397
+ editor instance on the main thread, preventing memory leaks.
398
+
399
+ This pattern ensures that your application code remains clean, declarative, and runs entirely within
400
+ the App Worker, while the complexities of main thread interaction are neatly encapsulated within the
401
+ component wrapper and its associated addon.
402
+
403
+ ## Advanced: Lazy Loading External Libraries with `loadFiles()`
404
+
405
+ For addons that depend on large, external JavaScript libraries (like a charting or mapping library),
406
+ you don't want to load that library until it's actually needed. The `Neo.main.addon.Base` class
407
+ provides a powerful mechanism for this: the `async loadFiles()` method.
408
+
409
+ 1. **Implement `loadFiles()`:** Place your library loading logic (e.g., dynamically injecting a
410
+ `<script>` tag) inside the `async loadFiles()` method in your addon. This method **must** return
411
+ a `Promise` that resolves when the library is fully loaded and ready.
412
+ 2. **Automatic Queuing via `interceptRemotes`:** For methods listed in an addon's `interceptRemotes`
413
+ config, the framework automatically handles queuing any remote method calls that arrive before
414
+ the addon's `isReady` property is `true`.
415
+
416
+ Here's a conceptual example:
417
+
418
+ ```javascript readonly
419
+ // Inside a hypothetical src/main/addon/ChartingLibrary.mjs
420
+ import Base from './Base.mjs';
421
+
422
+ class ChartingLibrary extends Base {
423
+ static config = {
424
+ className : 'Neo.main.addon.ChartingLibrary',
425
+ interceptRemotes: ['createChart'], // List methods to be automatically queued
426
+ remote : { app: ['createChart'] } // Exposes `createChart` as a remote method to the app worker
427
+ }
428
+
429
+ async loadFiles() {
430
+ // Dynamically load the external charting library script
431
+ await Neo.main.DomAccess.loadScript({
432
+ id : 'charting-lib-script',
433
+ src: 'https://example.com/charting-library.js'
434
+ });
435
+ // You might also need to wait for the library to initialize itself
436
+ // await new Promise(resolve => window.ExternalChartingLibrary.onReady(resolve));
437
+ }
438
+
439
+ createChart(opts) {
440
+ // This code will only run after the script has loaded
441
+ // and the addon is ready. The framework handles queuing automatically.
442
+ return window.ExternalChartingLibrary.create(opts.domId, opts.chartConfig);
443
+ }
444
+ }
445
+ ```
446
+
447
+ When a worker calls `Neo.main.addon.ChartingLibrary.createChart()` for the first time:
448
+ 1. The framework intercepts the call because `createChart` is in `interceptRemotes`.
449
+ 2. If the addon is not `isReady`, the call is automatically queued.
450
+ 3. `loadFiles()` is triggered (if not already running).
451
+ 4. Once `loadFiles()` resolves and the addon becomes `isReady`, the queued `createChart` call is
452
+ executed.
453
+ 5. The `Promise` back in the worker is resolved.
454
+
455
+ All subsequent calls will execute immediately, as the library will already be loaded. This powerful
456
+ feature ensures optimal performance by deferring the loading of heavy resources until they are
457
+ absolutely necessary.
458
+
459
+ ## Conclusion: Empowering Your Application with Main Thread Addons
460
+
461
+ Main Thread Addons are a cornerstone of Neo.mjs's multi-threaded architecture, providing a robust and
462
+ elegant solution for interacting with browser-specific APIs and third-party libraries. By offloading
463
+ these tasks to the main thread while keeping your core application logic in workers, Neo.mjs ensures
464
+ unparalleled responsiveness and performance.
465
+
466
+ This guide has explored the full lifecycle of addons, from their "semi-singleton" design that promotes
467
+ extensibility, to the sophisticated `initAsync` and `isReady` mechanisms that guarantee safe,
468
+ asynchronous initialization. You've seen how the framework seamlessly handles remote method calls,
469
+ queuing them when necessary, and how the component wrapper pattern provides a clean, declarative
470
+ interface for your application.
471
+
472
+ By leveraging Main Thread Addons, you can confidently integrate any browser-dependent functionality
473
+ into your Neo.mjs application, knowing that the framework is handling the complex inter-thread
474
+ communication and lifecycle management for you. This powerful pattern is key to building
475
+ high-performance, extensible, and truly modern web applications.
@@ -15,7 +15,7 @@ While 99% of Neo.mjs development happens at the Component Tree layer, creating c
15
15
  Neo.mjs VDom nodes are plain JavaScript objects that represent DOM elements.
16
16
  **Important**: VDom only contains structure, styling, content, and attributes - **never event listeners**.
17
17
 
18
- ```javascript
18
+ ```javascript readonly
19
19
  // Basic VDom node structure
20
20
  {
21
21
  tag : 'div', // HTML tag (default: 'div')
@@ -41,7 +41,7 @@ Neo.mjs VDom nodes are plain JavaScript objects that represent DOM elements.
41
41
 
42
42
  Components define their internal DOM structure via the `vdom` config:
43
43
 
44
- ```javascript
44
+ ```javascript readonly
45
45
  import Component from './src/component/Base.mjs';
46
46
 
47
47
  class CustomButton extends Component {
@@ -81,7 +81,7 @@ For a comprehensive deep dive into all aspects of DOM event handling in Neo.mjs
81
81
 
82
82
  Here's a simple example of how an event handler defined via `domListeners` would interact with a component's VDom:
83
83
 
84
- ```javascript
84
+ ```javascript readonly
85
85
  import Component from './src/component/Base.mjs';
86
86
  import VdomUtil from './src/util/Vdom.mjs'; // For accessing VDom nodes by flag
87
87
 
@@ -144,7 +144,7 @@ class InteractiveComponent extends Component {
144
144
 
145
145
  The typical way to sync VDom changes to the DOM is through the component's `update()` method:
146
146
 
147
- ```javascript
147
+ ```javascript readonly
148
148
  import Component from './src/component/Base.mjs'; // Required import
149
149
 
150
150
  class StandardComponent extends Component {
@@ -180,7 +180,7 @@ class StandardComponent extends Component {
180
180
  For performance-critical scenarios, you can bypass the VDom worker's diffing engine and send manually crafted deltas
181
181
  directly from the App Worker to the Main Thread. This offers precise control but requires careful manual delta construction.
182
182
 
183
- ```javascript
183
+ ```javascript readonly
184
184
  import Component from './src/component/Base.mjs'; // Required import
185
185
 
186
186
  class AdvancedComponent extends Component {
@@ -240,7 +240,7 @@ class AdvancedComponent extends Component {
240
240
 
241
241
  Flags provide efficient, direct access to specific VDom nodes within a component's `vdom` structure, avoiding the need for DOM queries.
242
242
 
243
- ```javascript
243
+ ```javascript readonly
244
244
  import Component from './src/component/Base.mjs';
245
245
  import VdomUtil from './src/util/Vdom.mjs'; // Required import for VdomUtil
246
246
  import NeoArray from './src/util/Array.mjs'; // Required import for NeoArray
@@ -304,7 +304,7 @@ class IconButton extends Component {
304
304
 
305
305
  Build VDom structures programmatically, often in response to data changes. This is common for lists or complex, data-driven UI fragments.
306
306
 
307
- ```javascript
307
+ ```javascript readonly
308
308
  import Component from './src/component/Base.mjs'; // Required import
309
309
 
310
310
  class DataList extends Component {
@@ -380,7 +380,7 @@ class DataList extends Component {
380
380
 
381
381
  For sophisticated UI patterns like 3D visualizations or complex dynamic layouts, you might imperatively calculate and apply VDom properties or even use `Neo.applyDeltas()` for maximum performance.
382
382
 
383
- ```javascript
383
+ ```javascript readonly
384
384
  import Component from './src/component/Base.mjs'; // Base component class
385
385
 
386
386
  class Helix extends Component {
@@ -452,7 +452,7 @@ class Helix extends Component {
452
452
 
453
453
  ### XSS Prevention
454
454
 
455
- ```javascript
455
+ ```javascript readonly
456
456
  import Component from './src/component/Base.mjs'; // Required import
457
457
  // import DOMPurify from 'dompurify'; // Example for external sanitization library
458
458
 
@@ -499,7 +499,7 @@ this.update();
499
499
 
500
500
  ### 1. Batch VDom Updates
501
501
 
502
- ```javascript
502
+ ```javascript readonly
503
503
  import Component from './src/component/Base.mjs'; // Required import
504
504
  import Neo from './src/Neo.mjs'; // Required import for Neo.applyDeltas
505
505
 
@@ -547,7 +547,7 @@ class PerformantComponent extends Component {
547
547
 
548
548
  ### 2. Efficient Event Delegation
549
549
 
550
- ```javascript
550
+ ```javascript readonly
551
551
  import Component from './src/component/Base.mjs'; // Required import
552
552
 
553
553
  class EfficientEventComponent extends Component {
@@ -595,7 +595,7 @@ super.construct(config);
595
595
 
596
596
  ### 3. Memory Management
597
597
 
598
- ```javascript
598
+ ```javascript readonly
599
599
  import Component from './src/component/Base.mjs'; // Required import
600
600
 
601
601
  class MemoryEfficientComponent extends Component {
@@ -632,7 +632,7 @@ class MemoryEfficientComponent extends Component {
632
632
 
633
633
  Dynamically show or hide VDom nodes by setting their `removeDom` property. This is efficient as the VDom node remains in the tree, but its corresponding DOM element is removed/added from the document flow by the framework.
634
634
 
635
- ```javascript
635
+ ```javascript readonly
636
636
  import Component from './src/component/Base.mjs'; // Required import
637
637
  import VdomUtil from './src/util/Vdom.mjs'; // Required import
638
638
 
@@ -673,7 +673,7 @@ class ConditionalComponent extends Component {
673
673
 
674
674
  Programmatically create and update lists of VDom nodes, typically from data. This approach is highly efficient as the VDom diffing engine optimizes the DOM updates.
675
675
 
676
- ```javascript
676
+ ```javascript readonly
677
677
  import Component from './src/component/Base.mjs'; // Required import
678
678
 
679
679
  class ListComponent extends Component {