Skip to content

Commit b8a3dd8

Browse files
authored
New VDOM Rendering Engine (#58)
* Initial vdom rewrite * refactor * vdom * Additional vdom changes * More changes to vdom to support merges correctly * Refactors * More refactors * Fix dom vnode logic and refactor property/attribute/event updates * more refactors * Use original properties passed to the widget when creating the instruction for an invalidation * Do not try to merge for custom elements * Move dom application to after the invalidation queue has been processed * Run deferred properties in WidgetBase * README * Clean up and tests for ProjectorMixin * Move invalidate back to instanceData * Uncomment tests * Move from class to function for vdom * vdom reorg and tweaking * README * Add back remaining vdom unit tests * refactor and fixes to ensure the latest wrapper is used * Run filtering before clearing the node handler to allow meta usage in deferred props * Move registry resolution to WidgetBase from vdom * Create attach application for operations on a widget after it has been appended * Fix insert before calculation to ensure results of widgets added to the correct position * Falsy dnodes are now filtered by widgetbase, so deal with this in asserting render * more tidying
1 parent bf03a16 commit b8a3dd8

30 files changed

+2880
-3372
lines changed

package-lock.json

+15-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/testing/support/assertRender.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DNode, WNode, VNode, DefaultWidgetBaseInterface, Constructor } from '../../widget-core/interfaces';
2-
import { isWNode } from '../../widget-core/d';
2+
import { isWNode, isVNode } from '../../widget-core/d';
33
import * as diff from 'diff';
44
import WeakMap from '../../shim/WeakMap';
55
import Set from '../../shim/Set';
@@ -30,21 +30,28 @@ export function formatDNodes(nodes: DNode | DNode[], depth: number = 0): string
3030
for (let i = 0; i < depth; i++) {
3131
tabs = `${tabs}\t`;
3232
}
33+
let requiresCarriageReturn = false;
3334
let formattedNode = nodes.reduce((result: string, node, index) => {
34-
if (node === null || node === undefined) {
35+
if (!node) {
3536
return result;
3637
}
37-
if (index > 0) {
38+
if (requiresCarriageReturn) {
3839
result = `${result}\n`;
40+
} else {
41+
requiresCarriageReturn = true;
3942
}
4043
result = `${result}${tabs}`;
4144

4245
if (typeof node === 'string') {
4346
return `${result}"${node}"`;
4447
}
4548

49+
if (isVNode(node) && node.text) {
50+
return `${result}"${node.text}"`;
51+
}
52+
4653
result = `${result}${formatNode(node, tabs)}`;
47-
if (node.children && node.children.length > 0) {
54+
if (node.children && node.children.some((child) => !!child)) {
4855
result = `${result}, [\n${formatDNodes(node.children, depth + 1)}\n${tabs}]`;
4956
}
5057
return `${result})`;

src/widget-core/README.md

+41-26
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,30 @@ class HelloDojo extends WidgetBase {
6161

6262
#### Rendering a Widget in the DOM
6363

64-
To display your new component in the view you will need to decorate it with some functionality needed to "project" the widget into the browser. This is done using the `ProjectorMixin` from `@dojo/framework/widget-core/mixins/Projector`.
64+
To display your new component in the view you will to use the `renderer` from the `@dojo/framework/widget-core/vdom` module. The `renderer` function accepts function that returns your component using the `w()` pragma and calling `.mount()` on the returned API.
6565

6666
```ts
67-
const Projector = ProjectorMixin(HelloDojo);
68-
const projector = new Projector();
67+
import renderer from '@dojo/framework/widget-core/vdom';
68+
import { w } from '@dojo/framework/widget-core/d';
6969

70-
projector.append();
70+
const r = renderer(() => w(HelloDojo, {}));
71+
r.mount();
7172
```
7273

73-
By default, the projector will attach the widget to the `document.body` in the DOM, but this can be overridden by passing a reference to the preferred parent DOM Element.
74+
`renderer#mount` accepts an optional argument of `MountOptions` that controls configuration of the mount operation.
75+
76+
```ts
77+
interface MountOptions {
78+
sync: boolean; // (default `false`)
79+
80+
merge: boolean; // (default `true`)
81+
82+
domNode: HTMLElement; // (default `document.body)
83+
84+
transition: TransitionStrategy; // (default `cssTransitions`)
85+
}
86+
87+
The renderer by default mounts to the `document.body` in the DOM, but this can be overridden by passing the preferred target dom node to the `.mount()` function.
7488

7589
Consider the following in your HTML file:
7690

@@ -81,11 +95,12 @@ Consider the following in your HTML file:
8195
You can target this Element:
8296

8397
```ts
84-
const root = document.getElementById('my-app');
85-
const Projector = ProjectorMixin(HelloDojo);
86-
const projector = new Projector();
98+
import renderer from '@dojo/framework/widget-core/vdom';
99+
import { w } from '@dojo/framework/widget-core/d';
87100
88-
projector.append(root);
101+
const root = document.getElementById('my-app');
102+
const r = renderer(() => w(HelloDojo, {}));
103+
r.mount({ domNode: root });
89104
```
90105

91106
#### Widgets and Properties
@@ -130,17 +145,13 @@ class App extends WidgetBase {
130145
}
131146
```
132147

133-
We can now use `App` with the `ProjectorMixin` to render the `Hello` widgets.
148+
We can now use `App` with the `renderer` to display the `Hello` widgets.
134149

135150
```ts
136-
const Projector = ProjectorMixin(App);
137-
const projector = new Projector();
138-
139-
projector.append();
151+
const r = renderer(() => w(App, {}));
152+
r.mount({ domNode: root });
140153
```
141154

142-
**Note:** Widgets must return a single top-level `DNode` from the `render` method, which is why the `Hello` widgets were wrapped in a `div` element.
143-
144155
#### Decomposing Widgets
145156

146157
Splitting widgets into multiple smaller widgets is easy and helps to add extended functionality and promotes reuse.
@@ -197,7 +208,7 @@ interface ListItemProperties {
197208
id: string;
198209
content: string;
199210
highlighted: boolean;
200-
onItemClick: (id: string) => void;
211+
onItemClick(id: string) => void;
201212
}
202213

203214
class ListItem extends WidgetBase<ListItemProperties> {
@@ -226,7 +237,7 @@ interface ListProperties {
226237
content: string;
227238
highlighted: boolean;
228239
};
229-
onItemClick: (id: string) => void;
240+
onItemClick(id: string) => void;
230241
}
231242

232243
class List extends WidgetBase<ListProperties> {
@@ -596,7 +607,7 @@ These are some of the **important** principles to keep in mind when creating and
596607
597608
1. The widget's *`__render__`*, *`__setProperties__`*, *`__setChildren__`* functions should **never** be called or overridden.
598609
- These are the internal methods of the widget APIs and their behavior can change in the future, causing regressions in your application.
599-
2. Except for projectors, you should **never** need to deal directly with widget instances
610+
2. You should **never** need to deal directly with widget instances
600611
- The Dojo widget system manages all instances required including caching and destruction, trying to create and manage other widgets will cause issues and will not work as expected.
601612
3. **Never** update `properties` within a widget instance, they should be considered pure.
602613
- Properties are considered read-only and should not be updated within a widget instance, updating properties could cause unexpected behavior and introduce bugs in your application.
@@ -798,23 +809,27 @@ class MyWidget extends WidgetBase {
798809
799810
The `Registry` provides a mechanism to define widgets and injectors (see the [`Containers & Injectors`](#containers--injectors) section), that can be dynamically/lazily loaded on request. Once the registry widget is loaded all widgets that need the newly loaded widget will be invalidated and re-rendered automatically.
800811
801-
A main registry can be provided to the `projector`, which will be automatically passed to all widgets within the tree (referred to as `baseRegistry`). Each widget also gets access to a private `Registry` instance that can be used to define registry items that are scoped to the widget. The locally defined registry items are considered a higher precedence than an item registered in the `baseRegistry`.
812+
A main registry can be provided to the `renderer`, which will be automatically passed to all widgets within the tree (referred to as `baseRegistry`). Each widget also gets access to a private `Registry` instance that can be used to define registry items that are scoped to the widget. The locally defined registry items are considered a higher precedence than an item registered in the `baseRegistry`.
802813
803814
```ts
804815
import { Registry } from '@dojo/framework/widget-core/Registry';
816+
import { w } from '@dojo/framework/widget-core/d';
805817

806-
import { MyWidget } from './MyWidget';
807-
import { MyAppContext } from './MyAppContext';
818+
import MyWidget from './MyWidget';
819+
import MyAppContext from './MyAppContext';
820+
import App from './App';
808821

809822
const registry = new Registry();
823+
810824
registry.define('my-widget', MyWidget);
825+
811826
registry.defineInjector('my-injector', (invalidator) => {
812827
const appContext = new MyAppContext(invalidator);
813828
return () => appContext;
814829
});
815-
// ... Mixin and create Projector ...
816830

817-
projector.setProperties({ registry });
831+
const r = renderer(() => w(App, {}));
832+
r.registry = registry;
818833
```
819834
820835
In some scenarios, it might be desirable to allow the `baseRegistry` to override an item defined in the local `registry`. Use true as the second argument of the registry.get function to override the local item.
@@ -970,7 +985,7 @@ class MyClass extends WidgetBase {
970985
971986
### Containers & Injectors
972987
973-
There is built-in support for side-loading/injecting values into sections of the widget tree and mapping them to a widget's properties. This is achieved by registering an injector factory with a `registry` and setting the registry as a property on the application's `projector` to ensure the registry instance is available to your application.
988+
There is built-in support for side-loading/injecting values into sections of the widget tree and mapping them to a widget's properties. This is achieved by registering an injector factory with a `registry` and setting the registry on the application's `renderer` to ensure the registry instance is available to your application.
974989
975990
Create a factory function for a function that returns the required `payload`.
976991
@@ -1570,7 +1585,7 @@ Your widget will be registered with the browser using the provided tag name. The
15701585
15711586
##### Initialization
15721587
1573-
Custom logic can be performed after properties/attributes have been defined but before the projector is created. This
1588+
Custom logic can be performed after properties/attributes have been defined but before the custom element is rendered. This
15741589
allows you full control over your widget, allowing you to add custom properties, event handlers, work with child nodes, etc.
15751590
The initialization function is run from the context of the HTML element.
15761591

0 commit comments

Comments
 (0)