├── .gitignore ├── src ├── index.ts ├── expose.ts └── wrap.ts ├── examples ├── 01_minimal │ ├── src │ │ ├── slow_fib.worker.js │ │ └── index.js │ ├── public │ │ └── index.html │ └── package.json └── 02_typescript │ ├── src │ ├── slow_fib.worker.js │ ├── index.ts │ ├── App.tsx │ ├── Main.tsx │ └── DisplayFib.tsx │ ├── public │ └── index.html │ └── package.json ├── CHANGELOG.md ├── __tests__ └── 01_basic_spec.ts ├── tsconfig.json ├── .github └── workflows │ ├── ci.yml │ └── cd.yml ├── webpack.config.js ├── LICENSE ├── .eslintrc.json ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | node_modules 4 | /dist 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { expose } from './expose'; 2 | export { wrap } from './wrap'; 3 | -------------------------------------------------------------------------------- /examples/01_minimal/src/slow_fib.worker.js: -------------------------------------------------------------------------------- 1 | import { expose } from 'react-suspense-worker'; 2 | 3 | const fib = (i) => (i <= 1 ? i : fib(i - 1) + fib(i - 2)); 4 | 5 | expose(fib); 6 | -------------------------------------------------------------------------------- /examples/02_typescript/src/slow_fib.worker.js: -------------------------------------------------------------------------------- 1 | import { expose } from 'react-suspense-worker'; 2 | 3 | const fib = (i) => (i <= 1 ? i : fib(i - 1) + fib(i - 2)); 4 | 5 | expose(fib); 6 | -------------------------------------------------------------------------------- /examples/01_minimal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | react-suspense-worker example 4 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/02_typescript/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | react-suspense-worker example 4 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.1.0] - 2022-06-09 6 | ### Changed 7 | - Update with React 18 8 | 9 | ## [0.0.1] - 2020-01-19 10 | ### Added 11 | - Initial release 12 | -------------------------------------------------------------------------------- /__tests__/01_basic_spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | expose, 3 | wrap, 4 | } from '../src/index'; 5 | 6 | describe('basic spec', () => { 7 | it('exported function', () => { 8 | expect(expose).toBeDefined(); 9 | expect(wrap).toBeDefined(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /examples/02_typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import App from './App'; 5 | 6 | const ele = document.getElementById('app'); 7 | if (!ele) throw new Error('no app'); 8 | createRoot(ele).render(React.createElement(App)); 9 | -------------------------------------------------------------------------------- /examples/02_typescript/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | 3 | import Main from './Main'; 4 | 5 | const App: React.FC = () => ( 6 | Loading...}> 7 |
8 |
9 |
10 |
11 | ); 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /src/expose.ts: -------------------------------------------------------------------------------- 1 | import { expose as comlinkExpose } from 'comlink'; 2 | 3 | /** 4 | * Expose a value in worker thread to be wrapped in main thread 5 | * 6 | * @example 7 | * import { expose } from 'react-suspense-worker'; 8 | * 9 | * const fib = (i) => (i <= 1 ? i : fib(i - 1) + fib(i - 2)); 10 | * 11 | * expose(fib); 12 | */ 13 | export const expose = comlinkExpose; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "target": "es5", 5 | "esModuleInterop": true, 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "allowJs": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "noUncheckedIndexedAccess": true, 13 | "exactOptionalPropertyTypes": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "paths": { 17 | "react-suspense-worker": ["./src"] 18 | }, 19 | "outDir": "./dist" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/01_minimal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-suspense-worker-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "react": "latest", 7 | "react-dom": "latest", 8 | "react-suspense-worker": "latest", 9 | "react-scripts": "latest" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "browserslist": [ 18 | ">0.2%", 19 | "not dead", 20 | "not ie <= 11", 21 | "not op_mini all" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /examples/02_typescript/src/Main.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useTransition } from 'react'; 2 | 3 | import DisplayFib from './DisplayFib'; 4 | 5 | const Main: React.FC = () => { 6 | const [number, setNumber] = useState(1); 7 | const [isPending, startTransition] = useTransition(); 8 | const onClick = () => { 9 | startTransition(() => { 10 | setNumber((c) => c + 1); 11 | }); 12 | }; 13 | return ( 14 |
15 | number: {number} 16 | 17 | {isPending && 'Pending...'} 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default Main; 24 | -------------------------------------------------------------------------------- /examples/02_typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-suspense-worker-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/react": "latest", 7 | "@types/react-dom": "latest", 8 | "react": "latest", 9 | "react-dom": "latest", 10 | "react-suspense-worker": "latest", 11 | "react-scripts": "latest", 12 | "typescript": "latest" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "browserslist": [ 21 | ">0.2%", 22 | "not dead", 23 | "not ie <= 11", 24 | "not op_mini all" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/02_typescript/src/DisplayFib.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { wrap } from 'react-suspense-worker'; 4 | 5 | type Fib = (i: number) => number; 6 | 7 | const fib = wrap(new Worker(new URL('./slow_fib.worker', import.meta.url))); 8 | 9 | const fastFib = (i: number) => { 10 | if (i <= 1) return i; 11 | let a = 0; 12 | let b = 1; 13 | for (let x = 1; x < i; x += 1) { 14 | [a, b] = [b, a + b]; 15 | } 16 | return b; 17 | }; 18 | 19 | type Props = { 20 | number: number; 21 | }; 22 | 23 | const DisplayFib: React.FC = ({ number }) => { 24 | const result = fib(number); 25 | const result2 = fastFib(number); 26 | return ( 27 |
28 |
result: {result}
29 |
result2: {result2}
30 |
31 | ); 32 | }; 33 | 34 | export default DisplayFib; 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Setup Node 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: '12.x' 17 | 18 | - name: Get yarn cache 19 | id: yarn-cache 20 | run: echo "::set-output name=dir::$(yarn cache dir)" 21 | 22 | - name: Cache dependencies 23 | uses: actions/cache@v1 24 | with: 25 | path: ${{ steps.yarn-cache.outputs.dir }} 26 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 27 | restore-keys: | 28 | ${{ runner.os }}-yarn- 29 | 30 | - name: Install dependencies 31 | run: yarn install 32 | 33 | - name: Test 34 | run: yarn test 35 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const { DIR, EXT = 'ts' } = process.env; 5 | 6 | module.exports = { 7 | mode: 'development', 8 | devtool: 'cheap-module-source-map', 9 | entry: `./examples/${DIR}/src/index.${EXT}`, 10 | output: { 11 | publicPath: '/', 12 | }, 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: `./examples/${DIR}/public/index.html`, 16 | }), 17 | ], 18 | module: { 19 | rules: [{ 20 | test: /\.[jt]sx?$/, 21 | exclude: /node_modules/, 22 | loader: 'ts-loader', 23 | options: { 24 | transpileOnly: true, 25 | }, 26 | }], 27 | }, 28 | resolve: { 29 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 30 | alias: { 31 | 'react-suspense-worker': `${__dirname}/src`, 32 | }, 33 | }, 34 | devServer: { 35 | port: process.env.PORT || '8080', 36 | static: { 37 | directory: `./examples/${DIR}/public`, 38 | }, 39 | historyApiFallback: true, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Setup Node 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: '12.x' 18 | registry-url: 'https://registry.npmjs.org' 19 | 20 | - name: Get yarn cache 21 | id: yarn-cache 22 | run: echo "::set-output name=dir::$(yarn cache dir)" 23 | 24 | - name: Cache dependencies 25 | uses: actions/cache@v1 26 | with: 27 | path: ${{ steps.yarn-cache.outputs.dir }} 28 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-yarn- 31 | 32 | - name: Install dependencies 33 | run: yarn install 34 | 35 | - name: Test 36 | run: yarn test 37 | 38 | - name: Compile 39 | run: yarn run compile 40 | 41 | - name: Publish 42 | run: npm publish 43 | env: 44 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2022 Daishi Kato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint", 5 | "react-hooks" 6 | ], 7 | "extends": [ 8 | "plugin:@typescript-eslint/recommended", 9 | "airbnb" 10 | ], 11 | "env": { 12 | "browser": true 13 | }, 14 | "settings": { 15 | "import/resolver": { 16 | "node": { 17 | "extensions": [".js", ".ts", ".tsx"] 18 | } 19 | } 20 | }, 21 | "rules": { 22 | "react-hooks/rules-of-hooks": "error", 23 | "@typescript-eslint/explicit-function-return-type": "off", 24 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".tsx"] }], 25 | "react/prop-types": "off", 26 | "react/jsx-one-expression-per-line": "off", 27 | "import/extensions": ["error", "never"], 28 | "import/prefer-default-export": "off", 29 | "import/no-unresolved": ["error", { "ignore": ["react-suspense-worker"] }], 30 | "@typescript-eslint/no-explicit-any": "off", 31 | "no-use-before-define": "off", 32 | "no-unused-vars": "off", 33 | "react/function-component-definition": ["error", { "namedComponents": "arrow-function" }] 34 | }, 35 | "overrides": [{ 36 | "files": ["__tests__/**/*"], 37 | "env": { 38 | "jest": true 39 | }, 40 | "rules": { 41 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] 42 | } 43 | }] 44 | } 45 | -------------------------------------------------------------------------------- /examples/01_minimal/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense, useState, useTransition } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import { wrap } from 'react-suspense-worker'; 5 | 6 | const fib = wrap(new Worker(new URL('./slow_fib.worker', import.meta.url))); 7 | 8 | const fastFib = (i) => { 9 | if (i <= 1) return i; 10 | let a = 0; 11 | let b = 1; 12 | for (let x = 1; x < i; x += 1) { 13 | [a, b] = [b, a + b]; 14 | } 15 | return b; 16 | }; 17 | 18 | const DisplayFib = ({ number }) => { 19 | const result = fib(number); 20 | const result2 = fastFib(number); 21 | return ( 22 |
23 |
result: {result}
24 |
result2: {result2}
25 |
26 | ); 27 | }; 28 | 29 | const Main = () => { 30 | const [number, setNumber] = useState(1); 31 | const [isPending, startTransition] = useTransition(); 32 | const onClick = () => { 33 | startTransition(() => { 34 | setNumber((c) => c + 1); 35 | }); 36 | }; 37 | return ( 38 |
39 | number: {number} 40 | 41 | {isPending && 'Pending...'} 42 | 43 |
44 | ); 45 | }; 46 | 47 | const App = () => ( 48 | Loading...}> 49 |
50 | 51 | ); 52 | 53 | createRoot(document.getElementById('app')).render(); 54 | -------------------------------------------------------------------------------- /src/wrap.ts: -------------------------------------------------------------------------------- 1 | import { wrap as comlinkWrap, Endpoint } from 'comlink'; 2 | 3 | const isPromise = (x: unknown): x is Promise => ( 4 | !!x && typeof (x as Promise).then === 'function' 5 | ); 6 | 7 | const isObject = (x: unknown): x is object => typeof x === 'object' && x !== null; 8 | 9 | const createProxy = (remote: any): any => { 10 | const cacheForGet = new Map(); 11 | const cacheForApply = new Map(); 12 | return new Proxy(remote, { 13 | get(target, key) { 14 | if (cacheForGet.has(key)) { 15 | const result = cacheForGet.get(key); 16 | // cacheForGet.delete(key); 17 | if (isObject(result)) { 18 | return createProxy(result); 19 | } 20 | return result; 21 | } 22 | const result = target[key]; 23 | if (isPromise(result)) { 24 | throw result.then((r) => { cacheForGet.set(key, r); }); 25 | } 26 | if (isObject(result)) { 27 | return createProxy(result); 28 | } 29 | return result; 30 | }, 31 | apply(target, thisArg, args) { 32 | const key = JSON.stringify(args); // hoping args is small and stringify is fast 33 | if (cacheForApply.has(key)) { 34 | const result = cacheForApply.get(key); 35 | // cacheForApply.delete(key); 36 | if (isObject(result)) { 37 | return createProxy(result); 38 | } 39 | return result; 40 | } 41 | const result = target.apply(thisArg, args); 42 | if (isPromise(result)) { 43 | throw result.then((r) => { cacheForApply.set(key, r); }); 44 | } 45 | if (isObject(result)) { 46 | return createProxy(result); 47 | } 48 | return result; 49 | }, 50 | }); 51 | }; 52 | 53 | /** 54 | * Wrap a worker to be used with React Suspense 55 | * 56 | * @example 57 | * import { wrap } from 'react-suspense-worker'; 58 | * 59 | * const fib = wrap(new Worker(new URL('./slow_fib.worker', import.meta.url))); 60 | * 61 | * const DisplayFib = ({ number }) => { 62 | * const result = fib(number); 63 | * return
result: {result}
; 64 | * }; 65 | */ 66 | export const wrap = (ep: Endpoint): T => { 67 | const remote = comlinkWrap(ep); 68 | const proxy = createProxy(remote); 69 | return proxy as T; 70 | }; 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-suspense-worker", 3 | "description": "React Suspense for Web Worker with Comlink", 4 | "version": "0.1.0", 5 | "author": "Daishi Kato", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/dai-shi/react-suspense-worker.git" 9 | }, 10 | "source": "./src/index.ts", 11 | "main": "./dist/index.umd.js", 12 | "module": "./dist/index.modern.js", 13 | "types": "./dist/src/index.d.ts", 14 | "exports": { 15 | "./package.json": "./package.json", 16 | ".": { 17 | "types": "./dist/src/index.d.ts", 18 | "module": "./dist/index.modern.js", 19 | "import": "./dist/index.modern.mjs", 20 | "default": "./dist/index.umd.js" 21 | } 22 | }, 23 | "sideEffects": false, 24 | "files": [ 25 | "src", 26 | "dist" 27 | ], 28 | "scripts": { 29 | "compile": "microbundle build -f modern,umd --globals comlink=Comlink", 30 | "postcompile": "cp dist/index.modern.js dist/index.modern.mjs && cp dist/index.modern.js.map dist/index.modern.mjs.map", 31 | "test": "run-s eslint tsc-test jest", 32 | "eslint": "eslint --ext .js,.ts,.tsx --ignore-pattern dist .", 33 | "jest": "jest", 34 | "tsc-test": "tsc --project . --noEmit", 35 | "apidoc": "documentation readme --section API --markdown-toc false --parse-extension ts --require-extension ts src/*.ts", 36 | "examples:01_minimal": "DIR=01_minimal EXT=js webpack serve", 37 | "examples:02_typescript": "DIR=02_typescript webpack serve" 38 | }, 39 | "jest": { 40 | "preset": "ts-jest/presets/js-with-ts" 41 | }, 42 | "keywords": [ 43 | "react", 44 | "suspense", 45 | "worker" 46 | ], 47 | "license": "MIT", 48 | "dependencies": { 49 | "comlink": "^4.3.1" 50 | }, 51 | "devDependencies": { 52 | "@testing-library/react": "^13.3.0", 53 | "@types/jest": "^28.1.1", 54 | "@types/react": "^18.0.12", 55 | "@types/react-dom": "^18.0.5", 56 | "@typescript-eslint/eslint-plugin": "^5.27.1", 57 | "@typescript-eslint/parser": "^5.27.1", 58 | "documentation": "^13.2.5", 59 | "eslint": "^8.17.0", 60 | "eslint-config-airbnb": "^19.0.4", 61 | "eslint-plugin-import": "^2.26.0", 62 | "eslint-plugin-jsx-a11y": "^6.5.1", 63 | "eslint-plugin-react": "^7.30.0", 64 | "eslint-plugin-react-hooks": "^4.5.0", 65 | "html-webpack-plugin": "^5.5.0", 66 | "jest": "^28.1.1", 67 | "microbundle": "^0.15.0", 68 | "npm-run-all": "^4.1.5", 69 | "react": "^18.1.0", 70 | "react-dom": "^18.1.0", 71 | "ts-jest": "^28.0.4", 72 | "ts-loader": "^9.3.0", 73 | "typescript": "^4.7.3", 74 | "webpack": "^5.73.0", 75 | "webpack-cli": "^4.9.2", 76 | "webpack-dev-server": "^4.9.2" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-suspense-worker 2 | 3 | [![CI](https://img.shields.io/github/workflow/status/dai-shi/react-suspense-worker/CI)](https://github.com/dai-shi/react-suspense-worker/actions?query=workflow%3ACI) 4 | [![npm](https://img.shields.io/npm/v/react-suspense-worker)](https://www.npmjs.com/package/react-suspense-worker) 5 | [![size](https://img.shields.io/bundlephobia/minzip/react-suspense-worker)](https://bundlephobia.com/result?p=react-suspense-worker) 6 | [![discord](https://img.shields.io/discord/627656437971288081)](https://discord.gg/MrQdmzd) 7 | 8 | React Suspense for Web Worker with Comlink 9 | 10 | ## Introduction 11 | 12 | This is an experimental library to support Web Workers with React Suspense. 13 | Currently, it's implemented with 14 | [Comlink](https://github.com/GoogleChromeLabs/comlink). 15 | Comlink is promise based, but a value wrapped by this library 16 | can be treated as a normal value without async/await. 17 | 18 | Known issues: 19 | 20 | * No way to clear cache 21 | * Class not supported (yet) 22 | * (...and maybe more) 23 | 24 | ## Install 25 | 26 | ```bash 27 | npm install react-suspense-worker 28 | ``` 29 | 30 | ## Usage 31 | 32 | slow_fib.worker.js: 33 | 34 | ```javascript 35 | import { expose } from 'react-suspense-worker'; 36 | 37 | const fib = i => (i <= 1 ? i : fib(i - 1) + fib(i - 2)); 38 | 39 | expose(fib); 40 | ``` 41 | 42 | App.jsx: 43 | 44 | ```javascript 45 | import { wrap } from 'react-suspense-worker'; 46 | 47 | const fib = wrap(new Worker(new URL('./slow_fib.worker', import.meta.url))); 48 | 49 | const DisplayFib = ({ number }) => { 50 | const result = fib(number); 51 | return
result: {result}
; 52 | }; 53 | 54 | const Main = () => { 55 | const [number, setNumber] = useState(1); 56 | const [isPending, startTransition] = useTransition(); 57 | const onClick = () => { 58 | startTransition(() => { 59 | setNumber((c) => c + 1); 60 | }); 61 | }; 62 | return ( 63 |
64 | number: {number} 65 | 66 | {isPending && 'Pending...'} 67 | 68 |
69 | ); 70 | }; 71 | 72 | const App = () => ( 73 | Loading...}> 74 |
75 | 76 | ); 77 | ``` 78 | 79 | ## API 80 | 81 | 82 | 83 | ### expose 84 | 85 | Expose a value in worker thread to be wrapped in main thread 86 | 87 | #### Examples 88 | 89 | ```javascript 90 | import { expose } from 'react-suspense-worker'; 91 | 92 | const fib = (i) => (i <= 1 ? i : fib(i - 1) + fib(i - 2)); 93 | 94 | expose(fib); 95 | ``` 96 | 97 | ### wrap 98 | 99 | Wrap a worker to be used with React Suspense 100 | 101 | #### Parameters 102 | 103 | * `ep` **Endpoint** 104 | 105 | #### Examples 106 | 107 | ```javascript 108 | import { wrap } from 'react-suspense-worker'; 109 | 110 | const fib = wrap(new Worker(new URL('./slow_fib.worker', import.meta.url))); 111 | 112 | const DisplayFib = ({ number }) => { 113 | const result = fib(number); 114 | return
result: {result}
; 115 | }; 116 | ``` 117 | 118 | Returns **T** 119 | 120 | ## Examples 121 | 122 | The [examples](examples) folder contains working examples. 123 | You can run one of them with 124 | 125 | ```bash 126 | PORT=8080 npm run examples:01_minimal 127 | ``` 128 | 129 | and open in your web browser. 130 | 131 | 136 | --------------------------------------------------------------------------------