Skip to content

Commit bdea811

Browse files
authored
[expo-camera] Remove @koale/useworker (#23967)
# Why `@koale/useworker` is only used for Web QR code scanning and has been causing some issues due to lack of maintenance. Fixes: expo/expo#17846 Related: expo/expo#23296, and the PR to revert its 'fix' expo/expo-cli#4746 # How Refactors the existing code to use vanilla JS 1. We create a inline webworker by creating an inline blob with `qrWorkerMethod.toString()` (code adapted from `@koale/useworker`) 2. Create a smaller wrapper that tracks messages being sent to the worker via a queue of promises As the worker is synchronous and will receive messages in order a simple First In First Out queue should suffice. This does _slightly_ change the functionality of the existing code. 1. Previously we created a new Worker for every instance of `<Camera />`. Now a single worker is shared across all instances. 1. As the worker is declared in the global scope, it is created regardless if QR code scanning is enabled. We could lazy initialise it, but because its extremely small and inline (no network requests) I don't see this being an issue. I also removed an extra `useEffect` hook by moving its logic into the cleanup step of the enabling hook.
1 parent 6ebbcac commit bdea811

File tree

6 files changed

+72
-35
lines changed

6 files changed

+72
-35
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
- Fixed flash is not enabled during recordings. ([#23776](https://github.com/expo/expo/pull/23776) by [@tszheichoi](https://github.com/tszheichoi))
1212
- On iOS, fix dead frames when starting a video recording. ([#22037](https://github.com/expo/expo/pull/22037) by [@alanjhughes](https://github.com/alanjhughes))
13+
- Remove @koale/useworker. ([#23967](https://github.com/expo/expo/pull/23967) by [@marklawlor](https://github.com/marklawlor))
1314

1415
### 💡 Others
1516

build/useWebQRScanner.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/useWebQRScanner.js

Lines changed: 30 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/useWebQRScanner.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"preset": "expo-module-scripts"
3535
},
3636
"dependencies": {
37-
"@koale/useworker": "^4.0.2",
3837
"invariant": "^2.2.4"
3938
},
4039
"devDependencies": {

src/useWebQRScanner.ts

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { useWorker } from '@koale/useworker';
21
import * as React from 'react';
32

43
import { BarCodeScanningResult, CameraPictureOptions, MountErrorListener } from './Camera.types';
@@ -37,12 +36,41 @@ const qrWorkerMethod = ({ data, width, height }: ImageData): any => {
3736
return parsed;
3837
};
3938

40-
function useRemoteJsQR() {
41-
return useWorker(qrWorkerMethod, {
42-
remoteDependencies: ['https://cdn.jsdelivr.net/npm/[email protected]/dist/jsQR.min.js'],
43-
autoTerminate: false,
44-
});
45-
}
39+
const createWorkerAsyncFunction = <T extends (data: any) => any>(fn: T, deps: string[]) => {
40+
const stringifiedFn = [
41+
`self.func = ${fn.toString()};`,
42+
'self.onmessage = (e) => {',
43+
' const result = self.func(e.data);',
44+
' self.postMessage(result);',
45+
'};',
46+
];
47+
48+
if (deps.length > 0) {
49+
stringifiedFn.unshift(`importScripts(${deps.map((dep) => `'${dep}'`).join(', ')});`);
50+
}
51+
52+
const blob = new Blob(stringifiedFn, { type: 'text/javascript' });
53+
const worker = new Worker(URL.createObjectURL(blob));
54+
55+
// First-In First-Out queue of promises
56+
const promises: {
57+
resolve: (value: ReturnType<T>) => void;
58+
reject: (reason?: any) => void;
59+
}[] = [];
60+
61+
worker.onmessage = (e) => promises.shift()?.resolve(e.data);
62+
63+
return (data: Parameters<T>[0]) => {
64+
return new Promise<ReturnType<T>>((resolve, reject) => {
65+
promises.push({ resolve, reject });
66+
worker.postMessage(data);
67+
});
68+
};
69+
};
70+
71+
const decode = createWorkerAsyncFunction(qrWorkerMethod, [
72+
'https://cdn.jsdelivr.net/npm/[email protected]/dist/jsQR.min.js',
73+
]);
4674

4775
export function useWebQRScanner(
4876
video: React.MutableRefObject<HTMLVideoElement | null>,
@@ -63,8 +91,6 @@ export function useWebQRScanner(
6391
const isRunning = React.useRef<boolean>(false);
6492
const timeout = React.useRef<number | undefined>(undefined);
6593

66-
const [decode, clearWorker] = useRemoteJsQR();
67-
6894
async function scanAsync() {
6995
// If interval is 0 then only scan once.
7096
if (!isRunning.current || !onScanned) {
@@ -109,15 +135,12 @@ export function useWebQRScanner(
109135
if (isEnabled) {
110136
isRunning.current = true;
111137
scanAsync();
112-
} else {
113-
stop();
114138
}
115-
}, [isEnabled]);
116139

117-
React.useEffect(() => {
118140
return () => {
119-
stop();
120-
clearWorker.kill();
141+
if (isEnabled) {
142+
stop();
143+
}
121144
};
122-
}, []);
145+
}, [isEnabled]);
123146
}

0 commit comments

Comments
 (0)