|
| 1 | +# App Shell & MVVM: The DevIndex Architecture |
| 2 | + |
| 3 | +The DevIndex application is built with the Neo.mjs framework, which utilizes a revolutionary architecture: it runs almost entirely outside the browser's main thread. This guide details how the DevIndex frontend is structured to leverage this "Off-Main-Thread" paradigm, along with a hybrid approach to MVC and MVVM patterns. |
| 4 | + |
| 5 | +> **Explore the Source:** You can find the complete source code for the DevIndex application here: [GitHub: apps/devindex](https://github.com/neomjs/neo/tree/dev/apps/devindex). |
| 6 | +
|
| 7 | +## The "Fat Client" Philosophy |
| 8 | + |
| 9 | +Before diving into the components, it's crucial to understand the core philosophy of DevIndex: it is a pure **"Fat Client"**. |
| 10 | + |
| 11 | +Unlike traditional web apps that rely on a server for sorting, filtering, and pagination, DevIndex streams in a single `users.jsonl` file on startup. From that point forward, the client literally does *everything*. All 50,000+ developer records are held in memory within the App Worker. Every filter, every sort, and every view transition happens instantly on the client side without ever hitting a backend server again. |
| 12 | + |
| 13 | +*(For a deep dive into how this streaming works, see [The Backend Twist](#/learn/Backend)).* |
| 14 | + |
| 15 | +## The Minimal Main Thread |
| 16 | + |
| 17 | +If you inspect the source of the DevIndex application's entry point (`apps/devindex/index.html`), you will notice how remarkably empty it is: |
| 18 | + |
| 19 | +```html readonly |
| 20 | +<!DOCTYPE HTML> |
| 21 | +<html> |
| 22 | +<head> |
| 23 | + <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 24 | + <meta charset="UTF-8"> |
| 25 | + <title>DevIndex</title> |
| 26 | +</head> |
| 27 | +<body> |
| 28 | + <script src="../../src/MicroLoader.mjs" type="module"></script> |
| 29 | +</body> |
| 30 | +</html> |
| 31 | +``` |
| 32 | + |
| 33 | +The `<body>` tag is completely empty. The only script loaded is `MicroLoader.mjs`. |
| 34 | + |
| 35 | +The main thread of the browser is intentionally kept as idle as possible. It has three primary responsibilities: |
| 36 | +1. **Starting Workers**: Bootstrapping the Neo.mjs multi-worker environment. |
| 37 | +2. **Delegating UI Events**: Capturing native DOM events (like scroll or click), serializing them as JSON, and delegating them to the App Worker. |
| 38 | +3. **Applying Delta Updates**: Receiving minimal Virtual DOM diffs and applying them efficiently via the `domApiRenderer`. |
| 39 | + |
| 40 | +By keeping the main thread clear of application logic, data processing, and state management, the DevIndex application ensures the UI remains fluid and responsive, completely immune to "jank" even under heavy computational load. |
| 41 | + |
| 42 | +## The App Worker: Where DevIndex Lives |
| 43 | + |
| 44 | +After the MicroLoader boots up the framework, it spawns the application inside a dedicated background thread known as the **App Worker**. |
| 45 | + |
| 46 | +```mermaid |
| 47 | +graph TD |
| 48 | + subgraph Browser Engine |
| 49 | + Main(Main Thread<br/>DOM & Events) |
| 50 | + end |
| 51 | + |
| 52 | + subgraph Neo.mjs Workers |
| 53 | + App(App Worker<br/>Business Logic & State) |
| 54 | + VDom(VDom Worker<br/>Diffing & Deltas) |
| 55 | + end |
| 56 | +
|
| 57 | + Main -->|UI Events JSON| App |
| 58 | + App -->|VDOM JSON| VDom |
| 59 | + VDom -->|DOM Deltas| Main |
| 60 | +``` |
| 61 | + |
| 62 | +*(Note: For more on how other workers like the Canvas and Data workers fit in, see the [OffscreenCanvas & Rendering](#/learn/frontend/OffscreenCanvas) and [The 50k-Row Grid](#/learn/frontend/TheGrid) guides.)* |
| 63 | + |
| 64 | +The entry point for DevIndex in the App Worker is `app.mjs`: |
| 65 | + |
| 66 | +```javascript readonly |
| 67 | +import Overwrites from './Overwrites.mjs'; |
| 68 | +import Viewport from './view/Viewport.mjs'; |
| 69 | + |
| 70 | +export const onStart = () => Neo.app({ |
| 71 | + mainView: Viewport, |
| 72 | + name : 'DevIndex' |
| 73 | +}); |
| 74 | +``` |
| 75 | + |
| 76 | +This is where the magic happens. The entire application—every component, every piece of state, every network request—runs within this App Worker. |
| 77 | + |
| 78 | +## Viewport and Lazy Loading |
| 79 | + |
| 80 | +The root component of the application is the `Viewport` (`apps/devindex/view/Viewport.mjs`). It defines the declarative layout using Neo.mjs's JSON-based Virtual DOM blueprints. |
| 81 | + |
| 82 | +Crucially, DevIndex utilizes **lazy loading** for its main sections to keep the initial bundle size minimal: |
| 83 | + |
| 84 | +```javascript readonly |
| 85 | +items: [{ |
| 86 | + module: Header, |
| 87 | + flex : 'none' |
| 88 | +}, { |
| 89 | + ntype : 'container', |
| 90 | + flex : 1, |
| 91 | + layout : {ntype: 'card', activeIndex: null}, |
| 92 | + reference: 'main-content', |
| 93 | + items : [ |
| 94 | + {module: () => import('./home/MainContainer.mjs')}, |
| 95 | + {module: () => import('./learn/MainContainer.mjs')} |
| 96 | + ] |
| 97 | +}] |
| 98 | +``` |
| 99 | + |
| 100 | +Notice the `module: () => import(...)` syntax. The framework will only fetch and execute the code for the Home (Grid) or Learn (Documentation) sections when the routing logic dictates they should be visible. |
| 101 | + |
| 102 | +## A Hybrid Approach: MVC meets MVVM |
| 103 | + |
| 104 | +DevIndex organizes its codebase using a blend of classic Model-View-Controller (MVC) for its data layer and Model-View-ViewModel (MVVM) for its UI components. |
| 105 | + |
| 106 | +### 1. The MVC Foundation |
| 107 | + |
| 108 | +At the top level of the `apps/devindex` folder, you will find a traditional MVC directory structure: |
| 109 | +* **Model**: `model/Contributor.mjs` defines the schema and virtual fields for the developer data. |
| 110 | +* **Store**: `store/Contributors.mjs` acts as the collection manager, holding the 50,000+ records in memory. |
| 111 | +* **View**: Folders like `view/home/` contain the visual components (e.g., `GridContainer.mjs`). |
| 112 | + |
| 113 | +### 2. The MVVM UI Layer (Hierarchical Architecture) |
| 114 | + |
| 115 | +While the data layer leans MVC, the UI components utilize a powerful, instance-based MVVM pattern. Instead of global controllers or single-store monoliths, DevIndex attaches **State Providers** and **View Controllers** directly to specific container instances within the component tree. |
| 116 | + |
| 117 | +These providers and controllers form a **hierarchical chain**: |
| 118 | + |
| 119 | +* **Hierarchical State Providers (ViewModel)**: |
| 120 | + When a component requests data (e.g., `bind: { text: data => data.title }`), the framework searches up the component tree. If the current component's State Provider doesn't have the data, it checks the parent's State Provider, and so on. This creates a deeply nested chain of merged data access. The `ViewportStateProvider` holds global UI state, while the `MainContainerStateProvider` might hold the specific `Contributors` store. |
| 121 | + |
| 122 | +* **Hierarchical View Controllers**: |
| 123 | + Event handling works the same way. When a button is configured with a string-based listener (e.g., `handler: 'onButtonClick'`), the framework doesn't look in a global registry. It walks up the component tree to find the *closest* View Controller that implements the `onButtonClick` method. This elegantly encapsulates logic exactly where it's needed, preventing bloated, monolithic controllers. |
| 124 | + |
| 125 | +For a deeper dive into how these controllers manage filtering and global state, see the [State Management & Controls](#/learn/frontend/StateAndControls) guide. |
| 126 | + |
| 127 | +## Summary |
| 128 | + |
| 129 | +By running its entire architecture inside an App Worker, DevIndex achieves true concurrency. While the Main Thread paints, the App Worker handles state changes, lazy loads modules, and coordinates with other workers. Combined with a hierarchical MVVM approach and a "Fat Client" philosophy, this ensures that even when processing 50,000 developer records, the UI remains perfectly fluid and maintainable. |
0 commit comments