惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
Google Developers Blog
S
Schneier on Security
The Hacker News
The Hacker News
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
I
Intezer
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
Security Latest
Security Latest
AWS News Blog
AWS News Blog
B
Blog RSS Feed
Microsoft Security Blog
Microsoft Security Blog
有赞技术团队
有赞技术团队
博客园 - 叶小钗
The Last Watchdog
The Last Watchdog
O
OpenAI News
月光博客
月光博客
Hacker News: Ask HN
Hacker News: Ask HN
阮一峰的网络日志
阮一峰的网络日志
S
Security @ Cisco Blogs
Google Online Security Blog
Google Online Security Blog
云风的 BLOG
云风的 BLOG
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Latest news
Latest news
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
Google DeepMind News
Google DeepMind News
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
U
Unit 42
PCI Perspectives
PCI Perspectives
博客园 - 聂微东
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
博客园 - 三生石上(FineUI控件)
Application and Cybersecurity Blog
Application and Cybersecurity Blog
罗磊的独立博客
WordPress大学
WordPress大学
D
Darknet – Hacking Tools, Hacker News & Cyber Security

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Boost the Performance of an AngularJS Application Using Immutable Data Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
Reactive framework in ~200 lines of JavaScript
2025-01-09 · via Minko Gechev's blog

One of my current projects is converging Angular and Wiz into the same framework. This is a complex projects that involves a lot of work and many people. It also got me thinking about different client-side rendering models. In this blog post I’ll show a very simple library that enables you to develop components with fine-grained reactivity. To make it easier to talk about this library, I called it “revolt.”

You can find the implementation in my repo on GitHub.

This prototype is just meant to be a fun experiment and nothing more!

Component model

Each component in revolt is a function which returns a view. The view is represented by nested objects which correspond to DOM elements and text nodes that we’d render on the page. For each DOM element we can specify event listeners and attributes.

Here’s how a “Hello, world!” component in revolt:

const HelloWorld = () => {
  return 'Hello, world!';
};

Here’s how a timer would look like:

const Timer = () => {
  const timer = signal(0);
  setInterval(() => timer.set(timer() + 1), 1000);
  return () => `Timer: ${timer()}`;
};

And here’s an example of how you can compose components and pass properties:

const Avatar = (photo: () => string) => {
  return () => {
    name: 'img',
    attributes: {
      src: photo
    }
  };
};

const UserProfile = () => {
  const userProfile = signal(...);
  return [{
      name: 'h1',
      children: [
        () => `Profile of ${userProfile.name()}`
      ]
    },
    Avatar(userProfile.avatarUrl)
  ];
};

You can play with the grocery list app below that I built with revolt:

A couple of things to observe:

  • Each component is a function which returns a structure of nested objects that represents the view
  • We declare the state of each component with signals within the function body
  • There’s a way to get a reference to a particular DOM node that’s inspired by React’s ref
  • We have “fine-grained reactivity” in a sense that we can bind an attribute or a text node to a particular signal

This implementation uses functions for convenience. I know that many people feel strongly about the abstractions that enable component definition (e.g. classes vs functions vs sfc). As an Angular team member, I feel obligated to share that this prototype does not represent mine or the team's vision for the future of Angular's authoring.

We can render a component using:

render(Component(), document.body);

I decided to use nested objects instead of JSX or templating language to simplify the build and use fewer abstractions.

View model

Let’s look at the type definitions of the view:

export type Binding = string | ReadableSignal<string>;
export type EventListener = <K extends keyof GlobalEventHandlersEventMap>(event: GlobalEventHandlersEventMap[K]) => void;

export interface When {
  condition: ReadableSignal<boolean>;
  then: View;
  else?: View;
}

export interface For<T, I extends Iterable<T> = T[]> {
  collection: ReadableSignal<T>;
  items: (item: T, index: number) => ViewNode;
}

export interface ElementConfig {
  name: keyof HTMLElementTagNameMap;
  attributes?: Record<string, string|ReadableSignal<string|false>;
  children?: View;
  events?: {[key in keyof GlobalEventHandlersEventMap]?: EventListener};
  ref?: (node: Element) => void;
}

export type ViewNode = Binding | ElementConfig | When | For<any>;

export type View = ViewNode | View[];

When writing these types, it’s fun to see how similar they look to a grammar of a programming language. Templating languages are pretty much programming languages that render DOM.

Each view is a composition of nodes which could be:

  • Text or a text binding
  • DOM elements
  • Control flow (when, for)

It’s interesting to notice that revolt does not have the concept of a “host element” - a component can produce any number of root nodes including zero if we just render a text node.

Also notice that both When and For accept a readable signal and they rerender when the value of the signal changes. Similarly, we can get a sense how we implement fine-grained reactivity in text and attribute bindings - both could be either strings or ReadableSignals.

Signals

Our reactive framework will use a minimal signal implementation that I reused from the post “Building a Reactive Library from Scratch”. The library exports three abstractions ReadableSignal<T>, WritableSignal<T> and Effect:

export type ReadableSignal<T> = () => T;

export interface WritableSignal<T> extends ReadableSignal<T> {
  set(value: T): void;
}

export type Effect = () => void;

Here’s how you can use them:

const counter = signal(0);

effect(() => {
  console.log('Current value', counter());
});

counter.set(1);

The code above will output "Current value 0" and "Current value 1". If you’re interested in how the signals library works, have a look at its implementation or Ryan’s blog post.

Rendering

We already have the view and the signal library. The only thing left is the renderer! Let’s look at the render function:

export const render = (view: View, root: Element): Node | Node[] => {
  if (isConditional(view)) {
    return renderCondition(view, root);
  }
  if (isIterator(view)) {
    return renderIterator(view, root);
  }
  if (view instanceof Array) {
    return renderViewList(view, root);
  }
  if (typeof view === "string") {
    return renderTextNode(view, root);
  }
  if (typeof view === "function") {
    return renderDynamicText(view, root);
  }
  return renderElement(view, root);
};

Pretty straightforward and very similar to a parser. Now let’s look at the implementation of isIterator to see how we use signals:

const renderIterator = (view: For<any>, root: Element) => {
  let collection;
  let result: Node | Node[] | undefined;
  effect(() => {
    collection = view.collection();
    if (result) {
      destroy(result);
    }
    result = render(collection.map(view.items), root);
  });
  return result ?? [];
};

That’s it!

Inside an effect we read the signal representing the collection, after that we destroy the DOM from the previous value of the signal, and we render the new collection. Here we take advantage of the synchronous effect implementation.

Keep in mind that's an oversimplified implementation. All modern frameworks will have diffing logic that will rerender only the changed items to optimize the runtime.

In a similar way we implement other signal bindings:

const renderElement = (view: ElementConfig, root: Element) => {
  const element = document.createElement(view.name);
  for (const attribute in view.attributes) {
    const binding = view.attributes[attribute];
    if (!isDynamicBinding(binding)) {
      element.setAttribute(attribute, binding);
      continue;
    }
    effect(() => {
      const value = binding();
      if (value === false) {
        element.removeAttribute(attribute);
        return;
      }
      element.setAttribute(attribute, value);
    });
  }
  // ...
};

Here we create an effect for attribute bindings that are signals. Each time we receive a new value for the signal, we manually update the attribute.

You can see the entire implementation on GitHub.

Other approaches

Angular and React use very different approaches. Virtual DOM relies on pruning parts of the component tree that have not changed. Its elegance comes from functional programming, but could also cause extra rendering.

Similarly to the approach in revolt, Angular separates creation from update, but differently. The Angular compiler generates two rendering code paths:

  • One for initial render of the component
  • Another which contains only the dynamic parts of the view

Signals notify the framework that something in the dynamic part of the view has changed, which causes Angular to schedule change detection and update it.

That’s all folks

That was pretty much everything. Revolt is a tiny library that allows exploration of different concepts from web frameworks such as fine-grained reactivity, host elements, references, server-side rendering, etc.

In this write up we focused primarily on rendering and fine-grained reactivity, but I’ll be happy to drill more in other topics. Let me know what you’d be interested in learning about!