Skip to content

Commit 920edfb

Browse files
committed
replace key weakmap with Keyed type (#98)
1 parent 099009d commit 920edfb

File tree

9 files changed

+52
-46
lines changed

9 files changed

+52
-46
lines changed

examples/uibench/main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// @ts-check
22
/// <reference types='./uibench.d.ts' />
33

4-
import { html } from 'dhtml'
5-
import { createRoot, keyed } from 'dhtml/client'
4+
import { html, keyed } from 'dhtml'
5+
import { createRoot } from 'dhtml/client'
66

77
/** @param {string} text */
88
function tableCell(text) {

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export { invalidate, keyed, onMount, onUnmount } from './client/controller.ts'
1+
export { invalidate, onMount, onUnmount } from './client/controller.ts'
22
export { attr_directive as attr, on_directive as on, type Directive } from './client/parts.ts'
33
export { createRoot, hydrate, type Root } from './client/root.ts'

src/client/controller.ts

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import type { Displayable, Renderable } from '../index.ts'
2-
import { assert, is_renderable } from '../shared.ts'
2+
import { assert, is_renderable, type Key } from '../shared.ts'
33
import { type Cleanup } from './util.ts'
44

5-
export type Key = string | number | bigint | boolean | symbol | object | null
6-
75
export interface Controller {
86
_mount_callbacks: (() => Cleanup)[]
97
_unmount_callbacks: Cleanup[]
@@ -26,8 +24,6 @@ export function get_controller(renderable: Renderable): Controller {
2624
return controller
2725
}
2826

29-
const keys: WeakMap<Displayable & object, Key> = new WeakMap()
30-
3127
export function invalidate(renderable: Renderable): void {
3228
const controller = controllers.get(renderable)
3329
assert(controller, 'the renderable has not been rendered')
@@ -47,14 +43,3 @@ export function onMount(renderable: Renderable, callback: () => Cleanup): void {
4743
export function onUnmount(renderable: Renderable, callback: () => void): void {
4844
onMount(renderable, () => callback)
4945
}
50-
51-
export function keyed<T extends Displayable & object>(displayable: T, key: Key): T {
52-
assert(!keys.has(displayable), 'renderable already has a key')
53-
keys.set(displayable, key)
54-
return displayable
55-
}
56-
57-
export function get_key(displayable: unknown): unknown {
58-
// the cast is fine because getting any non-object will return null
59-
return keys.get(displayable as object) ?? displayable
60-
}

src/client/parts.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {
22
assert,
33
is_html,
44
is_iterable,
5+
is_keyed,
56
is_renderable,
67
single_part_template,
78
type Displayable,
9+
type Key,
810
type Renderable,
911
} from '../shared.ts'
1012
import {
@@ -15,7 +17,7 @@ import {
1517
PART_PROPERTY,
1618
type CompiledTemplate,
1719
} from './compiler.ts'
18-
import { controllers, get_controller, get_key, type Key } from './controller.ts'
20+
import { controllers, get_controller } from './controller.ts'
1921
import { create_span_after, delete_contents, extract_contents, insert_node, type Span } from './span.ts'
2022
import type { Cleanup } from './util.ts'
2123

@@ -117,7 +119,7 @@ export function create_child_part(
117119
let i = 0
118120
let end = span._start
119121
for (const item of value) {
120-
const key = get_key(item) as Key
122+
const key = is_keyed(item) ? item._key : (item as Key)
121123
if (entries.length <= i) {
122124
const span = create_span_after(end)
123125
entries[i] = { _span: span, _part: create_child_part(span), _key: key }

src/client/root.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {
22
assert,
33
is_html,
44
is_iterable,
5+
is_keyed,
56
is_renderable,
67
single_part_template,
78
type Displayable,
9+
type Key,
810
type Renderable,
911
} from '../shared.ts'
1012
import {
@@ -16,7 +18,6 @@ import {
1618
PART_PROPERTY,
1719
type CompiledTemplate,
1820
} from './compiler.ts'
19-
import { get_key, type Key } from './controller.ts'
2021
import {
2122
create_attribute_part,
2223
create_child_part,
@@ -95,7 +96,7 @@ function hydrate_child_part(span: Span, value: unknown) {
9596
let end = span._start
9697

9798
for (const item of value) {
98-
const key = get_key(item) as Key
99+
const key = is_keyed(item) ? item._key : (item as Key)
99100

100101
const start = end.nextSibling
101102
assert(start && is_comment(start) && start.data === '?[')

src/client/tests/hydration.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { html, type Displayable, type Renderable } from 'dhtml'
2-
import { attr, hydrate, invalidate, keyed, onMount, type Directive, type Root } from 'dhtml/client'
1+
import { html, keyed, type Displayable, type Renderable } from 'dhtml'
2+
import { attr, hydrate, invalidate, onMount, type Directive, type Root } from 'dhtml/client'
33
import { renderToString } from 'dhtml/server'
44
import { assert, assert_deep_eq, assert_eq, test } from '../../../scripts/test/test.ts'
55

src/client/tests/lists.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { html, type Displayable } from 'dhtml'
2-
import { invalidate, keyed } from 'dhtml/client'
1+
import { html, keyed, type Displayable } from 'dhtml'
2+
import { invalidate } from 'dhtml/client'
33
import { assert, assert_eq, test } from '../../../scripts/test/test.ts'
44
import { setup } from './setup.ts'
55

src/index.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,6 @@
1-
import { html_tag, is_html } from './shared.ts'
1+
export { html, keyed, type Displayable, type HTML, type Renderable } from './shared.ts'
22

3-
export interface HTML {
4-
[html_tag]: true
5-
/* @internal */ _statics: TemplateStringsArray
6-
/* @internal */ _dynamics: unknown[]
7-
}
8-
9-
export function html(statics: TemplateStringsArray, ...dynamics: unknown[]): HTML {
10-
return {
11-
[html_tag]: true,
12-
_dynamics: dynamics,
13-
_statics: statics,
14-
}
15-
}
3+
import { is_html } from './shared.ts'
164

175
if (__DEV__) {
186
type JsonML = string | readonly [tag: string, attrs?: Record<string, any>, ...children: JsonML[]]
@@ -38,5 +26,3 @@ if (__DEV__) {
3826
},
3927
})
4028
}
41-
42-
export type { Displayable, Renderable } from './shared.ts'

src/shared.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { html, type HTML } from './index.ts'
21
export * as lexer from './shared/lexer.ts'
32

43
/** @internal */
@@ -10,7 +9,7 @@ export interface ToString {
109
toString(): string
1110
}
1211

13-
export type Displayable = null | undefined | ToString | Node | Renderable | Iterable<Displayable> | HTML
12+
export type Displayable = null | undefined | ToString | Node | Renderable | Iterable<Displayable> | HTML | Keyed
1413
export interface Renderable {
1514
render(): Displayable
1615
}
@@ -32,11 +31,44 @@ export function assert(value: unknown, message?: string): asserts value {
3231
}
3332
}
3433

35-
export const html_tag: unique symbol = Symbol()
34+
export interface HTML {
35+
[html_tag]: true
36+
/* @internal */ _statics: TemplateStringsArray
37+
/* @internal */ _dynamics: unknown[]
38+
}
39+
40+
const html_tag: unique symbol = Symbol()
41+
export function html(statics: TemplateStringsArray, ...dynamics: unknown[]): HTML {
42+
return {
43+
[html_tag]: true,
44+
_dynamics: dynamics,
45+
_statics: statics,
46+
}
47+
}
48+
3649
export function is_html(value: unknown): value is HTML {
3750
return typeof value === 'object' && value !== null && html_tag in value
3851
}
3952

4053
export function single_part_template(part: Displayable): HTML {
4154
return html`${part}`
4255
}
56+
57+
export type Key = string | number | bigint | boolean | symbol | object | null
58+
export interface Keyed extends Renderable {
59+
[keyed_tag]: true
60+
/** @internal */ _key: Key
61+
}
62+
63+
const keyed_tag: unique symbol = Symbol()
64+
export function keyed<T extends Displayable & object>(displayable: T, key: Key): Keyed {
65+
return {
66+
[keyed_tag]: true,
67+
_key: key,
68+
render: () => displayable,
69+
}
70+
}
71+
72+
export function is_keyed(value: any): value is Keyed {
73+
return typeof value === 'object' && value !== null && keyed_tag in value
74+
}

0 commit comments

Comments
 (0)