Skip to content

Commit 3105404

Browse files
author
Brian Kim
committed
deprecate prefixed spcial props in favor unprefixed special props
1 parent 33d60b9 commit 3105404

10 files changed

+286
-271
lines changed

src/crank.ts

+94-89
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export type Tag = string | symbol | Component;
5656
export type TagProps<TTag extends Tag> = TTag extends string
5757
? JSX.IntrinsicElements[TTag]
5858
: TTag extends Component<infer TProps>
59-
? TProps
60-
: Record<string, unknown>;
59+
? TProps & JSX.IntrinsicAttributes
60+
: Record<string, unknown> & JSX.IntrinsicAttributes;
6161

6262
/***
6363
* SPECIAL TAGS
@@ -190,29 +190,6 @@ export interface Element<TTag extends Tag = Tag> {
190190
* the attribute syntax from JSX.
191191
*/
192192
props: TagProps<TTag>;
193-
194-
/**
195-
* A value which uniquely identifies an element from its siblings so that it
196-
* can be added/updated/moved/removed by key rather than position.
197-
*
198-
* Passed in createElement() as the prop "c-key".
199-
*/
200-
key: Key;
201-
202-
/**
203-
* A callback which is called with the element’s result when it is committed.
204-
*
205-
* Passed in createElement() as the prop "c-ref".
206-
*/
207-
ref: ((value: unknown) => unknown) | undefined;
208-
209-
/**
210-
* A possible boolean which indicates that element should NOT be rerendered.
211-
* If the element has never been rendered, this property has no effect.
212-
*
213-
* Passed in createElement() as the prop "c-static".
214-
*/
215-
static_: boolean | undefined;
216193
}
217194

218195
/**
@@ -236,18 +213,21 @@ export interface Element<TTag extends Tag = Tag> {
236213
* rather than instatiating this class directly.
237214
*/
238215
export class Element<TTag extends Tag = Tag> {
239-
constructor(
240-
tag: TTag,
241-
props: TagProps<TTag>,
242-
key: Key,
243-
ref?: ((value: unknown) => unknown) | undefined,
244-
static_?: boolean | undefined,
245-
) {
216+
constructor(tag: TTag, props: TagProps<TTag>) {
246217
this.tag = tag;
247218
this.props = props;
248-
this.key = key;
249-
this.ref = ref;
250-
this.static_ = static_;
219+
}
220+
221+
get key(): Key {
222+
return this.props.key;
223+
}
224+
225+
get ref(): unknown {
226+
return this.props.ref;
227+
}
228+
229+
get static_(): boolean {
230+
return !!this.props["static"];
251231
}
252232
}
253233

@@ -258,6 +238,17 @@ export function isElement(value: any): value is Element {
258238
return value != null && value.$$typeof === ElementSymbol;
259239
}
260240

241+
const DEPRECATED_PROP_PREFIXES = ["crank-", "c-", "$"];
242+
243+
const SPECIAL_PROP_BASES = ["key", "ref", "static"];
244+
245+
const SPECIAL_PROPS = new Set(["children", ...SPECIAL_PROP_BASES]);
246+
for (const propPrefix of DEPRECATED_PROP_PREFIXES) {
247+
for (const propBase of SPECIAL_PROP_BASES) {
248+
SPECIAL_PROPS.add(propPrefix + propBase);
249+
}
250+
}
251+
261252
/**
262253
* Creates an element with the specified tag, props and children.
263254
*
@@ -271,47 +262,32 @@ export function createElement<TTag extends Tag>(
271262
props?: TagProps<TTag> | null | undefined,
272263
...children: Array<unknown>
273264
): Element<TTag> {
274-
let key: Key;
275-
let ref: ((value: unknown) => unknown) | undefined;
276-
let static_ = false;
277-
const props1 = {} as TagProps<TTag>;
278-
if (props != null) {
279-
for (const name in props) {
280-
switch (name) {
281-
case "crank-key":
282-
case "c-key":
283-
case "$key":
284-
// We have to make sure we don’t assign null to the key because we
285-
// don’t check for null keys in the diffing functions.
286-
if (props[name] != null) {
287-
key = props[name];
288-
}
289-
break;
290-
case "crank-ref":
291-
case "c-ref":
292-
case "$ref":
293-
if (typeof props[name] === "function") {
294-
ref = props[name];
295-
}
296-
break;
297-
case "crank-static":
298-
case "c-static":
299-
case "$static":
300-
static_ = !!props[name];
301-
break;
302-
default:
303-
props1[name] = props[name];
265+
if (props == null) {
266+
props = {} as TagProps<TTag>;
267+
}
268+
269+
for (const propPrefix of DEPRECATED_PROP_PREFIXES) {
270+
for (const propName of SPECIAL_PROP_BASES) {
271+
const deprecatedPropName = propPrefix + propName;
272+
if (deprecatedPropName in (props as TagProps<TTag>)) {
273+
// eslint-disable-next-line no-console
274+
console.warn(
275+
`The \`${deprecatedPropName}\` prop is deprecated. Use \`${propName}\` instead.`,
276+
);
277+
(props as TagProps<TTag>)[propName] = (props as TagProps<TTag>)[
278+
deprecatedPropName
279+
];
304280
}
305281
}
306282
}
307283

308284
if (children.length > 1) {
309-
props1.children = children;
285+
(props as TagProps<TTag>).children = children;
310286
} else if (children.length === 1) {
311-
props1.children = children[0];
287+
(props as TagProps<TTag>).children = children[0];
312288
}
313289

314-
return new Element(tag, props1, key, ref, static_);
290+
return new Element(tag, props as TagProps<TTag>);
315291
}
316292

317293
/** Clones a given element, shallowly copying the props object. */
@@ -322,7 +298,7 @@ export function cloneElement<TTag extends Tag>(
322298
throw new TypeError("Cannot clone non-element");
323299
}
324300

325-
return new Element(el.tag, {...el.props}, el.key, el.ref);
301+
return new Element(el.tag, {...el.props});
326302
}
327303

328304
/*** ELEMENT UTILITIES ***/
@@ -354,9 +330,10 @@ function narrow(value: Children): NarrowedChild {
354330
* When asking the question, what is the "value" of a specific element, the
355331
* answer varies depending on the tag:
356332
*
357-
* For host elements, the value is the nodes created for the element.
333+
* For host elements, the value is the nodes created for the element, e.g. the
334+
* DOM node in the case of the DOMRenderer.
358335
*
359-
* For fragments, the value is usually an array of nodes.
336+
* For fragments, the value is the value of the
360337
*
361338
* For portals, the value is undefined, because a Portal element’s root and
362339
* children are opaque to its parent.
@@ -616,24 +593,24 @@ export interface RendererImpl<
616593
tag: TTag,
617594
node: TNode,
618595
name: TName,
619-
value: TagProps<TTag>[TName],
620-
oldValue: TagProps<TTag>[TName] | undefined,
596+
value: unknown,
597+
oldValue: unknown,
621598
scope: TScope,
622599
): unknown;
623600

624601
arrange<TTag extends string | symbol>(
625602
tag: TTag,
626603
node: TNode,
627-
props: TagProps<TTag>,
604+
props: Record<string, unknown>,
628605
children: Array<TNode | string>,
629-
oldProps: TagProps<TTag> | undefined,
606+
oldProps: Record<string, unknown> | undefined,
630607
oldChildren: Array<TNode | string> | undefined,
631608
): unknown;
632609

633610
dispose<TTag extends string | symbol>(
634611
tag: TTag,
635612
node: TNode,
636-
props: TagProps<TTag>,
613+
props: Record<string, unknown>,
637614
): unknown;
638615

639616
flush(root: TRoot): unknown;
@@ -659,7 +636,8 @@ const defaultRendererImpl: RendererImpl<unknown, unknown, unknown, unknown> = {
659636
const _RendererImpl = Symbol.for("crank.RendererImpl");
660637
/**
661638
* An abstract class which is subclassed to render to different target
662-
* environments. This class is responsible for kicking off the rendering
639+
* environments. Subclasses will typically call super() with a custom
640+
* RendererImpl. This class is responsible for kicking off the rendering
663641
* process and caching previous trees by root.
664642
*
665643
* @template TNode - The type of the node for a rendering environment.
@@ -1140,7 +1118,7 @@ function updateRaw<TNode, TScope>(
11401118
): ElementValue<TNode> {
11411119
const props = ret.el.props;
11421120
if (!oldProps || oldProps.value !== props.value) {
1143-
ret.value = renderer.raw(props.value, scope, hydrationData);
1121+
ret.value = renderer.raw(props.value as any, scope, hydrationData);
11441122
}
11451123

11461124
return ret.value;
@@ -1162,7 +1140,7 @@ function updateFragment<TNode, TScope, TRoot extends TNode>(
11621140
ctx,
11631141
scope,
11641142
ret,
1165-
ret.el.props.children,
1143+
ret.el.props.children as any,
11661144
hydrationData,
11671145
);
11681146

@@ -1187,7 +1165,7 @@ function updateHost<TNode, TScope, TRoot extends TNode>(
11871165
const tag = el.tag as string | symbol;
11881166
let hydrationValue: TNode | string | undefined;
11891167
if (el.tag === Portal) {
1190-
root = ret.value = el.props.root;
1168+
root = ret.value = el.props.root as any;
11911169
} else {
11921170
if (hydrationData !== undefined) {
11931171
const value = hydrationData.children.shift();
@@ -1211,7 +1189,7 @@ function updateHost<TNode, TScope, TRoot extends TNode>(
12111189
ctx,
12121190
scope,
12131191
ret,
1214-
ret.el.props.children,
1192+
ret.el.props.children as any,
12151193
childHydrationData,
12161194
);
12171195

@@ -1261,7 +1239,7 @@ function commitHost<TNode, TScope>(
12611239
// TODO: The Copy tag doubles as a way to skip the patching of a prop.
12621240
// Not sure about this feature. Should probably be removed.
12631241
(copied = copied || new Set()).add(propName);
1264-
} else if (propName !== "children") {
1242+
} else if (!SPECIAL_PROPS.has(propName)) {
12651243
renderer.patch(
12661244
tag,
12671245
value,
@@ -1280,7 +1258,7 @@ function commitHost<TNode, TScope>(
12801258
props[name] = oldProps && oldProps[name];
12811259
}
12821260

1283-
ret.el = new Element(tag, props, ret.el.key, ret.el.ref);
1261+
ret.el = new Element(tag, props);
12841262
}
12851263

12861264
renderer.arrange(
@@ -1535,7 +1513,9 @@ class ContextImpl<
15351513
| AsyncIterator<Children, Children | void, unknown>
15361514
| undefined;
15371515

1538-
// The following properties are used to implement the
1516+
// A "block" is a promise which represents the duration during which new
1517+
// updates are queued, whereas "value" is a promise which represents the
1518+
// actual pending result of rendering.
15391519
declare inflightBlock: Promise<unknown> | undefined;
15401520
declare inflightValue: Promise<ElementValue<TNode>> | undefined;
15411521
declare enqueuedBlock: Promise<unknown> | undefined;
@@ -1611,7 +1591,7 @@ export class Context<T = any, TResult = any> implements EventTarget {
16111591
* plugins or utilities which wrap contexts.
16121592
*/
16131593
get props(): ComponentProps<T> {
1614-
return this[_ContextImpl].ret.el.props;
1594+
return this[_ContextImpl].ret.el.props as ComponentProps<T>;
16151595
}
16161596

16171597
// TODO: Should we rename this???
@@ -1637,7 +1617,7 @@ export class Context<T = any, TResult = any> implements EventTarget {
16371617
ctx.f |= NeedsToYield;
16381618
}
16391619

1640-
yield ctx.ret.el.props!;
1620+
yield ctx.ret.el.props as ComponentProps<T>;
16411621
}
16421622
} finally {
16431623
ctx.f &= ~IsInForOfLoop;
@@ -1661,7 +1641,7 @@ export class Context<T = any, TResult = any> implements EventTarget {
16611641

16621642
if (ctx.f & PropsAvailable) {
16631643
ctx.f &= ~PropsAvailable;
1664-
yield ctx.ret.el.props;
1644+
yield ctx.ret.el.props as ComponentProps<T>;
16651645
} else {
16661646
const props = await new Promise((resolve) => (ctx.onProps = resolve));
16671647
if (ctx.f & IsUnmounted) {
@@ -1962,8 +1942,8 @@ export class Context<T = any, TResult = any> implements EventTarget {
19621942
{
19631943
setEventProperty(ev, "eventPhase", AT_TARGET);
19641944
setEventProperty(ev, "currentTarget", ctx.owner);
1965-
const propCallback = ctx.ret.el.props["on" + ev.type];
1966-
if (propCallback != null) {
1945+
const propCallback = ctx.ret.el.props["on" + ev.type] as unknown;
1946+
if (typeof propCallback === "function") {
19671947
propCallback(ev);
19681948
if (immediateCancelBubble || ev.cancelBubble) {
19691949
return true;
@@ -2991,6 +2971,31 @@ declare global {
29912971
[tag: string]: any;
29922972
}
29932973

2974+
export interface IntrinsicAttributes {
2975+
children?: unknown;
2976+
key?: unknown;
2977+
ref?: unknown;
2978+
["static"]?: unknown;
2979+
/** @deprecated */
2980+
["crank-key"]?: unknown;
2981+
/** @deprecated */
2982+
["crank-ref"]?: unknown;
2983+
/** @deprecated */
2984+
["crank-static"]?: unknown;
2985+
/** @deprecated */
2986+
["c-key"]?: unknown;
2987+
/** @deprecated */
2988+
["c-ref"]?: unknown;
2989+
/** @deprecated */
2990+
["c-static"]?: unknown;
2991+
/** @deprecated */
2992+
$key?: unknown;
2993+
/** @deprecated */
2994+
$ref?: unknown;
2995+
/** @deprecated */
2996+
$static?: unknown;
2997+
}
2998+
29942999
export interface ElementChildrenAttribute {
29953000
children: {};
29963001
}

src/html.ts

+12
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ function printAttrs(props: Record<string, any>): string {
5656
switch (true) {
5757
case name === "children":
5858
case name === "innerHTML":
59+
case name === "key":
60+
case name === "ref":
61+
case name === "static":
62+
case name === "crank-key":
63+
case name === "crank-ref":
64+
case name === "crank-static":
65+
case name === "c-key":
66+
case name === "c-ref":
67+
case name === "c-static":
68+
case name === "$key":
69+
case name === "$ref":
70+
case name === "$static":
5971
break;
6072
case name === "style": {
6173
if (typeof value === "string") {

src/jsx-runtime.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import {createElement} from "./crank.js";
55

66
function jsxAdapter(tag: any, props: Record<string, any>, key: any) {
7-
// The new JSX transform extracts the key from props for reasons, but key is
8-
// not a special property in Crank.
9-
return createElement(tag, {...props, key});
7+
// The new JSX transform extracts the key from props for performance reasons,
8+
// but key is not a special property in Crank.
9+
props.key = key;
10+
return createElement(tag, props);
1011
}
1112

1213
export const Fragment = "";

0 commit comments

Comments
 (0)