Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6b777ba
Golf: rely on vnode/_dom truthiness instead of explicit null checks
JoviDeCroock Aug 6, 2026
5e96b7a
Golf: loose equality where the operands make it identical
JoviDeCroock Aug 6, 2026
28960ba
Golf: drop redundant _pendingArgs reset in the hooks diffed hook
JoviDeCroock Aug 6, 2026
9c63e87
Golf core diff: drop redundant excessDomChildren element check, tidy …
JoviDeCroock Aug 6, 2026
a56bf7b
Golf: fold insert()'s comment-skipping walk into the loop condition
JoviDeCroock Aug 6, 2026
acb6c1b
Golf: use Array#some for the excessDomChildren cleanup loops
JoviDeCroock Aug 6, 2026
2371246
Golf: simplify the streamed-hydration marker scans
JoviDeCroock Aug 6, 2026
0bea5fc
Golf: assorted expression-level simplifications in core
JoviDeCroock Aug 6, 2026
fd10120
Drop the context Provider's subs teardown now that subs is a Set
JoviDeCroock Aug 6, 2026
0ac70e6
Golf: simplify firstChildDom tracking and setState's nextState check
JoviDeCroock Aug 6, 2026
f50b1ca
Golf: read existing DOM attributes with slice+some
JoviDeCroock Aug 6, 2026
287494d
Golf encodeEntities down to chained replaces
JoviDeCroock Aug 6, 2026
eed9e88
Golf: use Array#some for the stale-children unmount loop
JoviDeCroock Aug 6, 2026
8a6e574
Fix compat element tag validation
JoviDeCroock Aug 6, 2026
d8da70a
Fix perf regression from the attribute/unmount loop golfing
JoviDeCroock Aug 7, 2026
28c9afb
Revert "Golf encodeEntities down to chained replaces"
JoviDeCroock Aug 7, 2026
69b0935
Merge branch 'main' into JoviDeCroock/reduce-bundle-size
JoviDeCroock Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compat/src/Children.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const Children = {
},
only(children) {
const normalized = toChildArray(children);
if (normalized.length !== 1) throw 'Children.only';
if (normalized.length != 1) throw 'Children.only';
return normalized[0];
},
toArray: toChildArray
Expand Down
4 changes: 2 additions & 2 deletions compat/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function isValidElement(element) {
* @returns {boolean}
*/
function isFragment(element) {
return isValidElement(element) && element.type === Fragment;
return isValidElement(element) && element.type == Fragment;
}

/**
Expand Down Expand Up @@ -118,7 +118,7 @@ function findDOMNode(component) {
return (
(component &&
((component._vnode && component._vnode._dom) ||
(component.nodeType === 1 && component))) ||
(component.nodeType == 1 && component))) ||
null
);
}
Expand Down
44 changes: 20 additions & 24 deletions compat/src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const MODE_HYDRATE = 1 << 5;
const CAMEL_PROPS =
/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;
const CAMEL_REPLACE = /[A-Z0-9]/g;
const IS_DOM = typeof document !== 'undefined';
const IS_DOM = typeof document != 'undefined';

/**
* This is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84
Expand Down Expand Up @@ -177,63 +177,59 @@ function handleDomVNode(vnode) {
let value = props[i];

if (
(i === 'value' && 'defaultValue' in props && value == null) ||
(i == 'value' && 'defaultValue' in props && value == null) ||
// Emulate React's behavior of not rendering the contents of noscript tags on the client.
(IS_DOM && i === 'children' && type === 'noscript') ||
i === 'class' ||
i === 'className'
(IS_DOM && i == 'children' && type == 'noscript') ||
i == 'class' ||
i == 'className'
) {
// Skip applying value if it is null/undefined and we already set
// a default value
continue;
}

if (i === 'style' && typeof value === 'object') {
if (i == 'style' && typeof value == 'object') {
let cloned;
for (let key in value) {
if (typeof value[key] === 'number' && !IS_NON_DIMENSIONAL.test(key)) {
if (typeof value[key] == 'number' && !IS_NON_DIMENSIONAL.test(key)) {
if (!cloned) {
cloned = value = assign({}, value);
}
value[key] += 'px';
}
}
} else if (
i === 'defaultValue' &&
'value' in props &&
props.value == null
) {
} else if (i == 'defaultValue' && 'value' in props && props.value == null) {
// `defaultValue` is treated as a fallback `value` when a value prop is present but null/undefined.
// `defaultValue` for Elements with no value prop is the same as the DOM defaultValue property.
i = 'value';
} else if (i === 'download' && value === true) {
} else if (i == 'download' && value === true) {
// Calling `setAttribute` with a truthy value will lead to it being
// passed as a stringified value, e.g. `download="true"`. React
// converts it to an empty string instead, otherwise the attribute
// value will be used as the file name and the file will be called
// "true" upon downloading it.
value = '';
} else if (i === 'translate' && value === 'no') {
} else if (i == 'translate' && value === 'no') {
value = false;
} else if (i[0] === 'o' && i[1] === 'n') {
} else if (i[0] == 'o' && i[1] == 'n') {
let lowerCased = i.toLowerCase();
if (lowerCased === 'ondoubleclick') {
if (lowerCased == 'ondoubleclick') {
i = 'ondblclick';
} else if (
lowerCased === 'onchange' &&
(type === 'input' || type === 'textarea') &&
lowerCased == 'onchange' &&
(type == 'input' || type == 'textarea') &&
!onChangeInputType(props.type)
) {
lowerCased = i = 'oninput';
} else if (lowerCased === 'onfocus') {
} else if (lowerCased == 'onfocus') {
i = 'onfocusin';
} else if (lowerCased === 'onblur') {
} else if (lowerCased == 'onblur') {
i = 'onfocusout';
}

// Add support for onInput and onChange, see #3561
// if we have an oninput prop already change it to oninputCapture
if (lowerCased === 'oninput') {
if (lowerCased == 'oninput') {
i = lowerCased;
if (normalizedProps[i]) {
i = 'oninputCapture';
Expand Down Expand Up @@ -289,9 +285,9 @@ function handleDomVNode(vnode) {
let oldVNodeHook = options.vnode;
options.vnode = vnode => {
// only normalize props on Element nodes
if (typeof vnode.type === 'string') {
if (typeof vnode.type == 'string') {
handleDomVNode(vnode);
} else if (typeof vnode.type === 'function') {
} else if (typeof vnode.type == 'function') {
const shouldApplyRef =
'prototype' in vnode.type && vnode.type.prototype.render;
if ('ref' in vnode.props && shouldApplyRef) {
Expand Down Expand Up @@ -337,7 +333,7 @@ options.diffed = function (vnode) {

if (
dom != null &&
vnode.type === 'textarea' &&
vnode.type == 'textarea' &&
'value' in props &&
props.value !== dom.value
) {
Expand Down
4 changes: 2 additions & 2 deletions compat/src/suspense.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function detachedClone(vnode, detachedParent, parentDom) {

vnode = assign({ constructor: UNDEFINED }, vnode);
if (vnode._component != null) {
if (vnode._component._parentDom === parentDom) {
if (vnode._component._parentDom == parentDom) {
vnode._component._parentDom = detachedParent;
}

Expand Down Expand Up @@ -85,7 +85,7 @@ function removeOriginal(vnode, detachedParent, originalParent) {
);

if (vnode._component) {
if (vnode._component._parentDom === detachedParent) {
if (vnode._component._parentDom == detachedParent) {
if (vnode._dom) {
originalParent.appendChild(vnode._dom);
}
Expand Down
4 changes: 2 additions & 2 deletions compat/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export const assign = Object.assign;
* @returns {boolean}
*/
export function shallowDiffers(a, b) {
for (let i in a) if (i !== '__source' && a[i] !== b[i]) return true;
for (let i in b) if (i !== '__source' && !(i in a)) return true;
for (let i in a) if (i != '__source' && a[i] !== b[i]) return true;
for (let i in b) if (i != '__source' && !(i in a)) return true;
return false;
}

Expand Down
6 changes: 6 additions & 0 deletions compat/test/browser/isValidElement.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ describe('isValidElement', () => {
expect(isValidElement({})).to.equal(false);
});

it('should reject a boxed react element symbol', () => {
expect(
isValidElement({ $$typeof: Object(Symbol.for('react.element')) })
).to.equal(false);
});

it('should detect a preact vnode', () => {
expect(isValidElement(preactCreateElement('div', {}))).to.equal(true);
});
Expand Down
17 changes: 8 additions & 9 deletions hooks/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ options._render = vnode => {

const hooks = currentComponent.__hooks;
if (hooks) {
if (previousComponent === currentComponent) {
if (previousComponent == currentComponent) {
currentComponent._renderCallbacks = [];
} else {
hooks._pendingEffects.some(invokeCleanup);
Expand Down Expand Up @@ -93,11 +93,10 @@ options.diffed = vnode => {
const c = vnode._component;
if (c && c.__hooks) {
if (c.__hooks._pendingEffects.length) afterPaint(afterPaintEffects.push(c));
// `_pendingArgs` is cleared again by `options._render` before anything
// can read it, so committing it here is enough.
c.__hooks._list.some(hookItem => {
if (hookItem._pendingArgs) {
hookItem._args = hookItem._pendingArgs;
hookItem._pendingArgs = undefined;
}
if (hookItem._pendingArgs) hookItem._args = hookItem._pendingArgs;
});
}
previousComponent = currentComponent = null;
Expand Down Expand Up @@ -253,7 +252,7 @@ export function useReducer(reducer, initialState, init) {
// have values that aren't equal to one another this pushes
// us to update further down the tree
let updatedHook = false;
let shouldUpdate = this.props !== p;
let shouldUpdate = this.props != p;
hooks._list.some(hookItem => {
if (hookItem._nextValue) {
updatedHook = true;
Expand Down Expand Up @@ -434,7 +433,7 @@ export function useId() {
// Grab either the root node or the nearest async boundary node.
/** @type {import('./internal').VNode} */
let root = currentComponent._vnode;
while (root !== null && !root._mask && root._parent !== null) {
while (!root._mask && root._parent) {
root = root._parent;
}

Expand Down Expand Up @@ -514,7 +513,7 @@ function afterNextFrame(callback) {
* @returns {void}
*/
function afterPaint(newQueueLength) {
if (newQueueLength === 1 || prevRaf !== options.requestAnimationFrame) {
if (newQueueLength == 1 || prevRaf != options.requestAnimationFrame) {
prevRaf = options.requestAnimationFrame;
(prevRaf || afterNextFrame)(flushAfterPaintEffects);
}
Expand Down Expand Up @@ -558,7 +557,7 @@ function invokeEffect(hook) {
function argsChanged(oldArgs, newArgs) {
return (
!oldArgs ||
oldArgs.length !== newArgs.length ||
oldArgs.length != newArgs.length ||
newArgs.some((arg, index) => !ObjectIs(arg, oldArgs[index]))
);
}
Expand Down
22 changes: 11 additions & 11 deletions jsx-runtime/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ const CSS_REGEX = /[A-Z]/g;
* @returns {*}
*/
function normalizeAttrValue(value) {
return value !== null &&
typeof value === 'object' &&
typeof value.valueOf === 'function'
return value != null &&
typeof value == 'object' &&
typeof value.valueOf == 'function'
? value.valueOf()
: value;
}
Expand All @@ -110,13 +110,13 @@ function normalizeAttrValue(value) {
function jsxAttr(name, value) {
if (options.attr) {
const result = options.attr(name, value);
if (typeof result === 'string') return result;
if (typeof result == 'string') return result;
}

value = normalizeAttrValue(value);

if (name === 'ref' || name === 'key') return '';
if (name === 'style' && typeof value === 'object') {
if (name == 'ref' || name == 'key') return '';
if (name == 'style' && typeof value == 'object') {
let str = '';
for (let prop in value) {
let val = value[prop];
Expand All @@ -136,8 +136,8 @@ function jsxAttr(name, value) {
if (
value == null ||
value === false ||
typeof value === 'function' ||
typeof value === 'object'
typeof value == 'function' ||
typeof value == 'object'
) {
return '';
} else if (value === true) return name;
Expand All @@ -155,13 +155,13 @@ function jsxAttr(name, value) {
function jsxEscape(value) {
if (
value == null ||
typeof value === 'boolean' ||
typeof value === 'function'
typeof value == 'boolean' ||
typeof value == 'function'
) {
return null;
}

if (typeof value === 'object') {
if (typeof value == 'object') {
// Check for VNode
if (value.constructor === undefined) return value;

Expand Down
6 changes: 3 additions & 3 deletions jsx-runtime/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const ENCODED_ENTITIES = /["&<]/;
/** @param {string} str */
export function encodeEntities(str) {
// Skip all work for strings with no entities needing encoding:
if (str.length === 0 || ENCODED_ENTITIES.test(str) === false) return str;
if (!str.length || !ENCODED_ENTITIES.test(str)) return str;

let last = 0,
i = 0,
Expand All @@ -26,11 +26,11 @@ export function encodeEntities(str) {
continue;
}
// Append skipped/buffered characters and the encoded entity:
if (i !== last) out += str.slice(last, i);
if (i != last) out += str.slice(last, i);
out += ch;
// Start the next seek/buffer after the entity's offset:
last = i + 1;
}
if (i !== last) out += str.slice(last, i);
if (i != last) out += str.slice(last, i);
return out;
}
12 changes: 4 additions & 8 deletions src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function BaseComponent(props, context) {
BaseComponent.prototype.setState = function (update, callback) {
// only clone state when copying to nextState the first time.
let s;
if (this._nextState != NULL && this._nextState != this.state) {
if (this._nextState && this._nextState != this.state) {
s = this._nextState;
} else {
s = this._nextState = assign({}, this.state);
Expand Down Expand Up @@ -106,7 +106,7 @@ export function getDomSibling(vnode, childIndex) {
for (; childIndex < vnode._children.length; childIndex++) {
sibling = vnode._children[childIndex];

if (sibling != NULL && sibling._dom != NULL) {
if (sibling && sibling._dom) {
// Since updateParentDomPointers keeps _dom pointer correct,
// we can rely on _dom to tell us if this subtree contains a
// rendered DOM node, and what the first rendered DOM node is
Expand Down Expand Up @@ -169,14 +169,10 @@ function renderComponent(component) {
*/
function updateParentDomPointers(vnode) {
// Stop at root boundaries (_parentDom)
if (
(vnode = vnode._parent) &&
vnode._component &&
!vnode.props._parentDom
) {
if ((vnode = vnode._parent) && vnode._component && !vnode.props._parentDom) {
vnode._dom = NULL;
vnode._children.some(child => {
if (child != NULL && child._dom != NULL) {
if (child && child._dom) {
return (vnode._dom = child._dom);
}
});
Expand Down
10 changes: 2 additions & 8 deletions src/create-context.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { enqueueRender } from './component';
import { NULL, COMPONENT_FORCE } from './constants';
import { COMPONENT_FORCE } from './constants';

export let i = 0;

Expand All @@ -13,10 +13,6 @@ export function createContext(defaultValue) {

this.getChildContext = () => ctx;

this.componentWillUnmount = () => {
subs = NULL;
};

this.shouldComponentUpdate = function (_props) {
// @ts-expect-error even
if (this.props.value != _props.value) {
Expand All @@ -31,9 +27,7 @@ export function createContext(defaultValue) {
subs.add(c);
let old = c.componentWillUnmount;
c.componentWillUnmount = () => {
if (subs) {
subs.delete(c);
}
subs.delete(c);
if (old) old.call(c);
};
};
Expand Down
Loading
Loading