Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/tiny-buttons-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'preact-render-to-string': patch
---

Add support for new component bits
20 changes: 12 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
HTML_LOWER_CASE,
HTML_ENUMERATED,
SVG_CAMEL_CASE,
createComponent
createComponent,
setDirty,
unsetDirty,
isDirty
} from './lib/util.js';
import { options, h, Fragment } from 'preact';
import {
Expand All @@ -15,7 +18,6 @@ import {
COMPONENT,
DIFF,
DIFFED,
DIRTY,
NEXT_STATE,
PARENT,
RENDER,
Expand Down Expand Up @@ -174,8 +176,9 @@ function renderClassComponent(vnode, context) {

c.props = vnode.props;
c.context = context;
// turn off stateful re-rendering:
c[DIRTY] = true;

// Turn off stateful rendering
setDirty(c);

if (c.state == null) c.state = EMPTY_OBJ;

Expand Down Expand Up @@ -370,8 +373,8 @@ function _renderToString(
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (component[DIRTY] && count++ < 25) {
component[DIRTY] = false;
while (isDirty(component) && count++ < 25) {
unsetDirty(component);

if (renderHook) renderHook(vnode);

Expand All @@ -384,7 +387,8 @@ function _renderToString(
throw e;
}
}
component[DIRTY] = true;

setDirty(component);
}

if (component.getChildContext != null) {
Expand Down Expand Up @@ -425,7 +429,7 @@ function _renderToString(
component.componentDidCatch(err, EMPTY_OBJ);
}

if (component[DIRTY]) {
if (isDirty(component)) {
rendered = renderClassComponent(vnode, context);
component = vnode[COMPONENT];

Expand Down
1 change: 1 addition & 0 deletions src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export const MASK = '__m';
// Component properties
export const VNODE = '__v';
export const DIRTY = '__d';
export const BITS = '__g';
export const NEXT_STATE = '__s';
export const CHILD_DID_SUSPEND = '__c';
26 changes: 26 additions & 0 deletions src/lib/util.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DIRTY, BITS } from './constants';

export const VOID_ELEMENTS = /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;
// oxlint-disable-next-line no-control-regex
export const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;
Expand All @@ -8,6 +10,30 @@ export const SVG_CAMEL_CASE = /^ac|^ali|arabic|basel|cap|clipPath$|clipRule$|col
// Boolean DOM properties that translate to enumerated ('true'/'false') attributes
export const HTML_ENUMERATED = new Set(['draggable', 'spellcheck']);

export const COMPONENT_DIRTY_BIT = 1 << 3;
export function setDirty(component) {
if (component[BITS] !== undefined) {
component[BITS] |= COMPONENT_DIRTY_BIT;
} else {
component[DIRTY] = true;
}
}

export function unsetDirty(component) {
if (component.__g !== undefined) {
component.__g &= ~COMPONENT_DIRTY_BIT;
} else {
component[DIRTY] = false;
}
}

export function isDirty(component) {
if (component.__g !== undefined) {
return (component.__g & COMPONENT_DIRTY_BIT) !== 0;
}
return component[DIRTY] === true;
}

// DOM properties that should NOT have "px" added when numeric
const ENCODED_ENTITIES = /["&<]/;

Expand Down
11 changes: 7 additions & 4 deletions src/pretty.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {
NAMESPACE_REPLACE_REGEX,
SVG_CAMEL_CASE,
HTML_LOWER_CASE,
getContext
getContext,
setDirty,
isDirty,
unsetDirty
} from './lib/util.js';
import { COMMIT, DIFF, DIFFED, RENDER, SKIP_EFFECTS } from './lib/constants.js';
import { options, Fragment } from 'preact';
Expand Down Expand Up @@ -139,8 +142,8 @@ function _renderToStringPretty(
// This will need to be updated for Preact 11 to use internal.flags rather than component._dirty:
// https://github.com/preactjs/preact/blob/d4ca6fdb19bc715e49fd144e69f7296b2f4daa40/src/diff/component.js#L35-L44
let count = 0;
while (c.__d && count++ < 25) {
c.__d = false;
while (isDirty(c) && count++ < 25) {
unsetDirty(c);

if (renderHook) renderHook(vnode);

Expand All @@ -154,7 +157,7 @@ function _renderToStringPretty(
c = vnode.__c = new nodeName(props, cctx);
c.__v = vnode;
// turn off stateful re-rendering:
c._dirty = c.__d = true;
setDirty(c);
c.props = props;
if (c.state == null) c.state = {};

Expand Down
8 changes: 6 additions & 2 deletions test/render.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'preact/hooks';
import { expect, vi, describe, it } from 'vitest';
import { svgAttributes, htmlAttributes } from './utils.jsx';
import { COMPONENT_DIRTY_BIT } from '../src/lib/util.js';

function shallowRender(vnode) {
const context = {};
Expand Down Expand Up @@ -942,8 +943,11 @@ describe('render', () => {

expect(render(<Foo />)).to.equal('<div></div>');

// expect(inst).to.have.property('_dirty', true);
expect(inst).to.have.property('__d', true);
if (inst.__g) {
expect(!!(inst.__g & COMPONENT_DIRTY_BIT)).to.be.true;
} else {
expect(inst).to.have.property('__d', true);
}
});

it('should prevent re-rendering', () => {
Expand Down