Skip to content

Commit dad84a4

Browse files
committed
Change Worker mocking setup.
Make it so that gz.ts can just use `new Worker` as if it was running in the browser. During tests, we mock window.Worker with our NodeWorker implementation. Replace the manual name-to-path mapping with the use of Webpack's file-loader. Using Webpack's file loader is a more natural way to define which files we depend on. This means that, if our this code is used as part of an npm library, the Webpack build output will contain the worker files, and the npm library should find it - as long as it's running in the browser. (I haven't verified this though; I'm not sure if extra resource files from npm dependencies are carried through into the final artifact properly.) As a follow-up, we can add a Node compatibility path to gz.js, for when this code is used as part of Node library, i.e. actually running within Node. In our tests we'd probably still want to use our Worker mock so that we test the code we ship to the browser.
1 parent 65fbff3 commit dad84a4

File tree

8 files changed

+58
-68
lines changed

8 files changed

+58
-68
lines changed

babel.config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
"module-resolver",
6666
{
6767
"alias": {
68-
"firefox-profiler": "./src"
68+
"firefox-profiler": "./src",
69+
"firefox-profiler-res": "./res"
6970
}
7071
}
7172
]
Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,41 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
import { Worker } from 'worker_threads';
5+
import { Worker as NodeWorkerClass } from 'worker_threads';
66

7-
class NodeWorker {
8-
_instance: Worker | null;
7+
function getWorkerScript(file: string): string {
8+
return `
9+
const { parentPort } = require('worker_threads');
10+
const fs = require('fs');
11+
const vm = require('vm');
12+
13+
const scriptContent = fs.readFileSync("${file}", 'utf8');
14+
15+
const sandbox = {
16+
importScripts: function () {
17+
throw new Error('The function "importScripts" is not implemented.');
18+
},
19+
postMessage: parentPort.postMessage.bind(parentPort),
20+
onmessage: function () {},
21+
};
22+
23+
vm.runInNewContext(scriptContent, sandbox, { filename: "${file}" });
24+
25+
parentPort.onmessage = sandbox.onmessage.bind(null);
26+
`;
27+
}
28+
29+
export class NodeWorker {
30+
_instance: NodeWorkerClass | null;
931
onmessage: ((event: MessageEvent) => unknown) | null;
1032

1133
constructor(file: string) {
12-
const worker = new Worker(__dirname + '/node-worker-contents.mjs', {
13-
workerData: file,
14-
});
34+
const worker = new NodeWorkerClass(getWorkerScript(file), { eval: true });
1535
worker.on('message', this.onMessage);
1636
worker.on('error', this.onError);
1737
this._instance = worker;
1838
this.onmessage = null;
39+
workerInstances.push(worker);
1940
}
2041

2142
postMessage(message: unknown, transfer?: any[]) {
@@ -53,23 +74,13 @@ class NodeWorker {
5374
}
5475
}
5576

56-
const workerConfigs: { [key: string]: string } = {
57-
'zee-worker': './res/zee-worker.js',
58-
};
59-
60-
const workerInstances: NodeWorker[] = [];
61-
62-
export default function (file: string): NodeWorker {
63-
const path = workerConfigs[file];
64-
const worker = new NodeWorker(path);
65-
workerInstances.push(worker);
66-
return worker;
67-
}
77+
const workerInstances: NodeWorkerClass[] = [];
6878

69-
/**
70-
* This function allows for stopping the workers, and is only part of the mock.
71-
*/
79+
// Called after running each test (see setup.js), otherwise Jest won't shut down.
7280
export function __shutdownWorkers() {
73-
workerInstances.forEach((worker) => worker.terminate());
81+
workerInstances.forEach((worker) => {
82+
worker.terminate();
83+
worker.unref();
84+
});
7485
workerInstances.length = 0;
7586
}

src/test/setup.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import 'jest-extended';
1212
import fetchMock from '@fetch-mock/jest';
1313
import crypto from 'crypto';
1414

15-
jest.mock('../utils/worker-factory');
16-
import * as WorkerFactory from '../utils/worker-factory';
15+
import { NodeWorker, __shutdownWorkers } from './fixtures/node-worker';
1716
import { autoMockResizeObserver } from './fixtures/mocks/resize-observer';
1817

1918
autoMockResizeObserver();
@@ -25,9 +24,15 @@ if (process.env.TZ !== 'UTC') {
2524
fetchMock.mockGlobal();
2625
(global as any).fetchMock = fetchMock;
2726

27+
// Mock the effects of the file-loader which our Webpack config defines
28+
// for JS files under res: The "default export" is the path to the file.
29+
jest.mock('firefox-profiler-res/zee-worker.js', () => './res/zee-worker.js');
30+
// Install a Worker class which is similar to the DOM Worker class.
31+
(global as any).Worker = NodeWorker;
32+
2833
afterEach(function () {
29-
// This `__shutdownWorkers` function only exists in the mocked test environment.
30-
const { __shutdownWorkers } = WorkerFactory as any;
34+
// All node workers must be shut down at the end of the test run,
35+
// otherwise Jest won't exit.
3136
__shutdownWorkers();
3237
});
3338

src/types/globals/global.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
// Added by webpack's DefinePlugin
66
declare const AVAILABLE_STAGING_LOCALES: string[] | null;
77

8+
declare module 'firefox-profiler-res/*.js' {
9+
const content: string;
10+
export default content;
11+
}
12+
813
declare module '*.css' {}
914

1015
declare module '*.svg' {

src/utils/__mocks__/node-worker-contents.mjs

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/utils/gz.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
// This worker is imported as WebWorker since it's conflicting with the Worker
6-
// global type.
7-
import WebWorker from './worker-factory';
5+
import zeeWorkerPath from 'firefox-profiler-res/zee-worker.js';
86

97
const zeeCallbacks: Array<{
108
success: (data: any) => void;
@@ -29,11 +27,11 @@ function workerOnMessage(zeeWorker: Worker) {
2927
}
3028

3129
// Neuters data's buffer, if data is a typed array.
32-
export function compress(
30+
export async function compress(
3331
data: string | Uint8Array,
3432
compressionLevel?: number
3533
): Promise<Uint8Array<ArrayBuffer>> {
36-
const zeeWorker = new WebWorker('zee-worker') as Worker;
34+
const zeeWorker = new Worker(zeeWorkerPath);
3735
workerOnMessage(zeeWorker);
3836

3937
const arrayData =
@@ -56,9 +54,9 @@ export function compress(
5654
}
5755

5856
// Neuters data's buffer, if data is a typed array.
59-
export function decompress(data: Uint8Array): Promise<Uint8Array> {
57+
export async function decompress(data: Uint8Array): Promise<Uint8Array> {
58+
const zeeWorker = new Worker(zeeWorkerPath);
6059
return new Promise(function (resolve, reject) {
61-
const zeeWorker = new WebWorker('zee-worker') as Worker;
6260
workerOnMessage(zeeWorker);
6361
zeeWorker.postMessage(
6462
{

src/utils/worker-factory.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

webpack.config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ const config = {
3838
rules: [
3939
{
4040
test: /\.js$/,
41-
use: ['babel-loader'],
42-
include: includes.concat(es6modulePaths),
41+
use: ['file-loader'],
42+
include: [path.join(__dirname, 'res')],
4343
},
4444
{
45-
test: /\.(ts|tsx)$/,
45+
test: /\.(js|ts|tsx)$/,
4646
use: ['babel-loader'],
47-
include: includes,
47+
include: [path.join(__dirname, 'src')],
4848
},
4949
{
5050
test: /\.json$/,

0 commit comments

Comments
 (0)