Skip to content

Commit 0bd01a8

Browse files
authored
Merge pull request #3797 from preactjs/v11-optimizations-1
V11 optimizations 1
2 parents cdfbc2e + 9e8d885 commit 0bd01a8

File tree

9 files changed

+118
-120
lines changed

9 files changed

+118
-120
lines changed

compat/src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ function findDOMNode(component) {
9191
return component;
9292
}
9393

94-
return getChildDom(component._internal);
94+
return getChildDom(component._internal, 0);
9595
}
9696

9797
/**

compat/test/browser/jsx-runtime.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Component } from 'preact/compat';
2+
import { jsx } from 'preact/compat/jsx-runtime';
3+
import { setupScratch, teardown } from '../../../test/_util/helpers';
4+
import { getSymbol } from './testUtils';
5+
6+
describe('compat createElement()', () => {
7+
/** @type {HTMLDivElement} */
8+
let scratch;
9+
10+
beforeEach(() => {
11+
scratch = setupScratch();
12+
});
13+
14+
afterEach(() => {
15+
teardown(scratch);
16+
});
17+
18+
it('should normalize vnodes', () => {
19+
let vnode = jsx('div', {
20+
a: 'b',
21+
children: jsx('a', { children: 't' })
22+
});
23+
24+
const $$typeof = getSymbol('react.element', 0xeac7);
25+
expect(vnode).to.have.property('$$typeof', $$typeof);
26+
expect(vnode).to.have.property('type', 'div');
27+
expect(vnode)
28+
.to.have.property('props')
29+
.that.is.an('object');
30+
expect(vnode.props).to.have.property('children');
31+
expect(vnode.props.children).to.have.property('$$typeof', $$typeof);
32+
expect(vnode.props.children).to.have.property('type', 'a');
33+
expect(vnode.props.children)
34+
.to.have.property('props')
35+
.that.is.an('object');
36+
expect(vnode.props.children.props).to.eql({ children: 't' });
37+
});
38+
39+
it('should apply defaultProps', () => {
40+
class Foo extends Component {
41+
render() {
42+
return jsx('div', {});
43+
}
44+
}
45+
46+
Foo.defaultProps = {
47+
foo: 'bar'
48+
};
49+
50+
const vnode = jsx(Foo, {});
51+
expect(vnode.props).to.deep.equal({
52+
foo: 'bar'
53+
});
54+
});
55+
56+
it('should keep props over defaultProps', () => {
57+
class Foo extends Component {
58+
render() {
59+
return jsx('div', {});
60+
}
61+
}
62+
63+
Foo.defaultProps = {
64+
foo: 'bar'
65+
};
66+
67+
const vnode = jsx(Foo, { foo: 'baz' });
68+
expect(vnode.props).to.deep.equal({
69+
foo: 'baz'
70+
});
71+
});
72+
});

jsx-runtime/src/index.js

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ function createVNode(type, props, key, __source, __self) {
2727
// We'll want to preserve `ref` in props to get rid of the need for
2828
// forwardRef components in the future, but that should happen via
2929
// a separate PR.
30+
let ref;
3031
let normalizedProps = {};
3132
for (let i in props) {
32-
if (i != 'ref') {
33+
if (i === 'ref') {
34+
ref = props[i];
35+
} else {
3336
normalizedProps[i] = props[i];
3437
}
3538
}
@@ -38,23 +41,13 @@ function createVNode(type, props, key, __source, __self) {
3841
type,
3942
props: normalizedProps,
4043
key,
41-
ref: props && props.ref,
44+
ref,
4245
constructor: undefined,
4346
_vnodeId: --vnodeId,
4447
__source,
4548
__self
4649
};
4750

48-
// If a Component VNode, check for and apply defaultProps.
49-
// Note: `type` is often a String, and can be `undefined` in development.
50-
let defaults, i;
51-
if (typeof type === 'function' && (defaults = type.defaultProps)) {
52-
for (i in defaults)
53-
if (normalizedProps[i] === undefined) {
54-
normalizedProps[i] = defaults[i];
55-
}
56-
}
57-
5851
if (options.vnode) options.vnode(vnode);
5952
return vnode;
6053
}

jsx-runtime/test/browser/jsx-runtime.test.js

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,40 +31,6 @@ describe('Babel jsx/jsxDEV', () => {
3131
expect(vnode.key).to.equal('foo');
3232
});
3333

34-
it('should apply defaultProps', () => {
35-
class Foo extends Component {
36-
render() {
37-
return <div />;
38-
}
39-
}
40-
41-
Foo.defaultProps = {
42-
foo: 'bar'
43-
};
44-
45-
const vnode = jsx(Foo, {}, null);
46-
expect(vnode.props).to.deep.equal({
47-
foo: 'bar'
48-
});
49-
});
50-
51-
it('should keep props over defaultProps', () => {
52-
class Foo extends Component {
53-
render() {
54-
return <div />;
55-
}
56-
}
57-
58-
Foo.defaultProps = {
59-
foo: 'bar'
60-
};
61-
62-
const vnode = jsx(Foo, { foo: 'baz' }, null);
63-
expect(vnode.props).to.deep.equal({
64-
foo: 'baz'
65-
});
66-
});
67-
6834
it('should set __source and __self', () => {
6935
const vnode = jsx('div', { class: 'foo' }, 'key', 'source', 'self');
7036
expect(vnode.__source).to.equal('source');

src/clone-element.js

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createVNode } from './create-element';
1+
import { EMPTY_ARR } from './constants';
2+
import { createElement } from './create-element';
23

34
/**
45
* Clones the given VNode, optionally adding attributes/props and replacing its children.
@@ -8,33 +9,15 @@ import { createVNode } from './create-element';
89
* @returns {import('./internal').VNode}
910
*/
1011
export function cloneElement(vnode, props, children) {
11-
let normalizedProps = Object.assign({}, vnode.props),
12-
key,
13-
ref,
14-
i;
15-
16-
for (i in props) {
17-
if (i == 'key') key = props[i];
18-
else if (i == 'ref') ref = props[i];
19-
else normalizedProps[i] = props[i];
20-
}
21-
2212
if (arguments.length > 3) {
23-
children = [children];
24-
for (i = 3; i < arguments.length; i++) {
25-
children.push(arguments[i]);
26-
}
13+
children = EMPTY_ARR.slice.call(arguments, 2);
2714
}
2815

29-
if (children !== undefined) {
30-
normalizedProps.children = children;
31-
}
32-
33-
return createVNode(
34-
vnode.type,
35-
normalizedProps,
36-
key || vnode.key,
37-
ref || vnode.ref,
38-
0
16+
const clonedProps = Object.assign(
17+
{ key: vnode.key, ref: vnode.ref },
18+
vnode.props,
19+
props
3920
);
21+
22+
return createElement(vnode.type, clonedProps, children);
4023
}

src/component.js

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { commitRoot } from './diff/commit';
22
import options from './options';
3-
import { createVNode, Fragment } from './create-element';
3+
import { createElement, Fragment } from './create-element';
44
import { patch } from './diff/patch';
55
import { DIRTY_BIT, FORCE_UPDATE, MODE_UNMOUNTING } from './constants';
66
import { getParentDom } from './tree';
@@ -91,26 +91,25 @@ Component.prototype.render = Fragment;
9191
/**
9292
* @param {import('./internal').Internal} internal The internal to rerender
9393
*/
94-
function rerender(internal) {
95-
if (~internal.flags & MODE_UNMOUNTING && internal.flags & DIRTY_BIT) {
96-
const vnode = createVNode(
97-
internal.type,
98-
internal.props,
99-
internal.key, // @TODO we shouldn't need to actually pass these
100-
internal.ref, // since the mode flag should bypass key/ref handling
101-
0
102-
);
103-
104-
patch(internal, vnode, getParentDom(internal));
105-
commitRoot(internal);
106-
}
94+
function renderQueuedInternal(internal) {
95+
// Don't render unmounting/unmounted trees:
96+
if (internal.flags & MODE_UNMOUNTING) return;
97+
98+
// Don't render trees already rendered in this pass:
99+
if (!(internal.flags & DIRTY_BIT)) return;
100+
101+
const vnode = createElement(internal.type, internal.props);
102+
vnode.props = internal.props;
103+
104+
patch(internal, vnode, getParentDom(internal));
105+
commitRoot(internal);
107106
}
108107

109108
/**
110109
* The render queue
111110
* @type {Array<import('./internal').Internal>}
112111
*/
113-
let rerenderQueue = [];
112+
let renderQueue = [];
114113

115114
/*
116115
* The value of `Component.debounce` must asynchronously invoke the passed in callback. It is
@@ -131,22 +130,22 @@ export function enqueueRender(internal) {
131130
if (
132131
(!(internal.flags & DIRTY_BIT) &&
133132
(internal.flags |= DIRTY_BIT) &&
134-
rerenderQueue.push(internal) &&
135-
!process._rerenderCount++) ||
133+
renderQueue.push(internal) &&
134+
!processRenderQueue._rerenderCount++) ||
136135
prevDebounce !== options.debounceRendering
137136
) {
138137
prevDebounce = options.debounceRendering;
139-
(prevDebounce || setTimeout)(process);
138+
(prevDebounce || setTimeout)(processRenderQueue);
140139
}
141140
}
142141

143142
/** Flush the render queue by rerendering all queued components */
144-
function process() {
145-
while ((len = process._rerenderCount = rerenderQueue.length)) {
146-
rerenderQueue.sort((a, b) => a._depth - b._depth);
143+
function processRenderQueue() {
144+
while ((len = processRenderQueue._rerenderCount = renderQueue.length)) {
145+
renderQueue.sort((a, b) => a._depth - b._depth);
147146
while (len--) {
148-
rerender(rerenderQueue.shift());
147+
renderQueuedInternal(renderQueue.shift());
149148
}
150149
}
151150
}
152-
let len = (process._rerenderCount = 0);
151+
let len = (processRenderQueue._rerenderCount = 0);

src/create-context.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ let nextContextId = 0;
44

55
const providers = new Set();
66

7+
/** @param {import('./internal').Internal} internal */
78
export const unsubscribeFromContext = internal => {
89
// if this was a context provider, delete() returns true and we exit:
910
if (providers.delete(internal)) return;

src/create-element.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,15 @@ export function createElement(type, props, children) {
3434
normalizedProps.children = children;
3535
}
3636

37-
return createVNode(type, normalizedProps, key, ref, 0);
38-
}
39-
40-
/**
41-
* Create a VNode (used internally by Preact)
42-
* @param {import('./internal').VNode["type"]} type The node name or Component
43-
* Constructor for this virtual node
44-
* @param {object | string | number | null} props The properties of this virtual node.
45-
* If this virtual node represents a text node, this is the text of the node (string or number).
46-
* @param {string | number | null} key The key for this virtual node, used when
47-
* diffing it against its children
48-
* @param {import('./internal').VNode["ref"]} ref The ref property that will
49-
* receive a reference to its created child
50-
* @returns {import('./internal').VNode}
51-
*/
52-
export function createVNode(type, props, key, ref, original) {
5337
// V8 seems to be better at detecting type shapes if the object is allocated from the same call site
5438
// Do not inline into createElement and coerceToVNode!
5539
const vnode = {
5640
type,
57-
props,
41+
props: normalizedProps,
5842
key,
5943
ref,
6044
constructor: undefined,
61-
_vnodeId: original || ++vnodeId
45+
_vnodeId: ++vnodeId
6246
};
6347

6448
if (options.vnode != null) options.vnode(vnode);
@@ -77,7 +61,7 @@ export function normalizeToVNode(childVNode) {
7761
}
7862
if (type === 'object') {
7963
if (Array.isArray(childVNode)) {
80-
return createVNode(Fragment, { children: childVNode }, null, null, 0);
64+
return createElement(Fragment, null, childVNode);
8165
}
8266
} else if (type !== 'string' && type !== 'function') {
8367
return String(childVNode);

src/tree.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,23 +143,23 @@ export function getDomSibling(internal, childIndex) {
143143

144144
/**
145145
* @param {import('./internal').Internal} internal
146-
* @param {number} [i]
146+
* @param {number} offset
147147
* @returns {import('./internal').PreactElement}
148148
*/
149-
export function getChildDom(internal, i) {
149+
export function getChildDom(internal, offset) {
150150
if (internal._children == null) {
151151
return null;
152152
}
153153

154-
for (i = i || 0; i < internal._children.length; i++) {
155-
let child = internal._children[i];
154+
for (; offset < internal._children.length; offset++) {
155+
let child = internal._children[offset];
156156
if (child != null) {
157157
if (child.flags & TYPE_DOM) {
158158
return child._dom;
159159
}
160160

161161
if (shouldSearchComponent(child)) {
162-
let childDom = getChildDom(child);
162+
let childDom = getChildDom(child, 0);
163163
if (childDom) {
164164
return childDom;
165165
}

0 commit comments

Comments
 (0)