neo.mjs 10.2.1 → 10.3.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 (79) hide show
  1. package/.github/CONCEPT.md +2 -4
  2. package/.github/GETTING_STARTED.md +72 -51
  3. package/.github/RELEASE_NOTES/v10.3.0.md +54 -0
  4. package/.github/epic-string-based-templates.md +690 -0
  5. package/ServiceWorker.mjs +2 -2
  6. package/apps/covid/view/MainContainer.mjs +1 -1
  7. package/apps/covid/view/country/Table.mjs +1 -1
  8. package/apps/portal/index.html +1 -1
  9. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  10. package/apps/portal/view/learn/ContentComponent.mjs +1 -1
  11. package/apps/realworld/api/Base.mjs +2 -2
  12. package/apps/sharedcovid/view/MainContainer.mjs +1 -1
  13. package/apps/sharedcovid/view/MainContainerController.mjs +1 -1
  14. package/buildScripts/buildESModules.mjs +23 -75
  15. package/buildScripts/bundleParse5.mjs +27 -0
  16. package/buildScripts/util/astTemplateProcessor.mjs +210 -0
  17. package/buildScripts/util/templateBuildProcessor.mjs +331 -0
  18. package/buildScripts/util/vdomToString.mjs +46 -0
  19. package/buildScripts/webpack/development/webpack.config.appworker.mjs +11 -0
  20. package/buildScripts/webpack/loader/template-loader.mjs +21 -0
  21. package/buildScripts/webpack/production/webpack.config.appworker.mjs +11 -0
  22. package/examples/README.md +1 -1
  23. package/examples/component/wrapper/googleMaps/MarkerDialog.mjs +2 -2
  24. package/examples/form/field/email/MainContainer.mjs +0 -1
  25. package/examples/form/field/number/MainContainer.mjs +0 -1
  26. package/examples/form/field/picker/MainContainer.mjs +0 -1
  27. package/examples/form/field/time/MainContainer.mjs +0 -1
  28. package/examples/form/field/trigger/copyToClipboard/MainContainer.mjs +0 -1
  29. package/examples/form/field/url/MainContainer.mjs +0 -1
  30. package/examples/functional/nestedTemplateComponent/Component.mjs +100 -0
  31. package/examples/functional/nestedTemplateComponent/MainContainer.mjs +48 -0
  32. package/examples/functional/nestedTemplateComponent/app.mjs +6 -0
  33. package/examples/functional/nestedTemplateComponent/index.html +11 -0
  34. package/examples/functional/nestedTemplateComponent/neo-config.json +6 -0
  35. package/examples/functional/templateComponent/Component.mjs +61 -0
  36. package/examples/functional/templateComponent/MainContainer.mjs +48 -0
  37. package/examples/functional/templateComponent/app.mjs +6 -0
  38. package/examples/functional/templateComponent/index.html +11 -0
  39. package/examples/functional/templateComponent/neo-config.json +6 -0
  40. package/learn/gettingstarted/Setup.md +29 -12
  41. package/learn/guides/fundamentals/ApplicationBootstrap.md +2 -2
  42. package/learn/guides/fundamentals/InstanceLifecycle.md +5 -5
  43. package/learn/guides/uibuildingblocks/HtmlTemplates.md +191 -0
  44. package/learn/guides/uibuildingblocks/HtmlTemplatesUnderTheHood.md +156 -0
  45. package/learn/guides/uibuildingblocks/WorkingWithVDom.md +1 -1
  46. package/learn/tree.json +2 -0
  47. package/package.json +62 -56
  48. package/src/DefaultConfig.mjs +3 -3
  49. package/src/calendar/view/calendars/List.mjs +1 -1
  50. package/src/calendar/view/month/Component.mjs +1 -1
  51. package/src/calendar/view/week/Component.mjs +1 -1
  52. package/src/component/Abstract.mjs +1 -1
  53. package/src/component/Base.mjs +33 -27
  54. package/src/container/Base.mjs +5 -5
  55. package/src/controller/Application.mjs +5 -5
  56. package/src/dialog/Base.mjs +6 -6
  57. package/src/draggable/DragProxyComponent.mjs +4 -4
  58. package/src/form/field/ComboBox.mjs +1 -1
  59. package/src/functional/_export.mjs +2 -1
  60. package/src/functional/component/Base.mjs +142 -93
  61. package/src/functional/util/HtmlTemplateProcessor.mjs +243 -0
  62. package/src/functional/util/html.mjs +24 -67
  63. package/src/list/Base.mjs +2 -2
  64. package/src/manager/Toast.mjs +1 -1
  65. package/src/menu/List.mjs +1 -1
  66. package/src/mixin/VdomLifecycle.mjs +87 -90
  67. package/src/tab/Container.mjs +2 -2
  68. package/src/tooltip/Base.mjs +1 -1
  69. package/src/tree/Accordion.mjs +2 -2
  70. package/src/worker/App.mjs +7 -7
  71. package/test/components/files/component/Base.mjs +1 -1
  72. package/test/siesta/siesta.js +2 -0
  73. package/test/siesta/tests/classic/Button.mjs +5 -5
  74. package/test/siesta/tests/functional/Button.mjs +6 -6
  75. package/test/siesta/tests/functional/HtmlTemplateComponent.mjs +193 -33
  76. package/test/siesta/tests/functional/Parse5Processor.mjs +82 -0
  77. package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +5 -5
  78. package/.github/epic-functional-components.md +0 -498
  79. package/.github/ticket-asymmetric-vdom-updates.md +0 -122
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta charset="UTF-8">
6
+ <title>Neo Functional Nested Templates</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath" : "examples/functional/nestedTemplateComponent/app.mjs",
3
+ "basePath" : "../../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs"
6
+ }
@@ -0,0 +1,61 @@
1
+ import {defineComponent, html, useConfig, useEvent} from '../../../src/functional/_export.mjs';
2
+
3
+ export default defineComponent({
4
+ config: {
5
+ /**
6
+ * @member {String} className='Neo.examples.functional.templateComponent.Component'
7
+ */
8
+ className: 'Neo.examples.functional.templateComponent.Component',
9
+ /**
10
+ * This is the key to unlock the `Template literals` based syntax for VDOM.
11
+ * @member {Boolean} enableHtmlTemplates=true
12
+ * @reactive
13
+ */
14
+ enableHtmlTemplates: true,
15
+ /**
16
+ * @member {String} greeting_='Hello'
17
+ * @reactive
18
+ */
19
+ greeting_: 'Hello',
20
+ /**
21
+ * @member {String} jobTitle_='Neo.mjs Developer'
22
+ * @reactive
23
+ */
24
+ jobTitle_: 'Neo.mjs Developer'
25
+ },
26
+
27
+ render(config) {
28
+ const [isActive, setIsActive] = useConfig(true);
29
+
30
+ useEvent('click', () => setIsActive(prev => !prev));
31
+
32
+ const cardStyle = {
33
+ border : '1px solid #eee',
34
+ borderRadius: '8px',
35
+ padding : '16px',
36
+ textAlign : 'center',
37
+ cursor : 'pointer',
38
+ boxShadow : '0 2px 4px rgba(0,0,0,0.1)'
39
+ };
40
+
41
+ const statusStyle = {
42
+ display : 'inline-block',
43
+ padding : '4px 8px',
44
+ borderRadius : '12px',
45
+ color : 'white',
46
+ backgroundColor: isActive ? '#28a745' : '#dc3545',
47
+ fontSize : '12px',
48
+ marginTop : '10px'
49
+ };
50
+
51
+ return html`
52
+ <div style="${cardStyle}">
53
+ <h2>${config.greeting}, Neo!</h2>
54
+ <p>${config.jobTitle}</p>
55
+ <div style="${statusStyle}">
56
+ ${isActive ? 'Active' : 'Inactive'}
57
+ </div>
58
+ </div>
59
+ `
60
+ }
61
+ });
@@ -0,0 +1,48 @@
1
+ import ConfigurationViewport from '../../ConfigurationViewport.mjs';
2
+ import MyFunctionalComponent from './Component.mjs';
3
+ import TextField from '../../../src/form/field/Text.mjs';
4
+
5
+ /**
6
+ * @class Neo.examples.functional.templateComponent.MainContainer
7
+ * @extends Neo.examples.ConfigurationViewport
8
+ */
9
+ class MainContainer extends ConfigurationViewport {
10
+ static config = {
11
+ className : 'Neo.examples.functional.templateComponent.MainContainer',
12
+ configItemLabelWidth: 160,
13
+ configItemWidth : 280,
14
+ layout : {ntype: 'hbox', align: 'stretch'}
15
+ }
16
+
17
+ createConfigurationComponents() {
18
+ let me = this;
19
+
20
+ return [{
21
+ module : TextField,
22
+ clearable : true,
23
+ labelText : 'greeting',
24
+ listeners : {change: me.onConfigChange.bind(me, 'greeting')},
25
+ style : {marginTop: '10px'},
26
+ value : me.exampleComponent.greeting
27
+ }, {
28
+ module : TextField,
29
+ clearable : true,
30
+ labelText : 'jobTitle',
31
+ listeners : {change: me.onConfigChange.bind(me, 'jobTitle')},
32
+ style : {marginTop: '10px'},
33
+ value : me.exampleComponent.jobTitle
34
+ }]
35
+ }
36
+
37
+ /**
38
+ * @returns {Neo.functional.Component}
39
+ */
40
+ createExampleComponent() {
41
+ return {
42
+ module : MyFunctionalComponent,
43
+ greeting: 'Hi'
44
+ }
45
+ }
46
+ }
47
+
48
+ export default Neo.setupClass(MainContainer);
@@ -0,0 +1,6 @@
1
+ import MainContainer from './MainContainer.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: MainContainer,
5
+ name : 'Neo.examples.functional.templateComponent'
6
+ });
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta charset="UTF-8">
6
+ <title>Neo Functional Template Component</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath" : "examples/functional/templateComponent/app.mjs",
3
+ "basePath" : "../../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs"
6
+ }
@@ -1,24 +1,41 @@
1
1
  # Setup
2
2
 
3
- Setting up Neo.mjs is pretty easy &mdash; all you have to do is confirm that you have some required
4
- software then follow a single npx script.
3
+ Setting up a Neo.mjs project is straightforward. The recommended method is to use `npx neo-app`, a command-line tool that generates a ready-to-use workspace for you.
5
4
 
6
5
  ## Prerequisites
7
6
 
8
- Before you get started, Neo.mjs requires that you have a few of things installed:
7
+ Before you begin, ensure you have the following software installed:
9
8
 
10
- - npm & nodejs (see <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" target="_blank">https://docs.npmjs.com/downloading-and-installing-node-js-and-npm</a>)
11
- - npx (see <a href="https://www.npmjs.com/package/npx" target="_blank">https://www.npmjs.com/package/npx</a>)
9
+ - **Node.js and npm**: Required for running JavaScript projects and managing packages.
10
+ (see <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" target="_blank">https://docs.npmjs.com/downloading-and-installing-node-js-and-npm</a>)
11
+ - **npx**: Comes bundled with npm and is used to execute npm packages.
12
+ (see <a href="https://www.npmjs.com/package/npx" target="_blank">https://www.npmjs.com/package/npx</a>)
12
13
 
13
- ## Installation
14
+ ## Creating a Workspace
14
15
 
15
- Your neo.mjs work is done in a _workspace_, which is an npm project that includes the Neo.mjs package,
16
- and an `app` directory that holds your applications. To generate a Neo.mjs workspace follow these
17
- steps.
16
+ Your Neo.mjs work is done in a **workspace**, which is an npm project that includes the Neo.mjs framework as a dependency and an `apps` directory to hold your applications.
18
17
 
19
- Open a terminal window and navigate to an empty director on your computer.
20
- Then run the following command, choosing the defaults for each prompt.
18
+ To generate a new workspace, open a terminal window, navigate to an empty directory, and run the following command:
21
19
 
22
20
  `npx neo-app@latest`
23
21
 
24
- When you're finished you should have a Neo.mjs workspace, as described in the _Workspaces and Applications_ topic, which follows.
22
+ This script automates the entire setup process:
23
+ 1. It scaffolds the workspace directory structure.
24
+ 2. It creates a `package.json` file for your project.
25
+ 3. It installs all necessary dependencies (`npm install`).
26
+ 4. It runs the initial framework builds (`npm run build-all`).
27
+ 5. It starts the development server for you (`npm run server-start`).
28
+
29
+ When you're finished, you will have a complete Neo.mjs workspace, as described in the _Workspaces and Applications_ topic, which follows.
30
+
31
+ ## Understanding the Four Environments
32
+
33
+ A unique advantage of Neo.mjs is its support for four distinct environments, allowing you to switch between a rapid, zero-builds development workflow and highly optimized deployment builds. Understanding these environments is key to leveraging the full power of the framework.
34
+
35
+ - **Development Mode**: No builds needed. Edit your code and see changes instantly in the browser. This is your primary environment for building and debugging.
36
+ - **dist/esm**: A modern deployment target that ships your application as native ES modules, preserving the file structure for easier debugging in production.
37
+ - **dist/production**: Creates highly optimized, minified bundles for each framework thread, ensuring the smallest possible footprint for deployment.
38
+ - **dist/development**: A bundled, non-minified version with source maps, useful for debugging issues that might only appear in a bundled environment.
39
+
40
+ For a detailed explanation of each environment and how they work together, please read our comprehensive guide:
41
+ **[Learn more: The 4 Environments](../benefits/FourEnvironments.md)**
@@ -1,7 +1,7 @@
1
1
  # Neo.mjs Application Bootstrap Process
2
2
 
3
3
  This guide explains how Neo.mjs applications start, initialize, and come to life - from the initial HTML file to your
4
- first rendered component.
4
+ first mounted component.
5
5
 
6
6
  ## Overview
7
7
 
@@ -322,7 +322,7 @@ The component instantiation process:
322
322
  3. Event listeners are attached via the framework's event system
323
323
  4. Data bindings are established for reactive updates
324
324
 
325
- ### 9. VDom Generation and Initial Render
325
+ ### 9. VDom Generation and Initial VNode Initialization
326
326
 
327
327
  Once the component tree is built:
328
328
 
@@ -8,7 +8,7 @@ This guide will walk you through the entire lifecycle, which can be broken down
8
8
 
9
9
  1. **Synchronous Creation**: The initial, synchronous setup of the instance and its configuration.
10
10
  2. **Asynchronous Initialization**: An optional phase for asynchronous tasks like data fetching.
11
- 3. **Mounting & Unmounting**: The dynamic phase where a component is rendered into the DOM, and potentially unmounted
11
+ 3. **Mounting & Unmounting**: The dynamic phase where a component is initialized and mounted into the DOM, and potentially unmounted
12
12
  and re-mounted.
13
13
  4. **Destruction**: The final cleanup phase.
14
14
 
@@ -29,7 +29,7 @@ instance. Always let the framework handle instantiation.
29
29
 
30
30
  When the framework creates a new instance, it executes a sequence of synchronous methods. This initial phase is
31
31
  responsible for setting up the instance's basic configuration and state. **At this stage, the component has not been
32
- rendered to the DOM.**
32
+ mounted to the DOM.**
33
33
 
34
34
  The synchronous lifecycle methods are called in the following order:
35
35
 
@@ -43,7 +43,7 @@ The synchronous lifecycle methods are called in the following order:
43
43
 
44
44
  3. **`onConstructed()`**: This hook is called immediately after `construct()` has finished. It's the ideal place to
45
45
  perform any setup that depends on the initial configuration. **Crucially, do not attempt to access the DOM here**,
46
- as the component is not yet rendered.
46
+ as the component is not yet mounted.
47
47
 
48
48
  4. **`onAfterConstructed()`**: This hook is called after `onConstructed()`. It provides another opportunity for setup logic.
49
49
 
@@ -158,10 +158,10 @@ multiple times, even into different browser windows, all while preserving its in
158
158
  ### `mounted`: The DOM-Ready Signal
159
159
 
160
160
  The `mounted_` config is the key to this phase. It is a boolean flag that indicates whether the component is currently
161
- rendered in the DOM.
161
+ painted in the DOM.
162
162
 
163
163
  * **`afterSetMounted(isMounted, wasMounted)`**: This is the most important hook for DOM interaction. It is called with
164
- * `true` when the component's VDOM is successfully rendered into the DOM, and with `false` when it is removed.
164
+ * `true` when the component's VDOM is successfully initialized and mounted into the DOM, and with `false` when it is removed.
165
165
 
166
166
  **This is the only safe and reliable place to perform DOM measurements or manipulations.**
167
167
 
@@ -0,0 +1,191 @@
1
+ # Using HTML Templates for VDOM
2
+
3
+ This guide covers the purpose, syntax, and trade-offs of using tagged template literals (HTML-like syntax) to define VDOM structures in neo.mjs. This feature provides an intuitive and familiar alternative to the traditional JSON-based VDOM approach, especially for developers coming from other traditional, single-threaded frameworks.
4
+
5
+ ## The "Why": An Alternative, Not a Replacement
6
+
7
+ The core of neo.mjs is built on a highly efficient, JSON-based VDOM structure. This approach is powerful and performant, as it requires zero parsing or transformation at runtime.
8
+
9
+ However, we recognize that many developers are accustomed to writing UI with an HTML-like syntax. HTML templates are offered as a **beginner-friendly and transitional option** to lower the barrier to entry. They allow you to get started quickly, leveraging familiar patterns, while you learn the more advanced capabilities of the framework.
10
+
11
+ ## The Trade-Off: Performance in Development
12
+
13
+ Using this feature comes with a clear trade-off. To enable live parsing of HTML templates in your browser during development, the framework must load the `parse5` library. This adds **~176KB (minified)** to your application's initial download size in development mode.
14
+
15
+ For production builds, this penalty is removed, as the templates are pre-compiled into the standard JSON VDOM format.
16
+
17
+ ## Best Practices
18
+
19
+ - **Use for simpler components:** Templates are excellent for components with relatively static structures, such as informational dialogs, buttons, or basic forms.
20
+ - **Prefer JSON VDOM for complex views:** For highly dynamic, data-driven, or programmatically generated views (like complex grids or dashboards), the native JSON VDOM approach is often clearer and more performant.
21
+ - **Embrace JavaScript for Logic:** Do not look for template-specific directives like `n-if` or `n-for`. All conditional logic and looping should be handled with standard JavaScript inside your `createVdom` method, before the `html` tag is even used.
22
+
23
+ ## For Developers Coming from JSX (e.g., React)
24
+
25
+ You may be accustomed to writing component tags directly, like `<Button>`. In neo.mjs, the equivalent is `<${Button}>`. This small difference is intentional and unlocks significant architectural benefits.
26
+
27
+ - **JSX Requires a Compiler:** JSX is not standard JavaScript. It must be compiled into `React.createElement(Button, ...)` calls. The simplicity of `<Button>` is an abstraction provided by a mandatory build step.
28
+ - **neo.mjs Templates are Native JavaScript:** The `html`...`` syntax is a standard JavaScript feature called a "tagged template literal." It runs directly in the browser without a build step in development. The `<${Button}>` syntax is the native JavaScript way to pass the actual `Button` component constructor (the variable) into the template function.
29
+
30
+ This approach means you are not learning a special template language; you are using the full power of JavaScript itself for all your view logic, including conditionals and loops, which is a core design principle of the framework.
31
+
32
+ ## Basic Usage
33
+
34
+ To use this feature, import the `html` tag from `neo.mjs/src/functional/util/html.mjs` and use it to wrap your template string.
35
+
36
+ ```javascript
37
+ import { html } from '../../../src/functional/util/html.mjs';
38
+
39
+ const vdom = html`
40
+ <div class="container">
41
+ <h1>Hello, World!</h1>
42
+ </div>
43
+ `;
44
+ ```
45
+
46
+ ## Component vs. HTML Tags
47
+
48
+ The template processor distinguishes between standard HTML elements and neo.mjs components based on the tag itself.
49
+
50
+ ### 1. HTML Tags
51
+
52
+ Standard HTML tags are written as lowercase literal strings, as you would in normal HTML.
53
+
54
+ ```javascript
55
+ const vdom = html`
56
+ <section>
57
+ <p>This is a standard HTML paragraph.</p>
58
+ </section>
59
+ `;
60
+ ```
61
+
62
+ ### 2. Component Tags
63
+
64
+ There are two ways to render a neo.mjs component, with the first being the strongly recommended approach.
65
+
66
+ #### Recommended: Interpolation (Lexical Scope)
67
+
68
+ Import the component you need and pass the constructor directly into the template as the tag name using `${...}` syntax. This is the most explicit and robust method, as it uses the file's lexical scope.
69
+
70
+ **Note:** Components can and should be self-closing if they do not have children.
71
+
72
+ ```javascript
73
+ import { html } from '../../../src/functional/util/html.mjs';
74
+ import Button from '../../../src/button/Base.mjs';
75
+
76
+ const vdom = html`<${Button} text="Click Me" />`;
77
+ ```
78
+
79
+ #### Fallback: Global Namespace
80
+
81
+ If a tag name is written as a literal string in `PascalCase` or with dots (e.g., `Neo.button.Base` or `MyApp.view.MyButton`), the processor will attempt to resolve it from the global namespace using the `Neo.ns()` utility. This should be used sparingly, as the interpolation method is more explicit and less prone to issues with name collisions or refactoring.
82
+
83
+ ```javascript
84
+ const vdom = html`<Neo.button.Base text="Global Button" />`;
85
+ ```
86
+
87
+ ## Attributes and Configs
88
+
89
+ Attributes are used to pass configuration values to both HTML tags and components.
90
+
91
+ ### Primitives (String, Number, Boolean)
92
+
93
+ Static strings can be passed directly. For dynamic values (including numbers, booleans, or variables), use interpolation.
94
+
95
+ ```javascript
96
+ const myId = 'main-container';
97
+ const disabled = true;
98
+
99
+ const vdom = html`
100
+ <div id="${myId}" class="static-class"></div>
101
+ <${Button} text="Submit" disabled="${disabled}" />
102
+ `;
103
+ ```
104
+
105
+ ### Objects, Arrays, and Functions
106
+
107
+ To pass non-string data like objects (for styles), arrays (for child items), or functions (for renderers or handlers), you **must** use interpolation.
108
+
109
+ ```javascript
110
+ import { html } from '../../../src/functional/util/html.mjs';
111
+ import Grid from '../../../src/grid/Container.mjs';
112
+
113
+ const myStyle = { color: 'blue', fontSize: '16px' };
114
+
115
+ // A renderer function for a grid column
116
+ const statusRenderer = ({ value }) => {
117
+ const color = value === 'Active' ? 'green' : 'red';
118
+ // A renderer can return a VDOM object for rich cell content
119
+ return { tag: 'span', style: { color }, cn: [value] };
120
+ };
121
+
122
+ // The array of column configuration objects
123
+ const gridColumns = [
124
+ { dataField: 'name', text: 'Name' },
125
+ { dataField: 'status', text: 'Status', renderer: statusRenderer } // The function is passed here
126
+ ];
127
+
128
+ const vdom = html`
129
+ <div style="${myStyle}">Styled Text</div>
130
+ <${Grid} columns="${gridColumns}" />
131
+ `;
132
+ ```
133
+
134
+ ## Dynamic and Conditional Rendering
135
+
136
+ Unlike other frameworks, neo.mjs templates do not have special directives for logic. Instead, you use the full power of JavaScript to build your dynamic UI *before* it goes into the template.
137
+
138
+ ### Conditional Rendering
139
+
140
+ Use standard JavaScript constructs like `if/else` statements, ternary operators, or `&&` for conditional rendering.
141
+
142
+ ```javascript
143
+ // Using a ternary operator
144
+ const content = isVisible
145
+ ? html`<p>Now you see me.</p>`
146
+ : html`<small>Now you don't.</small>`;
147
+
148
+ const vdom = html`
149
+ <div>
150
+ <h1>Conditional Content</h1>
151
+ ${content}
152
+ </div>
153
+ `;
154
+
155
+ // Using short-circuiting (&&)
156
+ const vdom2 = html`
157
+ <div>
158
+ <h2>Admin Section</h2>
159
+ ${isAdmin && html`<p>Welcome, Admin!</p>`}
160
+ </div>
161
+ `;
162
+ ```
163
+
164
+ ### Rendering Lists
165
+
166
+ Use the standard `Array.prototype.map()` method to transform an array of data into an array of VDOM nodes.
167
+
168
+ ```javascript
169
+ const items = [
170
+ { id: 1, name: 'First Item' },
171
+ { id: 2, name: 'Second Item' }
172
+ ];
173
+
174
+ const listItems = items.map(item => html`
175
+ <li id="${item.id}">${item.name}</li>
176
+ `);
177
+
178
+ const vdom = html`
179
+ <ul>
180
+ ${listItems}
181
+ </ul>
182
+ `;
183
+ ```
184
+
185
+ **IMPORTANT:** When rendering lists, always provide a unique `id` attribute to each element in the list. This is crucial for the VDOM diffing algorithm to efficiently update, reorder, or remove items.
186
+
187
+ ## DOM Events (Out of Scope)
188
+
189
+ This template system **does not** support inline DOM event handlers (e.g., `onClick="..."`).
190
+
191
+ To handle DOM events, you must continue to use the framework's standard, efficient, and secure delegated event system via the `domListeners` config on class-based components or the `useEvent()` hook in functional components. This maintains architectural consistency and performance.
@@ -0,0 +1,156 @@
1
+ # Under the Hood: The Philosophy and Mechanics of HTML Templates
2
+
3
+ This guide explores the "why" and "how" behind the HTML template feature in Neo.mjs. It's a deep dive into an architecture
4
+ designed to deliver on a core framework promise: a zero-builds, instant-feedback development mode that doesn't sacrifice
5
+ production performance.
6
+
7
+ This serves as a companion to the [Using HTML Templates](./HtmlTemplates.md) guide, which focuses on syntax
8
+ and best practices.
9
+
10
+ ## The Core Philosophy: Why Not JSX?
11
+
12
+ One of the most critical design goals for Neo.mjs is to provide a **zero-builds development environment**. We believe
13
+ that developers should be able to write code and see their changes instantly in the browser, without a mandatory
14
+ compilation step. This philosophy directly informed our approach to UI templating.
15
+
16
+ Frameworks like React and Angular rely on non-standard syntax (JSX, Angular templates) that **must** be compiled into
17
+ valid JavaScript. This requirement for a build step, even in development, introduces complexity and slows down
18
+ the feedback loop.
19
+
20
+ Neo.mjs chose a different path: **Tagged Template Literals**. This is a standard, native JavaScript feature. By using
21
+ `html`... ``, we are not inventing a new language; we are leveraging the power of JavaScript itself. This allows for:
22
+
23
+ 1. **True Zero-Builds Development:** Your template code runs directly in the browser. What you write is what you get,
24
+ with no hidden magic or required transformations.
25
+ 2. **No Special Directives:** Logic isn't handled by template-specific directives like `n-if` or `n-for`. You use
26
+ standard JavaScript (`if/else`, `map()`) for all conditionals and loops, which is more powerful and familiar.
27
+ 3. **Architectural Purity:** The template is just a function call that returns a data structure (a VDOM object).
28
+ This maintains a clean separation between your view definition and the framework's rendering engine.
29
+
30
+ ## Mechanism 1: The Zero-Builds Development Experience
31
+
32
+ In development mode, templates must be parsed at runtime. This is where the trade-off for instant feedback becomes
33
+ apparent.
34
+
35
+ ### Conditional Loading: A Smart Optimization
36
+
37
+ To parse HTML strings, we need an HTML parser. Neo.mjs uses `parse5`, a robust and spec-compliant library. However, at
38
+ ~176KB, we don't want to load it unless absolutely necessary.
39
+
40
+ This is why the parser is **only loaded if a component on the page actually uses an HTML template**. This check happens
41
+ inside the `initAsync` method of `Neo.functional.component.Base`.
42
+
43
+ ```javascript
44
+ // src/functional/component/Base.mjs
45
+ async initAsync() {
46
+ await super.initAsync();
47
+
48
+ if (this.enableHtmlTemplates && Neo.config.environment === 'development') {
49
+ if (!Neo.ns('Neo.functional.util.HtmlTemplateProcessor')) {
50
+ const module = await import('../util/HtmlTemplateProcessor.mjs');
51
+ this.htmlTemplateProcessor = module.default
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ If `enableHtmlTemplates` is true, the component dynamically imports the `HtmlTemplateProcessor`, which in turn pulls in
58
+ `parse5`. This ensures that applications not using this feature pay no performance penalty.
59
+
60
+ ### The Runtime Parsing Process
61
+
62
+ When a component's `createVdom()` method returns an `HtmlTemplate` object, it's handed off to the `HtmlTemplateProcessor`.
63
+ You can inspect its source code here:
64
+ [src/functional/util/HtmlTemplateProcessor.mjs](../../../../src/functional/util/HtmlTemplateProcessor.mjs).
65
+
66
+ The processor executes a series of steps to convert the template literal into a VDOM object, which are detailed in the
67
+ expandable section below.
68
+
69
+ <details>
70
+ <summary>Detailed Runtime Parsing Steps</summary>
71
+
72
+ 1. **Flattening:** It recursively flattens any nested templates into a single string and a corresponding array of
73
+ dynamic values.
74
+ 2. **Placeholder Injection:** It replaces dynamic values (e.g., event handlers, component configs, other components)
75
+ with special placeholders in the string (e.g., `__DYNAMIC_VALUE_0__`, `neotag1`).
76
+ 3. **Self-Closing Tag Conversion:** Since `parse5` does not handle self-closing custom element tags, a regular
77
+ expression adds explicit closing tags (e.g., `<MyComponent />` becomes `<MyComponent></MyComponent>`).
78
+ 4. **Parsing:** The sanitized HTML string is parsed into a standard AST using `parse5.parseFragment()`.
79
+ 5. **VDOM Conversion:** The processor traverses the `parse5` AST and converts it back into a Neo.mjs VDOM object.
80
+ During this process, it re-inserts the original dynamic values from the `values` array, preserving their rich data
81
+ types (functions, objects, etc.). It also carefully reconstructs the original case-sensitive tag names for custom
82
+ components.
83
+
84
+ </details>
85
+
86
+ Once the VDOM is constructed, it's passed back to the component's `continueUpdateWithVdom()` method, and the standard
87
+ rendering lifecycle proceeds.
88
+
89
+ ## Mechanism 2: Maximum Performance for Production
90
+
91
+ For production, the goal is to achieve the exact same VDOM output as the development mode, but with **zero runtime
92
+ parsing overhead**. This is accomplished with a powerful build-time AST (Abstract Syntax Tree) transformation.
93
+
94
+ This work is handled by two main scripts:
95
+
96
+ - [buildScripts/util/templateBuildProcessor.mjs](../../../../buildScripts/util/templateBuildProcessor.mjs):
97
+ Contains the core logic for parsing the template string and converting it to a serializable VDOM object.
98
+ - [buildScripts/util/astTemplateProcessor.mjs](../../../../buildScripts/util/astTemplateProcessor.mjs):
99
+ Orchestrates the overall process of reading a JS file, finding `html` templates, and replacing them with the final
100
+ VDOM object via AST manipulation.
101
+
102
+ ### The AST Transformation Process
103
+
104
+ The `astTemplateProcessor.mjs` script is a marvel of build-time engineering. Instead of just doing a simple text
105
+ replacement, it performs a full AST transformation to ensure 100% accuracy.
106
+
107
+ 1. **Parse Code:** It uses `acorn` to parse the JavaScript file content into an AST.
108
+ 2. **Find Templates:** It traverses the AST to find all `html` tagged template expressions.
109
+ 3. **Process Template:** Each template is processed by `templateBuildProcessor.mjs`, which converts the HTML-like syntax
110
+ into a serializable VDOM object.
111
+ 4. **Convert to AST:** The resulting VDOM object is converted back into a valid AST `ObjectExpression` node.
112
+ 5. **Replace Node:** The original `TaggedTemplateExpression` is replaced in the main AST with the new `ObjectExpression`.
113
+ 6. **Generate Code:** The modified AST is converted back into a string of JavaScript code using `astring`.
114
+
115
+ As a developer convenience, if a template is the return value of a method named `render`, the build script automatically
116
+ renames the method to `createVdom`.
117
+
118
+ ### Integration with Build Environments
119
+
120
+ This logic is seamlessly integrated into all three of Neo.mjs's production build environments:
121
+
122
+ - **`dist/esm`:** The [buildScripts/buildESModules.mjs](../../../../buildScripts/buildESModules.mjs) script directly
123
+ invokes the `processFileContent` function from the `astTemplateProcessor` for each JavaScript file before minification.
124
+ - **`dist/dev` & `dist/prod`:** These environments use Webpack. The transformation is handled by a custom loader:
125
+ [buildScripts/webpack/loader/template-loader.mjs](../../../../buildScripts/webpack/loader/template-loader.mjs).
126
+ This loader is strategically applied **only to the App worker's build configuration**, an optimization that saves
127
+ build time by not processing code for other workers.
128
+
129
+ ### Key Differences from Runtime Parsing
130
+
131
+ The build-time process is fundamentally different from the runtime parsing:
132
+
133
+ - **No Lexical Scope:** The build script cannot access runtime variables like `this`. It captures the raw code (e.g.,
134
+ `this.name`) as a string.
135
+ - **Placeholder Wrapping:** These code strings are wrapped in special placeholders (e.g.,
136
+ `##__NEO_EXPR__this.name##__NEO_EXPR__##`).
137
+ - **Custom Resolution:** During the VDOM-to-AST conversion, the `jsonToAst` function uses `acorn.parseExpressionAt`
138
+ to parse these placeholders back into proper AST nodes, perfectly preserving the original expressions for runtime
139
+ evaluation.
140
+ - **Component Tag Handling:** A tag like `<MyComponent>` is converted into a placeholder object
141
+ (`{ __neo_component_name__: 'MyComponent' }`) which the `astTemplateProcessor` turns into a plain `Identifier` in
142
+ the final AST.
143
+
144
+ ## Conclusion: The Neo.mjs Advantage
145
+
146
+ The dual-mode approach to HTML templates is a perfect example of the Neo.mjs philosophy in action. It provides:
147
+
148
+ - **An Unmatched Developer Experience:** The zero-builds development mode offers an instant feedback loop that is
149
+ impossible in frameworks requiring mandatory compilation. You write standard JavaScript and it simply works.
150
+ - **Maximum Production Performance:** The build-time AST transformation ensures that your production code is as fast
151
+ as possible, with no client-side parsing overhead. The `parse5` library is completely eliminated from your final bundle.
152
+ - **Architectural Consistency:** The system is designed to produce the exact same VDOM structure in both modes.
153
+ This eliminates a whole class of bugs where development and production environments behave differently.
154
+
155
+ This architecture isn't just a feature; it's a statement. It demonstrates a commitment to web standards, developer
156
+ productivity, and end-user performance that sets Neo.mjs apart from the crowd.
@@ -355,7 +355,7 @@ class DataList extends Component {
355
355
  this.update(); // Trigger VDom reconciliation
356
356
  }
357
357
 
358
- // Automatically re-render list when 'data' config changes
358
+ // Automatically update list VDom when 'data' config changes
359
359
  afterSetData(value, oldValue) {
360
360
  if (value) { // Only create items if data is set
361
361
  this.createListItems();
package/learn/tree.json CHANGED
@@ -43,6 +43,8 @@
43
43
  {"name": "Layouts", "parentId": "guides/uibuildingblocks", "id": "guides/uibuildingblocks/Layouts"},
44
44
  {"name": "Custom Components", "parentId": "guides/uibuildingblocks", "id": "guides/uibuildingblocks/CustomComponents"},
45
45
  {"name": "Working with VDom", "parentId": "guides/uibuildingblocks", "id": "guides/uibuildingblocks/WorkingWithVDom"},
46
+ {"name": "HTML Templates", "parentId": "guides/uibuildingblocks", "id": "guides/uibuildingblocks/HtmlTemplates"},
47
+ {"name": "Under the Hood: HTML Templates", "parentId": "guides/uibuildingblocks", "id": "guides/uibuildingblocks/HtmlTemplatesUnderTheHood"},
46
48
  {"name": "Data Handling", "parentId": "guides", "isLeaf": false, "id": "guides/datahandling", "collapsed": true},
47
49
  {"name": "Collections", "parentId": "guides/datahandling", "id": "guides/datahandling/Collections"},
48
50
  {"name": "Records", "parentId": "guides/datahandling", "id": "guides/datahandling/Records"},