Skip to content

Commit

Permalink
chore: backport 10.12.x to v11 (#4255)
Browse files Browse the repository at this point in the history
* backport #3871

* port test from #3884 functionality seems to work in v11

* backport #3880

* backport #3875

* backport #3862

* add todo for #3801

* backport #3868

* backport #3867

* backport #3863

* add todo for #3856

* backport #3844

* backport #3816

* backport #3888

* backport #3889
  • Loading branch information
JoviDeCroock authored Jan 12, 2024
1 parent da7c661 commit 36ef4e8
Show file tree
Hide file tree
Showing 22 changed files with 2,005 additions and 791 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://preactjs.com" target="_blank">

![Preact](https://raw.githubusercontent.com/preactjs/preact/8b0bcc927995c188eca83cba30fbc83491cc0b2f/logo.svg?sanitize=true "Preact")

</a>
Expand All @@ -15,7 +15,6 @@
- Highly optimized diff algorithm and seamless hydration from Server Side Rendering
- Supports all modern browsers and IE11
- Transparent asynchronous rendering with a pluggable scheduler
- **Instant production-grade app setup with [Preact CLI](https://github.com/preactjs/preact-cli)**

### 💁 More information at the [Preact Website ➞](https://preactjs.com)

Expand Down Expand Up @@ -52,8 +51,6 @@ You can find some awesome libraries in the [awesome-preact list](https://github.

> 💁 _**Note:** You [don't need ES2015 to use Preact](https://github.com/developit/preact-in-es3)... but give it a try!_
The easiest way to get started with Preact is to install [Preact CLI](https://github.com/preactjs/preact-cli). This simple command-line tool wraps up the best possible tooling for you, and even keeps things like Webpack and Babel up-to-date as they change. Best of all, it's easy to understand! Start a project or compile for production in a single command (`preact build`), with no configuration needed and best practices baked in! 🙌

#### Tutorial: Building UI with Preact

With Preact, you create user interfaces by assembling trees of components and elements. Components are functions or classes that return a description of what their tree should output. These descriptions are typically written in [JSX](https://facebook.github.io/jsx/) (shown underneath), or [HTM](https://github.com/developit/htm) which leverages standard JavaScript Tagged Templates. Both syntaxes can express trees of elements with "props" (similar to HTML attributes) and children.
Expand Down
2 changes: 1 addition & 1 deletion compat/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const StrictMode = Fragment;
* @template Arg
* @template Result
* @param {(arg: Arg) => Result} callback function that runs before the flush
* @param {Arg} [arg] Optional arugment that can be passed to the callback
* @param {Arg} [arg] Optional argument that can be passed to the callback
* @returns
*/
const flushSync = (callback, arg) => callback(arg);
Expand Down
144 changes: 144 additions & 0 deletions compat/test/browser/context.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { setupRerender } from 'preact/test-utils';
import { setupScratch, teardown } from '../../../test/_util/helpers';
import React, {
render,
createElement,
createContext,
Component,
useState,
useContext
} from 'preact/compat';

describe('components', () => {
/** @type {HTMLDivElement} */
let scratch;

/** @type {() => void} */
let rerender;

beforeEach(() => {
scratch = setupScratch();
rerender = setupRerender();
});

afterEach(() => {
teardown(scratch);
});

it('nested context updates propagate throughout the tree synchronously', () => {
const RouterContext = createContext({ location: '__default_value__' });

const route1 = '/page/1';
const route2 = '/page/2';

/** @type {() => void} */
let toggleLocalState;
/** @type {() => void} */
let toggleLocation;

/** @type {Array<{location: string, localState: boolean}>} */
let pageRenders = [];

function runUpdate() {
toggleLocalState();
toggleLocation();
}

/**
* @extends {React.Component<{children: any}, {location: string}>}
*/
class Router extends Component {
constructor(props) {
super(props);
this.state = { location: route1 };
toggleLocation = () => {
const oldLocation = this.state.location;
const newLocation = oldLocation === route1 ? route2 : route1;
// console.log('Toggling location', oldLocation, '->', newLocation);
this.setState({ location: newLocation });
};
}

render() {
// console.log('Rendering Router', { location: this.state.location });
return (
<RouterContext.Provider value={{ location: this.state.location }}>
{this.props.children}
</RouterContext.Provider>
);
}
}

/**
* @extends {React.Component<{children: any}>}
*/
class Route extends Component {
render() {
return (
<RouterContext.Consumer>
{(contextValue) => {
// console.log('Rendering Route', {
// location: contextValue.location
// });
// Pretend to do something with the context value
const newContextValue = { ...contextValue };
return (
<RouterContext.Provider value={newContextValue}>
{this.props.children}
</RouterContext.Provider>
);
}}
</RouterContext.Consumer>
);
}
}

function Page() {
const [localState, setLocalState] = useState(true);
const { location } = useContext(RouterContext);

pageRenders.push({ location, localState });
// console.log('Rendering Page', { location, localState });

toggleLocalState = () => {
let newValue = !localState;
// console.log('Toggling localState', localState, '->', newValue);
setLocalState(newValue);
};

return (
<>
<div>localState: {localState.toString()}</div>
<div>location: {location}</div>
<div>
<button type="button" onClick={runUpdate}>
Trigger update
</button>
</div>
</>
);
}

function App() {
return (
<Router>
<Route>
<Page />
</Route>
</Router>
);
}

render(<App />, scratch);
expect(pageRenders).to.deep.equal([{ location: route1, localState: true }]);

pageRenders = [];
runUpdate(); // Simulate button click
rerender();

// Page should rerender once with both propagated context and local state updates
expect(pageRenders).to.deep.equal([
{ location: route2, localState: false }
]);
});
});
33 changes: 18 additions & 15 deletions compat/test/browser/suspense.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createLazy, createSuspender } from './suspense-utils';
const h = React.createElement;
/* eslint-env browser, mocha */

// TODO: https://github.com/preactjs/preact/pull/3856
class Catcher extends Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -192,7 +193,9 @@ describe('suspense', () => {
}
}

const FuncWrapper = props => <div id="func-wrapper">{props.children}</div>;
const FuncWrapper = (props) => (
<div id="func-wrapper">{props.children}</div>
);

const [Suspender, suspend] = createSuspender(() => <div>Hello</div>);

Expand Down Expand Up @@ -228,7 +231,7 @@ describe('suspense', () => {
/** @type {() => Promise<void>} */
let resolve;
let resolved = false;
const promise = new Promise(_resolve => {
const promise = new Promise((_resolve) => {
resolve = () => {
resolved = true;
_resolve();
Expand Down Expand Up @@ -296,7 +299,7 @@ describe('suspense', () => {
/** @type {() => Promise<void>} */
let resolve;
let resolved = false;
const promise = new Promise(_resolve => {
const promise = new Promise((_resolve) => {
resolve = () => {
resolved = true;
_resolve();
Expand Down Expand Up @@ -1290,15 +1293,15 @@ describe('suspense', () => {
/** @type {() => Promise<void>} */
let resolve;
let resolved = false;
const promise = new Promise(_resolve => {
const promise = new Promise((_resolve) => {
resolve = () => {
resolved = true;
_resolve();
return promise;
};
});

const Child = props => {
const Child = (props) => {
if (!resolved) {
throw promise;
}
Expand Down Expand Up @@ -1445,10 +1448,10 @@ describe('suspense', () => {
});

it('should allow same component to be suspended multiple times', async () => {
const cache = { '1': true };
const cache = { 1: true };
function Lazy({ value }) {
if (!cache[value]) {
throw new Promise(resolve => {
throw new Promise((resolve) => {
cache[value] = resolve;
});
}
Expand All @@ -1468,7 +1471,7 @@ describe('suspense', () => {
hide = () => {
this.setState({ show: false });
};
setValue = value => {
setValue = (value) => {
this.setState({ value });
};
}
Expand Down Expand Up @@ -1632,7 +1635,7 @@ describe('suspense', () => {
let increment;
function Updater() {
const [i, setState] = useState(0);
increment = () => setState(i => i + 1);
increment = () => setState((i) => i + 1);
return (
<div>
i: {i}
Expand Down Expand Up @@ -1688,7 +1691,7 @@ describe('suspense', () => {
let hide;

let suspender = null;
let suspenderRef = s => {
let suspenderRef = (s) => {
// skip null values as we want to keep the ref even after unmount
if (s) {
suspender = s;
Expand Down Expand Up @@ -1858,7 +1861,7 @@ describe('suspense', () => {
return this.state.content;
}
}
return [content => suspender.unsuspend(content), Suspender];
return [(content) => suspender.unsuspend(content), Suspender];
}

const [unsuspender1, Suspender1] = createSuspender();
Expand Down Expand Up @@ -2130,7 +2133,7 @@ describe('suspense', () => {
const suspense = (
<Suspense fallback={<div>Suspended...</div>}>
<ctx.Provider value="123">
<ctx.Consumer>{value => <Lazy value={value} />}</ctx.Consumer>
<ctx.Consumer>{(value) => <Lazy value={value} />}</ctx.Consumer>
</ctx.Provider>
</Suspense>
);
Expand All @@ -2139,7 +2142,7 @@ describe('suspense', () => {
rerender();
expect(scratch.innerHTML).to.equal(`<div>Suspended...</div>`);

return resolve(props => <div>{props.value}</div>).then(() => {
return resolve((props) => <div>{props.value}</div>).then(() => {
rerender();
expect(scratch.innerHTML).to.eql(`<div>123</div>`);
});
Expand All @@ -2154,7 +2157,7 @@ describe('suspense', () => {
const suspense = (
<Suspense fallback={<div>Suspended...</div>}>
<ctx.Provider value="123">
<ctx.Consumer>{value => <Suspender value={value} />}</ctx.Consumer>
<ctx.Consumer>{(value) => <Suspender value={value} />}</ctx.Consumer>
</ctx.Provider>
</Suspense>
);
Expand All @@ -2166,7 +2169,7 @@ describe('suspense', () => {
rerender();
expect(scratch.innerHTML).to.equal(`<div>Suspended...</div>`);

return resolve(props => <div>{props.value}</div>).then(() => {
return resolve((props) => <div>{props.value}</div>).then(() => {
rerender();
expect(scratch.innerHTML).to.eql(`<div>123</div>`);
});
Expand Down
9 changes: 5 additions & 4 deletions debug/src/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function initDebug() {
try {
oldCatchError(error, vnode, oldVNode);

// when an error was handled by an ErrorBoundary we will nontheless emit an error
// when an error was handled by an ErrorBoundary we will nonetheless emit an error
// event on the window object. This is to make up for react compatibility in dev mode
// and thus make the Next.js dev overlay work.
if (typeof error.then != 'function') {
Expand Down Expand Up @@ -120,6 +120,7 @@ export function initDebug() {
// `<MyJSONFormatter>{{ foo: 123, bar: "abc" }}</MyJSONFormatter>`).
if (vnode.constructor !== undefined) {
const keys = Object.keys(vnode).join(',');
// TODO: https://github.com/preactjs/preact/pull/3801
throw new Error(
`Objects are not valid as a child. Encountered an object with the keys {${keys}}.` +
`\n\n${getOwnerStack(internal)}`
Expand Down Expand Up @@ -327,7 +328,7 @@ export function initDebug() {
// https://esbench.com/bench/6021ebd7d9c27600a7bfdba3
const deprecatedProto = Object.create({}, deprecatedAttributes);

options.vnode = vnode => {
options.vnode = (vnode) => {
const props = vnode.props;
if (props != null && ('__source' in props || '__self' in props)) {
Object.defineProperties(props, debugProps);
Expand All @@ -340,7 +341,7 @@ export function initDebug() {
if (oldVnode) oldVnode(vnode);
};

options.diffed = vnode => {
options.diffed = (vnode) => {
hooksAllowed = false;

if (oldDiffed) oldDiffed(vnode);
Expand Down Expand Up @@ -374,7 +375,7 @@ export function initDebug() {
const setState = Component.prototype.setState;

/** @this {import('../../src/internal').Component} */
Component.prototype.setState = function(update, callback) {
Component.prototype.setState = function (update, callback) {
if (this._internal == null) {
// `this._internal` will be `null` during componentWillMount. But it
// is perfectly valid to call `setState` during cWM. So we
Expand Down
7 changes: 4 additions & 3 deletions jsx-runtime/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ let vnodeId = 0;
* @param {VNode['type']} type
* @param {VNode['props']} props
* @param {VNode['key']} [key]
* @param {string} [__source]
* @param {string} [__self]
* @param {unknown} [isStaticChildren]
* @param {unknown} [__source]
* @param {unknown} [__self]
*/
function createVNode(type, props, key, __source, __self) {
function createVNode(type, props, key, isStaticChildren, __source, __self) {
// We'll want to preserve `ref` in props to get rid of the need for
// forwardRef components in the future, but that should happen via
// a separate PR.
Expand Down
4 changes: 2 additions & 2 deletions jsx-runtime/test/browser/jsx-runtime.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, createElement, createRef } from 'preact';
import { createElement, createRef } from 'preact';
import { jsx, jsxs, jsxDEV, Fragment } from 'preact/jsx-runtime';
import { setupScratch, teardown } from '../../../test/_util/helpers';

Expand Down Expand Up @@ -32,7 +32,7 @@ describe('Babel jsx/jsxDEV', () => {
});

it('should set __source and __self', () => {
const vnode = jsx('div', { class: 'foo' }, 'key', 'source', 'self');
const vnode = jsx('div', { class: 'foo' }, 'key', false, 'source', 'self');
expect(vnode.__source).to.equal('source');
expect(vnode.__self).to.equal('self');
});
Expand Down
Loading

0 comments on commit 36ef4e8

Please sign in to comment.