;
191 | // For safety
192 | if (data.__uwrType !== "patch") return;
193 | const { id, patches } = data;
194 | if (!pendingIds.has(id)) return;
195 | pendingIds.delete(id);
196 | if (pendingIds.size === 0) setBusy(false);
197 | setState((state) => {
198 | return applyPatches(state, patches);
199 | });
200 | }
201 | worker.addEventListener("message", listener);
202 | send({ __uwrType: "init", initialState });
203 | return () => {
204 | worker.removeEventListener("message", listener);
205 | send({ __uwrType: "destroy" });
206 | };
207 | }, []);
208 |
209 | return [state, dispatch, isBusy];
210 | }
211 |
--------------------------------------------------------------------------------
/tests/basic/app.jsx:
--------------------------------------------------------------------------------
1 | /* @jsx h */
2 |
3 | import { h, render } from "preact";
4 | import { useEffect } from "preact/hooks";
5 | import { testSuite, externalPromise } from "/tests/test-utils.js";
6 | import { useWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
7 |
8 | const worker = new Worker(new URL("./worker.js", import.meta.url), {
9 | type: "module",
10 | });
11 |
12 | testSuite("Basic reducer functionality", async () => {
13 | const [promise, resolve] = externalPromise();
14 | function App() {
15 | const [state, dispatch] = useWorkerizedReducer(worker, "reducer", {
16 | counter: 0,
17 | });
18 |
19 | useEffect(() => {
20 | if (state.counter === 0) dispatch({ type: "increment" });
21 | if (state.counter === 1) resolve();
22 | }, [state]);
23 |
24 | return {JSON.stringify(state, null, " ")}
;
25 | }
26 |
27 | render(, document.all.main);
28 |
29 | return promise;
30 | });
31 |
--------------------------------------------------------------------------------
/tests/basic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/basic/worker.js:
--------------------------------------------------------------------------------
1 | import { initWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
2 |
3 | initWorkerizedReducer("reducer", (state, { type }) => {
4 | if (type === "increment") {
5 | state.counter += 1;
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
40 |
--------------------------------------------------------------------------------
/tests/instant-dispatch/app.jsx:
--------------------------------------------------------------------------------
1 | /* @jsx h */
2 |
3 | import { h, render } from "preact";
4 | import { useEffect } from "preact/hooks";
5 | import { testSuite, externalPromise } from "/tests/test-utils.js";
6 | import { useWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
7 |
8 | const worker = new Worker(new URL("./worker.js", import.meta.url), {
9 | type: "module",
10 | });
11 |
12 | testSuite("Dispatching on mount", async () => {
13 | const [promise, resolve] = externalPromise();
14 | function App() {
15 | const [state, dispatch] = useWorkerizedReducer(worker, "reducer", {
16 | counter: 0,
17 | });
18 |
19 | useEffect(() => dispatch({ type: "increment" }), []);
20 | if (state?.counter === 1) resolve();
21 |
22 | return {JSON.stringify(state, null, " ")}
;
23 | }
24 |
25 | render(, document.all.main);
26 |
27 | return promise;
28 | });
29 |
--------------------------------------------------------------------------------
/tests/instant-dispatch/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/instant-dispatch/worker.js:
--------------------------------------------------------------------------------
1 | import { initWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
2 |
3 | initWorkerizedReducer("reducer", (state, { type }) => {
4 | if (type === "increment") {
5 | state.counter += 1;
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/tests/local-state/app.jsx:
--------------------------------------------------------------------------------
1 | /* @jsx h */
2 |
3 | import { h, render } from "preact";
4 | import { useEffect } from "preact/hooks";
5 | import { testSuite, externalPromise } from "/tests/test-utils.js";
6 | import { useWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
7 |
8 | const worker = new Worker(new URL("./worker.js", import.meta.url), {
9 | type: "module",
10 | });
11 |
12 | testSuite("Holds local state", async () => {
13 | const [promise, resolve] = externalPromise();
14 | function App() {
15 | const [state, dispatch] = useWorkerizedReducer(worker, "reducer", {
16 | counter: 0,
17 | });
18 |
19 | useEffect(() => {
20 | if (state.counter === 0) dispatch({});
21 | if (state.counter === 1) resolve();
22 | }, [state]);
23 |
24 | return {JSON.stringify(state, null, " ")}
;
25 | }
26 |
27 | render(, document.all.main);
28 |
29 | return promise;
30 | });
31 |
--------------------------------------------------------------------------------
/tests/local-state/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/local-state/worker.js:
--------------------------------------------------------------------------------
1 | import { initWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
2 |
3 | initWorkerizedReducer(
4 | "reducer",
5 | (state, action, { increment }) => {
6 | state.counter += increment;
7 | },
8 | () => ({ increment: 1 })
9 | );
10 |
--------------------------------------------------------------------------------
/tests/multi-component/app.jsx:
--------------------------------------------------------------------------------
1 | /* @jsx h */
2 |
3 | import { h, render, Fragment } from "preact";
4 | import { useEffect } from "preact/hooks";
5 | import { testSuite, externalPromise } from "/tests/test-utils.js";
6 | import { useWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
7 |
8 | const worker = new Worker(new URL("./worker.js", import.meta.url), {
9 | type: "module",
10 | });
11 |
12 | testSuite("Reusing a component", async () => {
13 | const [promise, resolve, reject] = externalPromise();
14 | const checkState = { counter1: -1, counter2: -1 };
15 | function check() {
16 | if (checkState.counter2 > 0) reject("Counter 2 got incremented");
17 | if (checkState.counter1 == 1 && checkState.counter2 == 0) resolve();
18 | }
19 |
20 | function Counter({ name, autoIncrement = false }) {
21 | const [state, dispatch] = useWorkerizedReducer(worker, "reducer", {
22 | counter: 0,
23 | });
24 |
25 | checkState[name] = state?.counter;
26 | check();
27 |
28 | useEffect(() => {
29 | if (autoIncrement) dispatch({ type: "increment" });
30 | }, []);
31 |
32 | return {JSON.stringify(state, null, " ")}
;
33 | }
34 |
35 | function App() {
36 | return (
37 |
38 |
39 |
40 |
41 | );
42 | }
43 |
44 | render(, document.all.main);
45 |
46 | return promise;
47 | });
48 |
--------------------------------------------------------------------------------
/tests/multi-component/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/multi-component/worker.js:
--------------------------------------------------------------------------------
1 | import { initWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
2 |
3 | initWorkerizedReducer("reducer", (state, { type }) => {
4 | if (type === "increment") {
5 | state.counter += 1;
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/tests/remount/app.jsx:
--------------------------------------------------------------------------------
1 | /* @jsx h */
2 |
3 | import { h, render } from "preact";
4 | import { useState, useEffect } from "preact/hooks";
5 | import { testSuite, externalPromise } from "/tests/test-utils.js";
6 | import { useWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
7 |
8 | const worker = new Worker(new URL("./worker.js", import.meta.url), {
9 | type: "module",
10 | });
11 |
12 | testSuite("Remounting", async () => {
13 | const [promise, resolve] = externalPromise();
14 | function Counter({ provideState }) {
15 | const [state, dispatch] = useWorkerizedReducer(worker, "reducer", {
16 | counter: 0,
17 | });
18 |
19 | useEffect(() => provideState({ state, dispatch }), [state]);
20 | return {JSON.stringify(state, null, " ")}
;
21 | }
22 |
23 | function App() {
24 | const [state, setState] = useState(0);
25 |
26 | if (state === 0) {
27 | return (
28 |
29 | {state}:{" "}
30 | {
32 | if (state?.counter != 0) return;
33 | dispatch({ type: "increment" });
34 | setState((state) => state + 1);
35 | }}
36 | />
37 |
38 | );
39 | } else if (state === 1) {
40 | setState((state) => state + 1);
41 | return {state}
;
42 | } else if (state === 2) {
43 | return (
44 |
45 | {state}
46 | {
48 | if (state?.counter == 0) resolve();
49 | }}
50 | />
51 |
52 | );
53 | }
54 | }
55 |
56 | render(, document.all.main);
57 |
58 | return promise;
59 | });
60 |
--------------------------------------------------------------------------------
/tests/remount/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/tests/remount/worker.js:
--------------------------------------------------------------------------------
1 | import { initWorkerizedReducer } from "/dist/preact/use-workerized-reducer.es.js";
2 |
3 | initWorkerizedReducer("reducer", (state, { type }) => {
4 | if (type === "increment") {
5 | state.counter += 1;
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/tests/test-utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2021 Google Inc. All Rights Reserved.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | * Unless required by applicable law or agreed to in writing, software
8 | * distributed under the License is distributed on an "AS IS" BASIS,
9 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | * See the License for the specific language governing permissions and
11 | * limitations under the License.
12 | */
13 |
14 | export function externalPromise() {
15 | let resolve, reject;
16 | const promise = new Promise((resolve_, reject_) => {
17 | resolve = resolve_;
18 | reject = reject_;
19 | });
20 | return [promise, resolve, reject];
21 | }
22 |
23 | export function doubleRaf() {
24 | return new Promise((resolve) => {
25 | requestAnimationFrame(() => {
26 | requestAnimationFrame(() => {
27 | resolve();
28 | });
29 | });
30 | });
31 | }
32 |
33 | export function fail(msg) {
34 | window.parent?.postMessage(msg, "*");
35 | }
36 |
37 | export function success() {
38 | window.parent?.postMessage(true, "*");
39 | }
40 |
41 | export function nextEvent(el, name) {
42 | return new Promise((resolve) =>
43 | el.addEventListener(name, resolve, { once: true })
44 | );
45 | }
46 |
47 | export function assert(bool, msg) {
48 | if (!bool) {
49 | throw Error(msg);
50 | }
51 | }
52 |
53 | export function assertEquals(a, b, msg) {
54 | if (a !== b) {
55 | throw Error(`Expected ${a} == ${b}. ${msg}`);
56 | }
57 | }
58 |
59 | export function timeout(ms) {
60 | return new Promise((resolve) => setTimeout(resolve, ms));
61 | }
62 |
63 | export async function testSuite(name, cb) {
64 | try {
65 | await Promise.race([
66 | cb(),
67 | timeout(2000).then(() => {
68 | throw Error(`Timeout`);
69 | }),
70 | ]);
71 | } catch (e) {
72 | console.error(e);
73 | fail(`${name}: ${e}`);
74 | return;
75 | }
76 | success();
77 | console.log("Test passed successfully");
78 | }
79 |
--------------------------------------------------------------------------------
/vite.baseconfig.js:
--------------------------------------------------------------------------------
1 | export default function (variant) {
2 | return {
3 | outDir: "./dist",
4 | build: {
5 | target: "es2019",
6 | emptyOutDir: false,
7 | lib: {
8 | entry: [
9 | "./src/use-workerized-reducer",
10 | variant !== "generic" ? variant : null,
11 | "ts",
12 | ]
13 | .filter(Boolean)
14 | .join("."),
15 | fileName: `${variant}/use-workerized-reducer`,
16 | formats: ["es"],
17 | },
18 | rollupOptions: {
19 | external: ["preact", "react", "preact/hooks"],
20 | },
21 | },
22 | optimizeDeps: {},
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/vite.config.generic.js:
--------------------------------------------------------------------------------
1 | import generateConfig from "./vite.baseconfig.js";
2 |
3 | export default generateConfig("generic");
4 |
--------------------------------------------------------------------------------
/vite.config.preact.js:
--------------------------------------------------------------------------------
1 | import generateConfig from "./vite.baseconfig.js";
2 |
3 | export default generateConfig("preact");
4 |
--------------------------------------------------------------------------------
/vite.config.react.js:
--------------------------------------------------------------------------------
1 | import generateConfig from "./vite.baseconfig.js";
2 |
3 | export default generateConfig("react");
4 |
--------------------------------------------------------------------------------