├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── react-app-rewired.config.js
├── src
├── App.css
├── App.test.tsx
├── App.tsx
├── MyComlinkWorker.worker.ts
├── MyWorker.worker.ts
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
├── serviceWorker.ts
└── setupTests.ts
└── tsconfig.json
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: push
4 |
5 | jobs:
6 | build:
7 | name: Build
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout repository
11 | uses: actions/checkout@v2
12 | - name: Setup NodeJS
13 | uses: actions/setup-node@v1
14 | with:
15 | node-version: 14.15.x
16 | - name: Install dependencies
17 | run: npm ci
18 | - name: Build
19 | run: npm run build
20 | env:
21 | CI: true
22 |
23 | test:
24 | name: Test
25 | needs: build
26 | runs-on: ubuntu-latest
27 | steps:
28 | - name: Checkout repository
29 | uses: actions/checkout@v2
30 | - name: Setup NodeJS
31 | uses: actions/setup-node@v1
32 | with:
33 | node-version: 14.15.x
34 | - name: Install dependencies
35 | run: npm ci
36 | - name: Test
37 | run: npm run test
38 | env:
39 | CI: true
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
3 | # Created by https://www.gitignore.io/api/node,visualstudiocode
4 | # Edit at https://www.gitignore.io/?templates=node,visualstudiocode
5 |
6 | ### Node ###
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | lerna-debug.log*
14 |
15 | # Diagnostic reports (https://nodejs.org/api/report.html)
16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
17 |
18 | # Runtime data
19 | pids
20 | *.pid
21 | *.seed
22 | *.pid.lock
23 |
24 | # Directory for instrumented libs generated by jscoverage/JSCover
25 | lib-cov
26 |
27 | # Coverage directory used by tools like istanbul
28 | coverage
29 | *.lcov
30 |
31 | # nyc test coverage
32 | .nyc_output
33 |
34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # TypeScript v1 declaration files
51 | typings/
52 |
53 | # TypeScript cache
54 | *.tsbuildinfo
55 |
56 | # Optional npm cache directory
57 | .npm
58 |
59 | # Optional eslint cache
60 | .eslintcache
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # next.js build output
79 | .next
80 |
81 | # nuxt.js build output
82 | .nuxt
83 |
84 | # rollup.js default build output
85 | dist/
86 |
87 | # Uncomment the public line if your project uses Gatsby
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav
90 | # public
91 |
92 | # Storybook build outputs
93 | .out
94 | .storybook-out
95 |
96 | # vuepress build output
97 | .vuepress/dist
98 |
99 | # Serverless directories
100 | .serverless/
101 |
102 | # FuseBox cache
103 | .fusebox/
104 |
105 | # DynamoDB Local files
106 | .dynamodb/
107 |
108 | # Temporary folders
109 | tmp/
110 | temp/
111 |
112 | ### VisualStudioCode ###
113 | .vscode/*
114 | !.vscode/settings.json
115 | !.vscode/tasks.json
116 | !.vscode/launch.json
117 | !.vscode/extensions.json
118 |
119 | ### VisualStudioCode Patch ###
120 | # Ignore all local history of files
121 | .history
122 |
123 | # End of https://www.gitignore.io/api/node,visualstudiocode
124 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Dominique Müller
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # create-react-app-typescript-web-worker-setup
4 |
5 | Using **[Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)** in a
6 | **[TypeScript](https://github.com/microsoft/TypeScript)** **[React](https://github.com/facebook/react)** project based on
7 | **[create-react-app](https://github.com/facebook/create-react-app)**.
8 |
9 |
10 |
11 |
12 |
13 | ## How to use Web Workers in a TypeScript React app
14 |
15 | This project is an example React application that uses Web Workers. You can clone it and play around with it (see **[Commands](#commands)**).
16 | The following sub-chapters explain how to setup Web Worker support in a `create-react-app` project, and how to use Web Workers in your app -
17 | both vanilla and using **[Comlink](https://github.com/GoogleChromeLabs/comlink)**.
18 |
19 |
20 |
21 | ### 1. Install dependencies
22 |
23 | First of all, we need a few new dependencies. In particular:
24 |
25 | - We need **[react-app-rewired](https://github.com/timarney/react-app-rewired)** to hook into the Webpack configuration that `react-scripts`
26 | uses under the hood (without having to eject the config)
27 | - We use the Webpack **[worker-loader](https://github.com/webpack-contrib/worker-loader)** to integrate Web Workers into our build process
28 |
29 | Add both dependencies to your `package.json` file and install them. For example:
30 |
31 | ```diff
32 | {
33 | "devDependencies": {
34 | + "react-app-rewired": "2.1.x",
35 | + "worker-loader": "3.0.x"
36 | }
37 | }
38 | ```
39 |
40 |
41 |
42 | ### 2. Customize the build process
43 |
44 | First, replace all mentions of `react-scripts` within the `scripts` area of your `package.json` file by `react-app-rewired`. This enables us
45 | to tap into the build process in the next step. For example:
46 |
47 | ```diff
48 | {
49 | "scripts": {
50 | - "start": "react-scripts start",
51 | + "start": "react-app-rewired start",
52 | - "build": "react-scripts build",
53 | + "build": "react-app-rewired build",
54 | - "test": "react-scripts test",
55 | + "test": "react-app-rewired test",
56 | }
57 | }
58 | ```
59 |
60 | Then, create a file named `react-app-rewired.config.js` (or whatever name you prefer) within the root folder of your project. This file will
61 | be used by `react-app-rewired` when the build process runs, and allows us to customize the underlying Webpack configuration before the build
62 | runs. Fill it with the following content:
63 |
64 | ```js
65 | /**
66 | * React App Rewired Config
67 | */
68 | module.exports = {
69 | // Update webpack config to use custom loader for worker files
70 | webpack: (config) => {
71 | // Note: It's important that the "worker-loader" gets defined BEFORE the TypeScript loader!
72 | config.module.rules.unshift({
73 | test: /\.worker\.ts$/,
74 | use: {
75 | loader: 'worker-loader',
76 | options: {
77 | // Use directory structure & typical names of chunks produces by "react-scripts"
78 | filename: 'static/js/[name].[contenthash:8].js',
79 | },
80 | },
81 | });
82 |
83 | return config;
84 | },
85 | };
86 | ```
87 |
88 | Finally, reference the `react-app-rewired.config.js` file in your `package.json` file by adding the following line:
89 |
90 | ```diff
91 | {
92 | + "config-overrides-path": "react-app-rewired.config.js",
93 | }
94 | ```
95 |
96 |
97 |
98 | ### 3. Create and use a Web Worker
99 |
100 | Now you can start using Web Workers! Two things are important here: Files that contain a Web Worker must end with `*.worker.ts`, and they
101 | must start with the following two lines of code in order to work nicely together with TypeScript:
102 |
103 | ```ts
104 | declare const self: DedicatedWorkerGlobalScope;
105 | export default {} as typeof Worker & { new (): Worker };
106 |
107 | // Your code ...
108 | ```
109 |
110 | In your application, you can import your Web Workers like a normal module, and instantiate them as a class. For example:
111 |
112 | ```ts
113 | import MyWorker from './MyWorker.worker';
114 |
115 | const myWorkerInstance: Worker = new MyWorker();
116 | ```
117 |
118 | > Implementation pointers:
119 | >
120 | > - [Web Worker implementation](https://github.com/dominique-mueller/react-web-worker-experiment/blob/master/src/MyWorker.worker.ts)
121 | > - [Using the Web Worker](https://github.com/dominique-mueller/react-web-worker-experiment/blob/master/src/App.tsx#L9)
122 |
123 |
124 |
125 | ### Bonus: Using [Comlink](https://github.com/GoogleChromeLabs/comlink)
126 |
127 | Using Web Workers as is comes with the additional overhead of messaging between the main thread and the worker thread.
128 | **[Comlink](https://github.com/GoogleChromeLabs/comlink)** is a library by Googlers that simplifies the usage of Web Workers by turning
129 | the message-based communication into a "remote function execution"-like system.
130 |
131 | Within your React app, you can use Comlink just like you would expect. For example, expose your API within your worker:
132 |
133 | ```ts
134 | import { expose } from 'comlink';
135 |
136 | export default {} as typeof Worker & { new (): Worker };
137 |
138 | // Define API
139 | export const api = {
140 | createMessage: (name: string): string => {
141 | return `Hello ${name}!`;
142 | },
143 | };
144 |
145 | // Expose API
146 | expose(api);
147 | ```
148 |
149 | Then, from within you main thread, wrap the instantiated worker and use the worker API (asynchronously):
150 |
151 | ```ts
152 | import { wrap } from 'comlink';
153 | import MyComlinkWorker, { api } from './MyComlinkWorker.worker';
154 |
155 | // Instantiate worker
156 | const myComlinkWorkerInstance: Worker = new MyComlinkWorker();
157 | const myComlinkWorkerApi = wrap(myComlinkWorkerInstance);
158 |
159 | // Call function in worker
160 | myComlinkWorkerApi.createMessage('John Doe').then((message: string) => {
161 | console.log(message);
162 | });
163 | ```
164 |
165 | > Implementation pointers:
166 | >
167 | > - [Web Worker implementation](https://github.com/dominique-mueller/react-web-worker-experiment/blob/master/src/MyComlinkWorker.worker.ts)
168 | > - [Using the Web Worker](https://github.com/dominique-mueller/react-web-worker-experiment/blob/master/src/App.tsx#L14)
169 |
170 |
171 |
172 | ### A note on testing
173 |
174 | Soooo, Jest does not support Web Workers (see **[jest/#3449](https://github.com/facebook/jest/issues/3449)**). Plus, Jest does not use our
175 | customized Webpack configuration anyways. Thus - using one of the many ways you can mock stuff in Jest - mock away Web Workers when testing
176 | code that instantes them / works with them.
177 |
178 |