Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: backport 10.12.x to v11 #4255

Merged
merged 14 commits into from
Jan 12, 2024
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: 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
Loading