Giter Club home page Giter Club logo

glimmer-next's Introduction

GXT Netlify Status

Philosopher’s stone, logo of PostCSS

GXT is a cutting-edge, compilable runtime environment designed as glimmer-vm alternative, showcasing the power and flexibility of modern web component development. This repo includes a live example of how GXT can be used in real-world applications, providing developers with a practical and interactive experience. Explore our sample at netlify.

Benefits

  • πŸ”₯ Hot Module Replacement (Reloading)
  • πŸŒ‘ Native shadow-dom support
  • βŒ› Async element destructors support
  • πŸ–₯️ Server Side Rendering
  • πŸ’§ Rehydration
  • πŸ”§ Ember Developer Tools support
  • πŸƒ Runtime code tree-shaking
  • πŸ“¦ Small Bundle Size
  • ✍️ Typed Templates with Glint
  • 🀝 Ember syntax compatibility
  • πŸš€ 40% performance improvement compared to GlimmerVM
  • πŸ’Ύ 2x less memory usage compared to GlimmerVM
  • 🧹 Template linting support via Ember Template Lint
  • βš›οΈ Built-in reactivity system

Development tools for VS Code

Quick Links

Component sample

Based on template imports RFC

import { RemoveIcon } from "./RemoveIcon.gts";
import type { Item } from "@/utils/data";
import { type Cell, cellFor, Component } from "@lifeart/gxt";

type RowArgs = {
  Args: {
    item: Item;
    selectedCell: Cell<number>;
    onRemove: (item: Item) => void;
  };
};

export class Row extends Component<RowArgs> {
  get labelCell() {
    return cellFor(this.args.item, "label");
  }
  get id() {
    return this.args.item.id;
  }
  get selected() {
    return this.args.selectedCell.value;
  }
  set selected(value: number) {
    this.args.selectedCell.value = value;
  }
  get isSelected() {
    return this.selected === this.id;
  }
  get className() {
    return this.isSelected ? "danger" : "";
  }
  onClick = () => {
    this.selected = this.isSelected ? 0 : this.id;
  };
  onClickRemove = (e: Event) => {
    this.args.onRemove(this.args.item);
  };
  <template>
    <tr class={{this.className}}>
      <td class="col-md-1">{{this.id}}</td>
      <td class="col-md-4">
        <a {{on "click" this.onClick}} data-test-select>{{this.labelCell}}</a>
      </td>
      <td class="col-md-1">
        <a {{on "click" this.onClickRemove}} data-test-remove>
          <RemoveIcon />
        </a>
      </td>
      <td class="col-md-6"></td>
    </tr>
  </template>
}

Key Features

Simple and Expressive Component Model

  • Component as Functions: Every component in gNext is a function, executed only once for efficiency and better performance.
  • Class based components: Class based components are supported as well.
  • Basic Glint Support: Integration with Glint for improved TypeScript support and developer experience.
  • Comprehensive Slot Support: Full support for different kinds of slots, including {{yield}}, enhancing the flexibility in component composition.
  • Modifiers and Helpers APIs: Modifiers for element-specific logic. Helpers for reusable logic across components.
  • Template Imports: Import templates from other files, enabling better code organization and reusability.
  • Template Compilation: Compile templates to JavaScript functions for improved performance and efficiency.
  • Opcodes tree-shaking: Opcodes tree-shaking for smaller bundle size. We don't include unused DOM and component, flow-control opcodes in the bundle.

Reactive Primitives

  • Mutable State with cell<T>: Use cell for creating reactive, mutable states. Updating and accessing cell values is straightforward and efficient.
  • Derived State with formula: Create derived states that automatically update when dependencies change, ensuring reactive and responsive UIs.
  • Support for destructors: Enables clean-up and resource management, preventing memory leaks.

Benefits and Use Cases

gNext serves as a powerful tool for web developers looking to harness the capabilities of Glimmer-VM in a real-world setting. Its benefits and use cases include:

  • Efficient DOM Rendering: Experience fast and efficient DOM updates and rendering, crucial for high-performance web applications.
  • Reactive State Management: Manage component states reactively, ensuring UIs are always up-to-date with the underlying data.
  • Enhanced Developer Experience: Enjoy a seamless development experience with features like TypeScript support, comprehensive API documentation, and easy-to-understand examples.
  • Flexible Component Composition: Leverage advanced component composition techniques to build complex UIs with ease.
  • Resource Management: Efficiently manage resources with destructors, preventing common issues like memory leaks.

gNext is not just a library; it's a gateway to building modern, efficient, and reactive web applications using Glimmer-VM. Whether you are building dynamic user interfaces, complex single-page applications, or just experimenting with new front-end technologies, gNext provides the tools and capabilities to bring your ideas to life.

Explore gNext and elevate your web development experience!

Notes

  • modifiers API:
function modifier(element: Element, ...args: Args) {
    return () => {
        // destructor
    }
}
  • helpers API:
function helper(...args: Args): string | boolean | number | null {
  // helper logic
  return 3 + 2;
}

Reactive primitives

  • @tracked - decorator to mark class property as reactive primitive. It's autotrack dependencies and update when any of them changed. Note, to use it you need to add import 'decorator-transforms/globals'; in top-level file.

  • cell<T>(value) - reactive primitive, for mutable state. We could update cel calling cell.update(value), to get cell value we could use cell.value.

  • formula(fn: () => unknown) - reactive primitive, for derived state.

formula could be used to create derived state from Cell's. It's autotrack dependencies and update when any of them changed.

scope function is used to suspend ts error about unused variables. It's not required for runtime, but required for ts compilation.

destructors supported.

import { registerDestructor, hbs, scope } from "@lifeart/gxt";

export function Icon() {
  registerDestructor(this, () => {
    console.log("destructor");
  });

  return hbs`<i class="glyphicon glyphicon-remove"></i>`;
}

Setup

Start project from this template: https://github.com/lifeart/template-gxt

or

pnpm create vite my-app --template vanilla-ts
pnpm install @lifeart/gxt

Edit vite.config.mts to import compiler:

import { defineConfig } from "vite";
import { compiler } from "@lifeart/gxt/compiler";

export default defineConfig(({ mode }) => ({
  plugins: [compiler(mode)],
}));

To render root component, use renderComponent function.

import { renderComponent } from "@lifeart/gxt";
import App from "./App.gts";

const Instance = renderComponent(
  new App().template(),
  document.getElementById("app"),
);

To destroy component, use destroyElement function.

import { destroyElement } from "@lifeart/gxt";

destroyElement(Instance);

glimmer-next's People

Contributors

lifeart avatar lolmaus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

glimmer-next's Issues

Support Svelte syntax

[compiler] reuse tags

If template has case like this:

{{#if this.name}}
   {{this.name}}
   {{format this.name}}
{{/if}}

We create 3 tags for same property this.name.
We could avoid it and create one tag.

We could do it in compile-time with let.
Or we could replace this. with custom proxy and have caching on this level.
In addition, we could transform {{this.name}} to {{get this "name"}} and use something like cellFor

glint

InputEvent, MouseEvent, SubmitEvent - ts (glint)

Ember.js compatibility

We could explore drop-in glimmer-vm replacement approach for glimmer-next. To achieve it, we need to build compatibility layer matching glimmer-vm API.

Here is a list of things to explore:

  • each-in iterator (implemented in ember side)
  • unless condition
  • if helper
  • unless helper
  • eq helper
  • fn helper
  • let block (let is arrow self execution function with declared vars)
  • tracked decorator
  • array helper
  • hash helper
  • component manager
  • modifier manager #25 #83
  • helper manager #25 #82
  • destroyables api #93

Here list of ~used glimmer parts in ember:

import { DEBUG } from '@glimmer/env';
import { isDestroyed, destroy, registerDestructor, associateDestroyableChild } from '@glimmer/destroyable';
import { getCustomTagFor, setInternalHelperManager, getInternalHelperManager, helperCapabilities, setHelperManager, capabilityFlagsFrom } from '@glimmer/manager';
import type { Tag, TagMeta } from '@glimmer/validator';
import { CONSTANT_TAG, consumeTag, dirtyTagFor, tagFor, untrack } from '@glimmer/validator';
import { CurriedType } from '@glimmer/vm';
import { programCompilationContext } from '@glimmer/opcode-compiler';
import { artifacts, RuntimeOpImpl } from '@glimmer/program';
import { createComputeRef, createConstRef, createPrimitiveRef, childRefFor, UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference';
import { EMPTY_ARRAY, unwrapTemplate } from '@glimmer/util';
import { reifyPositional } from '@glimmer/runtime';

gxt - untrack function

export function untrack(callback: () => void): void {
  const previousRenderingState = _isRendering;
  // copy changed items set
  setIsRendering(false);
  try {
    callback();
  } finally {
    setIsRendering(previousRenderingState);
    // clear changed cells set
  }
}

Update destructors flow

At the moment we bind destructors to DOM nodes related to it.
It's quite simple and usable approach, but problem with it that we need to walk on DOM list to cleanup stuff.

With updated flow we may figure out points of destruction, it's really few in app:

1.) App root
2.) List
3.) If

So, based on this approach we could have destructors stack, where every level is

WeakMap<ComponentInstance, Destructors>

type AppDestructors = WeakMap<ComponentInstance, Destructors>;
type ListDestructors = WeakMap<ComponentInstance, Destructors>;
type IfDestructors = WeakMap<ComponentInstance, Destructors>;

/*
 App
   `each`
       Row
         `if`
            Case
              Nested
                Child
*/

const stack = [
 AppDestructors<App>,
 ListDestructors<Row>,
 IfDestructors<Case | Nested | Child>
]

/* to destroy all `if` leaf, we need to iterate over `IfDestructors` and execute it. */

As benefit, this stack represents application structure :)
To destroy some leaf in this graph we need to pick all destructors from it's end and execute.

Before every component creation, we have to pick last element from destructors stack and push our component to it.
Question to solve - is how to deal with dynamic destructors, registered outside of rendering cycle. It may be a case where we have to bind component context to specific stack (or resolve it from parent).

Todos

  • add modifiers
  • add slots (default)
  • check input primitives (checked, etc)
  • split compilation flows for properties / attributes
  • add SubExpression composition

document destroy flow

Managing Component Lifecycles: Destruction and Cleanup

GXT utilizes a decentralized approach to component lifecycle management, particularly for destruction and cleanup. Instead of relying on a single, centralized mechanism, GXT leverages a combination of utility functions and a flexible destructor registration system to ensure proper resource release.

Component-Level Cleanup

While GXT doesn't enforce a specific destroy method on components, users have the flexibility to implement one manually if needed. This allows for custom cleanup logic tailored to the specific requirements of a component.

registerDestructor Function

The core of GXT's cleanup strategy lies in the registerDestructor function. This function allows you to associate cleanup functions with a component instance. These registered destructors will be automatically executed when the component is removed from the rendering tree.

This mechanism is crucial for tasks that need to be performed regardless of whether a component has a custom destroy method. Common use cases include:

  • Clearing intervals and timeouts: Stop any ongoing timers initiated by the component.
  • Removing event listeners: Detach event listeners added by the component to prevent memory leaks.
  • Cleaning up DOM references: Release references to DOM elements to allow for garbage collection.
  • Disposing of external resources: Release any resources held by the component, such as network connections or file handles.

runDestructors Function

The runDestructors function is responsible for iterating through the registered destructors for a component and executing them. This function is typically called during the cleanup process initiated by GXT's internal rendering logic.

destroyElement and destroyElementSync Functions

These functions handle the removal of DOM elements associated with components. They are used by runDestructors and other cleanup routines to ensure proper DOM cleanup.

Destroy Flow

The destroy flow in GXT follows these steps:

  1. Destruction Triggered: Component destruction is initiated by GXT's internal rendering logic, often due to route changes, user actions, or test cleanup.

  2. runDestructors Invocation: GXT's internal cleanup process calls runDestructors for the component being removed.

  3. Registered Destructor Execution: Each registered destructor function associated with the component is executed, performing its specific cleanup task.

  4. DOM Element Removal: The destroyElement or destroyElementSync functions are used to remove the component's associated DOM elements from the DOM tree.

This decentralized approach, centered around the registerDestructor function, provides a flexible and robust mechanism for managing component cleanup in GXT applications. By registering appropriate destructors, you can ensure your applications are efficient and free of memory leaks, even without explicitly defining a destroy method on every component.

Documentation

We need to improve documentation, maybe create more .md files, also, we need to specify list of extra features from:

#3

For example:

  • Hot module replacement (Reloading)
  • Native shadow-dom support
  • Async element destructors support (ember-inspector)
  • ServerSideRendering
  • Rehydration
  • Ember Developer Tools support
  • Multiple class fields support on component
  • Runtime code tree-shaking
  • etc

Any documentation platforms to handle it?
Maybe https://docfy.dev/?

Tests

Add tests for:

  • VM (attribute updated, property updated, textContent updated, class merged) #37
  • component, helper, modifier helpers tests #85
  • each inside each
  • DOM builders (we could create known html tags)
  • Runtime (tbd)
  • Cell (cell instances should work as primitives in templates (boolean,number,string)), could be used as attribute/property and condition (for if) and remains reactive #34
  • MergedCell should work as primitives in templates (boolean,number,string)), could be used as attribute/property and condition (for if) and remains reactive #34
  • effect (function should re-execute if related fields changes) #32
  • modifier (it should run before document in dom and execute destructors if component is removed) #33
  • formula (tbd) #34
  • slots (if no yeild - no slots, default slot tests and multiple slots tests) #31
  • conditional slots (slot with same name could be in different if conditions, we need to check how it's working) #35
  • @args (field types should match inputs - primitives, functions) #36
  • class-based component (tbd) #36
  • template-only component #36
  • function based component (tbd)
  • components with root node (tbd)
  • components without root node (tbd)
  • if opcode
  • each opcode (list is rendered, and keeping in sync ordering of items)
  • hbs compiler (tbd)
  • in-element should remove anchor comment if updated/removed (now it's appending to old one)

Explore

Build

  • add gjs/gts loader
  • explore hbs compilation in runtime

slot default is not set

telegram-cloud-photo-size-2-5339341718929137130-y
create component with default slot (it should be inside if, and hided by default)
invoke component without slot
toggle "if", check error

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.