derbyjs/derby

repository·master·Indexed 26 days ago

https://github.com/derbyjs/derby

An MVC framework for building real-time, collaborative applications that run in both Node.js and browsers. It features isomorphic rendering, two-way data binding, and utilizes the Racer data synchronization engine for granular data propagation and offline support. The framework uses a component-based architecture consisting of views and controllers, providing built-in utilities for lifecycle management, function throttling, and debouncing.

Tokens
23.6K
Snippets
46
Records
150
Agent score
89%

What's inside derby

  1. Overview of the Derby MVC framework

    master

    Derby is an MVC framework designed for building real-time, collaborative applications that run in both Node.js and browsers. It utilizes a data synchronization engine called Racer to automatically sync data between browsers, servers, and databases.

    Key features include:

    • Granular Data Propagation: Models subscribe to specific objects, eliminating the need to manually define channels.
    • Offline Support: Racer provides built-in offline usage and conflict resolution.
    • Isomorphic Rendering: The same templates render on both server and client, ensuring fast initial loads and SEO friendliness.
    • Two-way Data Binding: Templates define bindings that instantly update the view when models change, and vice versa.
  2. Overview of DerbyJS capabilities

    master

    DerbyJS is a full-stack framework designed for modern web applications. It provides two core capabilities:

    1. Realtime collaboration: Automatically syncs data across clients and servers using ShareDB's operational transformation (OT) for JSON and text, which includes automatic conflict resolution.
    2. Isomorphic rendering: Templates are universal. They can be rendered in the browser using fast, native DOM methods, or on the server to produce HTML. This enables faster page loads, SEO support, and the ability to use the same templates for other HTML outputs like emails.
  3. Understand Racer Models in DerbyJS

    master

    DerbyJS uses Racer as its realtime model synchronization engine. Racer models represent data as a JSON object tree and provide several key capabilities:

    • Data Loading: Methods to load data into the model, including via database queries.
    • Accessors: Null-safe getter and mutator (setter) methods.
    • Reactive Functions: Automatically produce output data when input data changes (includes built-in filter and sort).
    • Data Change Events: Emits events when contents are updated, allowing for complex logic or view updates.

    Racer works on both the server and the browser, enabling realtime conflict resolution via ShareDB and Operational Transformation (OT).

  4. Understand Derby Components

    master

    Components are the fundamental building blocks of Derby applications. A component consists of a view (implemented as a Derby template) and a controller (implemented as a JavaScript class or constructor function).

    Key characteristics:

    • Lifecycle: Derby creates a new instance of the controller class every time the component view is rendered.
    • Reusability: Components act as modular UI pieces with clear inputs and outputs.
    • Isomorphic: They can be rendered on both the server and the client (static HTML, server-rendered dynamic apps, or client-rendered apps).
    • Encapsulation: Each component has its own scoped model in a unique namespace (acting as a ViewModel). Data or references to a parent component are passed in via view attributes.
  5. Use view namespaces for encapsulation

    master

    View names use colon (:) separated namespaces. Lookups are relative to the current namespace, allowing you to encapsulate sub-views within components or sections without naming conflicts. You can also use namespaces to override general views with more specific ones (similar to CSS specificity).

    Example of namespaced views:

    • <Title:>: A general view.
    • <about:Title:>: A more specific version for the about namespace.
    • <about:mission:Title:>: An even more specific version.
    <home:content:>
      ...
    
    <about:content:>
      ...
    
    <about:Body:>
      <!-- Outputs content for the about page -->
      <view is="content"></view>
  6. Use the `index` view name for namespace defaults

    master

    The name index can be used for a view that is returned when just the namespace name is referenced. This allows you to import a directory and have its index.html act as the primary view for that namespace.

    <!-- index.html -->
    <import: src="./home">
    
    <Body:>
      <view is="home"></view>
    <!-- home.html -->
    <index:>
      <h1
        <view is="message"></view>
      </h1>
    
    <message:>
      Hello!
  7. Build and view documentation with local Ruby

    master

    To build and preview the documentation locally using a Ruby installation, navigate to the docs directory, install the required gems via Bundler, and start the Jekyll development server. The server will auto-build the docs upon changes to source files, though a restart is required if _config.yml is modified.

    ```bash
    cd docs && bundle install
    bundle exec jekyll serve

    View the site at http://localhost:4000/derby/.

  8. Handle mutation errors globally on the root model

    master

    In frontend Derby applications, unhandled mutation errors are emitted as 'error' events on the root model. You can listen for these at the top level to display error messages or report them to error-tracking tools. Note that the 'ready' event is only emitted in the browser.

    // The 'ready' event is only emitted in the browser.
    app.on('ready', () => {
      app.model.on('error', (error) => {
        // Handle the error appropriately, such as displaying an error message
        // to the user and asking them to refresh.
        displayErrorMessage();
        // Report the error to your error-handling tool manually, or
        // just re-throw for reporting.
        throw error;
      });
    });
  9. Implement Server-side Rendering + Client-side Attachment

    master

    Derby optimizes perceived load time by rendering HTML on the server and then "attaching" client-side logic to the existing DOM nodes in the browser.

    To ensure successful attachment, your component code must be deterministic and follow these rules:

    Determinism Requirements

    • Avoid non-deterministic inputs: Do not rely on Date.now() or Math.random() during rendering. Instead, compute these values ahead of time and store them in the model on _session or _page so they match on both server and client.
    • Stable Sorting: Use stable comparison algorithms for sorting.
    • No Side Effects: Rendering components should not modify persistent state.

    HTML Template Requirements

    Derby requires that the HTML produced by your templates matches the resulting DOM exactly.

    • Valid HTML: Ensure templates are valid. For example, <p><div></div></p> is invalid because the <div> will force the <p> to close, changing the DOM structure.
    • Explicit Optional Tags: Always include optional tags (e.g., <tbody> in a <table>).
    • Explicit Closing Tags: All non-void elements must be explicitly closed (e.g., <li></li>).

    Validation Tip: To check if a template is safe, verify that setting its content via innerHTML and reading it back produces the exact same string.

    var html = '<p><div></div></p>';
    var div = document.createElement('div');
    div.innerHTML = html;
    html === div.innerHTML; // Must be true for Derby templates
  10. Call peer component methods using the `as=` attribute

    master

    You can connect events from one component to methods on another component or element by assigning the instance to a controller property using the as= HTML attribute. This allows you to reference the component instance in view expressions.

    Note: The page object is globally available on all controllers, allowing you to access components assigned to the page (e.g., page.flash).

    <!-- Assigning an instance to 'modal' -->
    <modal as="modal"></modal>
    <button on-click="modal.open()"></button>
    
    <!-- Accessing a component assigned to the global 'page' object -->
    <flash as="page.flash"></flash>
    <button on-click="page.flash.show('Clicked')"></button>
  11. Create a model via Express middleware

    master

    On the server, you can use backend.modelMiddleware() to automatically create a new empty model for every incoming Express request and attach it to req.model. This is the standard way to provide models to Derby application routes for server-side rendering.

    You can add custom middleware after modelMiddleware() to pre-populate the model with data (e.g., session information).

    // Middleware to add req.model on each request
    expressApp.use(backend.modelMiddleware());
    
    // Subsequent middleware can use the model
    expressApp.use((req, res, next) => {
      req.model.set('_session.userId', 'test-user');
      next();
    });
    
    // Derby application routes use req.model for rendering
    expressApp.use(derbyApp.router());
    // Middleware to add req.model on each request
    expressApp.use(backend.modelMiddleware());
    
    // Subsequent middleware can use the model
    expressApp.use((req, res, next) => {
      req.model.set('_session.userId', 'test-user');
      next();
    });
    
    // Derby application routes use req.model for rendering
    expressApp.use(derbyApp.router());