Skip to content

Commit

Permalink
Backport 10.8 changes (#3589)
Browse files Browse the repository at this point in the history
* backport svg shape rendering fix

* remove forceUpdate warning

* fixes to deferred hook commits

* add pendingvalue to mangle

* add export maps

* Update debug.js

* add server browser

* add text path

* improve compat types

* support nodenext

* containerInfo on portals

* improve act

* add client entry

* compatible

* oninput and onchange

* fix test

* less IE11
  • Loading branch information
JoviDeCroock authored Jun 24, 2022
1 parent 730604e commit d269b75
Show file tree
Hide file tree
Showing 29 changed files with 323 additions and 169 deletions.
19 changes: 19 additions & 0 deletions compat/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const { render, hydrate, unmountComponentAtNode } = require('preact/compat');

function createRoot(container) {
return {
render(children) {
render(children, container);
},
unmount() {
unmountComponentAtNode(container);
}
};
}

exports.createRoot = createRoot;

exports.hydrateRoot = function(container, children) {
hydrate(children, container);
return createRoot(container);
};
17 changes: 17 additions & 0 deletions compat/client.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { render, hydrate, unmountComponentAtNode } from 'preact/compat'

export function createRoot(container) {
return {
render(children) {
render(children, container)
},
unmount() {
unmountComponentAtNode(container)
}
}
}

export function hydrateRoot(container, children) {
hydrate(children, container)
return createRoot(container)
}
33 changes: 31 additions & 2 deletions compat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"version": "4.0.0",
"private": true,
"description": "A React compatibility layer for Preact",
"exports": "./dist/compat.mjs",
"module": "dist/compat.mjs",
"main": "dist/compat.js",
"umd:main": "dist/compat.umd.js",
Expand All @@ -16,5 +15,35 @@
},
"peerDependencies": {
"preact": "^10.0.0"
}
},
"exports": {
".": {
"types": "./src/index.d.ts",
"browser": "./dist/compat.module.js",
"umd": "./dist/compat.umd.js",
"import": "./dist/compat.mjs",
"require": "./dist/compat.js"
},
"./client": {
"import": "./client.mjs",
"require": "./client.js"
},
"./server": {
"browser": "./server.browser.js",
"import": "./server.mjs",
"require": "./server.js"
},
"./jsx-runtime": {
"import": "./jsx-runtime.mjs",
"require": "./jsx-runtime.js"
},
"./jsx-dev-runtime": {
"import": "./jsx-dev-runtime.mjs",
"require": "./jsx-dev-runtime.js"
},
"./scheduler": {
"import": "./scheduler.mjs",
"require": "./scheduler.js"
}
}
}
4 changes: 4 additions & 0 deletions compat/server.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
renderToString,
renderToString as renderToStaticMarkup
} from 'preact-render-to-string';
6 changes: 1 addition & 5 deletions compat/src/forwardRef.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ options._diff = (internal, vnode) => {
if (oldDiffHook) oldDiffHook(internal, vnode);
};

export const REACT_FORWARD_SYMBOL =
(typeof Symbol != 'undefined' &&
Symbol.for &&
Symbol.for('react.forward_ref')) ||
0xf47;
export const REACT_FORWARD_SYMBOL = Symbol.for('react.forward_ref');

/**
* Pass ref down to a child. This is mainly used in libraries with HOCs that
Expand Down
16 changes: 15 additions & 1 deletion compat/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ declare namespace React {
export import useState = _hooks.useState;

// Preact Defaults
export import ContextType = preact.ContextType;
export import RefObject = preact.RefObject;
export import Component = preact.Component;
export import FunctionComponent = preact.FunctionComponent;
export import FC = preact.FunctionComponent;
Expand All @@ -61,6 +63,16 @@ declare namespace React {
export import StrictMode = preact.Fragment;
export const version: string;

// HTML
export import HTMLAttributes = JSXInternal.HTMLAttributes;
export import DetailedHTMLProps = JSXInternal.DetailedHTMLProps;
export import CSSProperties = JSXInternal.CSSProperties;

// Events
export import TargetedEvent = JSXInternal.TargetedEvent;
export import ChangeEvent = JSXInternal.TargetedEvent;
export import ChangeEventHandler = JSXInternal.GenericEventHandler;

export function flushSync(cb: (arg: any) => any, arg: any): void;

export function createPortal(
Expand Down Expand Up @@ -91,7 +103,9 @@ declare namespace React {
...children: preact.ComponentChildren[]
) => preact.VNode<any>;
export function isValidElement(element: any): boolean;
export function findDOMNode(component: preact.Component): Element | null;
export function findDOMNode(
component: preact.Component | Element
): Element | null;

export abstract class PureComponent<P = {}, S = {}> extends preact.Component<
P,
Expand Down
25 changes: 13 additions & 12 deletions compat/src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,13 @@ import {
import { getParentContext } from '../../src/tree';
import { IS_NON_DIMENSIONAL } from './util';

export const REACT_ELEMENT_TYPE =
(typeof Symbol != 'undefined' && Symbol.for && Symbol.for('react.element')) ||
0xeac7;
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');

const CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;
const CAMEL_PROPS = /^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|shape|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;
const IS_DOM = typeof document !== 'undefined';

// Input types for which onchange should not be converted to oninput.
// type="file|checkbox|radio", plus "range" in IE11.
// (IE11 doesn't support Symbol, which we use here to turn `rad` into `ra` which matches "range")
const onChangeInputType = type =>
(typeof Symbol != 'undefined' && typeof Symbol() == 'symbol'
? /fil|che|rad/i
: /fil|che|ra/i
).test(type);
// type="file|checkbox|radio".
const onChangeInputType = type => /fil|che|rad/i.test(type);

// Some libraries like `react-virtualized` explicitly check for this.
Component.prototype.isReactComponent = {};
Expand Down Expand Up @@ -174,6 +166,15 @@ options.vnode = vnode => {
value = undefined;
}

// Add support for onInput and onChange, see #3561
// if we have an oninput prop already change it to oninputCapture
if (/^oninput$/i.test(i)) {
i = i.toLowerCase();
if (normalizedProps[i]) {
i = 'oninputCapture';
}
}

normalizedProps[i] = value;
}

Expand Down
28 changes: 0 additions & 28 deletions compat/test/browser/events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,34 +90,6 @@ describe('preact/compat events', () => {
);
});

it('should normalize onChange for range, except in IE11, including when IE11 has Symbol polyfill', () => {
// NOTE: we don't normalize `onchange` for range inputs in IE11.
// This test mimics a specific scenario when a Symbol polyfill may
// be present, in which case onChange should still not be normalized

const isIE11 = /Trident\//.test(navigator.userAgent);
const eventType = isIE11 ? 'change' : 'input';

if (isIE11) {
window.Symbol = () => 'mockSymbolPolyfill';
}
sinon.spy(window, 'Symbol');

render(<input type="range" onChange={() => null} />, scratch);
expect(window.Symbol).to.have.been.calledOnce;
expect(proto.addEventListener).to.have.been.calledOnce;
expect(proto.addEventListener).to.have.been.calledWithExactly(
eventType,
sinon.match.func,
false
);

window.Symbol.restore();
if (isIE11) {
window.Symbol = undefined;
}
});

it('should support onAnimationEnd', () => {
const func = sinon.spy(() => {});
render(<div onAnimationEnd={func} />, scratch);
Expand Down
36 changes: 36 additions & 0 deletions compat/test/browser/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,42 @@ describe('compat render', () => {
teardown(scratch);
});

it('should by pass onInputCapture normalization for custom onInputAction when using it along with onChange', () => {
const onInputAction = () => null;
const onChange = () => null;

let vnode = <textarea onChange={onChange} onInputAction={onInputAction} />;

expect(vnode.props).to.haveOwnProperty('onInputAction');
expect(vnode.props.onInputAction).to.equal(onInputAction);
expect(vnode.props).to.haveOwnProperty('oninput');
expect(vnode.props.oninput).to.equal(onChange);
expect(vnode.props).to.not.haveOwnProperty('oninputCapture');
});

it('should call onChange and onInput when input event is dispatched', () => {
const onChange = sinon.spy();
const onInput = sinon.spy();

render(<input onChange={onChange} onInput={onInput} />, scratch);

scratch.firstChild.dispatchEvent(createEvent('input'));

expect(onChange).to.be.calledOnce;
expect(onInput).to.be.calledOnce;

onChange.resetHistory();
onInput.resetHistory();

// change props order
render(<input onInput={onInput} onChange={onChange} />, scratch);

scratch.firstChild.dispatchEvent(createEvent('input'));

expect(onChange).to.be.calledOnce;
expect(onInput).to.be.calledOnce;
});

it('should render react-style jsx', () => {
let jsx = (
<div className="foo bar" data-foo="bar">
Expand Down
3 changes: 2 additions & 1 deletion compat/test/browser/svg.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ describe('svg', () => {
clipRule="value"
clipPathUnits="value"
glyphOrientationHorizontal="value"
shapeRendering="crispEdges"
glyphRef="value"
markerStart="value"
markerHeight="value"
Expand All @@ -87,7 +88,7 @@ describe('svg', () => {

expect(serializeHtml(scratch)).to.eql(
sortAttributes(
'<svg clip-path="value" clip-rule="value" clipPathUnits="value" glyph-orientationhorizontal="value" glyphRef="value" marker-start="value" markerHeight="value" markerUnits="value" markerWidth="value" x1="value" xChannelSelector="value"></svg>'
'<svg clip-path="value" clip-rule="value" clipPathUnits="value" glyph-orientationhorizontal="value" shape-rendering="crispEdges" glyphRef="value" marker-start="value" markerHeight="value" markerUnits="value" markerWidth="value" x1="value" xChannelSelector="value"></svg>'
)
);
});
Expand Down
11 changes: 9 additions & 2 deletions debug/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"version": "1.0.0",
"private": true,
"description": "Preact extensions for development",
"exports": "./dist/debug.mjs",
"module": "dist/debug.mjs",
"main": "dist/debug.js",
"umd:main": "dist/debug.umd.js",
Expand All @@ -15,5 +14,13 @@
},
"peerDependencies": {
"preact": "^10.0.0"
}
},
"exports": {
".": {
"browser": "./dist/debug.module.js",
"umd": "./dist/debug.umd.js",
"import": "./dist/debug.mjs",
"require": "./dist/debug.js"
}
}
}
24 changes: 0 additions & 24 deletions debug/src/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import {
getCurrentInternal,
getDisplayName
} from './component-stack';
// these constants get inlined at build time:
import { MODE_UNMOUNTING } from '../../src/constants';
import { IS_NON_DIMENSIONAL } from '../../compat/src/util';

const isWeakMapSupported = typeof WeakMap == 'function';
Expand Down Expand Up @@ -396,28 +394,6 @@ Component.prototype.setState = function(update, callback) {
return setState.call(this, update, callback);
};

const forceUpdate = Component.prototype.forceUpdate;

/** @this {import('../../src/internal').Component} */
Component.prototype.forceUpdate = function(callback) {
if (this._internal == null) {
console.warn(
`Calling "this.forceUpdate" inside the constructor of a component is a ` +
`no-op and might be a bug in your application.\n\n${getOwnerStack(
getCurrentInternal()
)}`
);
} else if (this._internal.flags & MODE_UNMOUNTING) {
console.warn(
`Can't call "this.forceUpdate" on an unmounted component. This is a no-op, ` +
`but it indicates a memory leak in your application. To fix, cancel all ` +
`subscriptions and asynchronous tasks in the componentWillUnmount method.` +
`\n\n${getOwnerStack(this._internal)}`
);
}
return forceUpdate.call(this, callback);
};

/**
* Serialize a vnode tree to a string
* @param {import('./internal').VNode} vnode
Expand Down
40 changes: 0 additions & 40 deletions debug/test/browser/debug.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,46 +204,6 @@ describe('debug', () => {
expect(console.warn).to.not.be.called;
});

it('should warn when calling forceUpdate inside the constructor', () => {
class Foo extends Component {
constructor(props) {
super(props);
this.forceUpdate();
}
render() {
return <div>foo</div>;
}
}

render(<Foo />, scratch);
expect(console.warn).to.be.calledOnce;
expect(console.warn.args[0]).to.match(/no-op/);
});

it('should warn when calling forceUpdate on an unmounted Component', () => {
let forceUpdate;

class Foo extends Component {
constructor(props) {
super(props);
forceUpdate = () => this.forceUpdate();
}
render() {
return <div>foo</div>;
}
}

render(<Foo />, scratch);
forceUpdate();
expect(console.warn).to.not.be.called;

render(null, scratch);

forceUpdate();
expect(console.warn).to.be.calledOnce;
expect(console.warn.args[0]).to.match(/no-op/);
});

it('should print an error when child is a plain object', () => {
let fn = () => render(<div>{{}}</div>, scratch);
expect(fn).to.throw(/not valid/);
Expand Down
Loading

0 comments on commit d269b75

Please sign in to comment.