Skip to content

Commit

Permalink
Create service worker (#20)
Browse files Browse the repository at this point in the history
Moved heavy calculations into a worker
  • Loading branch information
evg4b authored Nov 11, 2024
1 parent cbc98f0 commit 961744e
Show file tree
Hide file tree
Showing 17 changed files with 157 additions and 24 deletions.
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default tseslint.config(
{
rules: {
'@typescript-eslint/no-confusing-void-expression': 'off',
'@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',
'@typescript-eslint/restrict-template-expressions': [
'error',
{
Expand Down
4 changes: 2 additions & 2 deletions packages/jq/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { importWasm } from '../shared';
let go = new Go();

export const jq = async (data: string, query: string): Promise<TokenizerResponse> => {
if (!('___jq' in window) || go.exited) {
if (!('___jq' in globalThis) || go.exited) {
go = new Go();
await importWasm(go, 'jq.wasm');
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return window.___jq(data, query);
return globalThis.___jq(data, query);
};
3 changes: 2 additions & 1 deletion packages/tokenizer/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ export type TokenNode = ObjectNode | ArrayNode | StringNode | NumberNode | Boole

export interface ErrorNode {
type: 'error';
scope: 'jq' | 'tokenizer';
scope: 'jq' | 'tokenizer' | 'worker';
error: string;
stack?: string;
}

export type TokenizerResponse = TokenNode | TupleNode | ErrorNode;
4 changes: 2 additions & 2 deletions packages/tokenizer/tokenize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { TokenizerResponse } from './models';
let go = new Go();

export const tokenize = async (data: string): Promise<TokenizerResponse> => {
if (!('___tokenizeJSON' in window) || go.exited) {
if (!('___tokenizeJSON' in globalThis) || go.exited) {
go = new Go();
await importWasm(go, 'tokenizer.wasm');
}

return window.___tokenizeJSON(data);
return globalThis.___tokenizeJSON(data);
};
38 changes: 38 additions & 0 deletions src/background/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import '../../packages/wasm_exec.js';
import { JqParams, TokenizeParams } from '@core/background';
import { jq } from '@packages/jq';
import { tokenize } from '@packages/tokenizer';
import { is } from './helpres';

// eslint-disable-next-line @typescript-eslint/no-deprecated
chrome.runtime.onMessage.addListener(function (message: TokenizeParams | JqParams | object, _, sendResponse) {
if (is(message, 'tokenize')) {
tokenize(message.json)
.then(sendResponse)
.catch((err: Error) =>
sendResponse({
type: 'error',
scope: 'worker',
stack: err.stack,
error: err.message,
})
);

return true;
}

if (is(message, 'jq')) {
jq(message.json, message.query)
.then(sendResponse)
.catch((err: Error) =>
sendResponse({
type: 'error',
scope: 'worker',
stack: err.stack,
error: err.message,
})
);

return true;
}
});
10 changes: 10 additions & 0 deletions src/background/helpres.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { JqParams, Message, TokenizeParams } from '@core/background';

interface Types {
jq: JqParams;
tokenize: TokenizeParams;
}

export const is = <T extends Message['action']>(message: object, type: T): message is Types[T] => {
return 'action' in message && message.action == type;
};
2 changes: 2 additions & 0 deletions src/content-script/dom/build-dom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ describe('buildDom', () => {
{
type: 'string',
value: 'https://www.youtube.com/watch?v=QH2-TGUlwu4',
variant: 'url',
},
{
type: 'string',
value: '/json-formatter/examples/2-array-root.json',
variant: 'url',
},
],
},
Expand Down
37 changes: 22 additions & 15 deletions src/content-script/extension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { jq, tokenize, TokenizerResponse } from '@core/background';
import { registerStyles } from '@core/ui/helpers';
import { TokenizerResponse } from '@packages/tokenizer';
import { isNotNull } from 'typed-assert';
import { buildDom } from './dom';
import { buildErrorNode } from './dom/build-error-node';
Expand All @@ -13,8 +13,6 @@ export const runExtension = async () => {
return;
}

const { tokenize } = await import('@packages/tokenizer');

const shadowRoot = document.body.attachShadow({ mode: 'open' });
registerStyles(shadowRoot, styles);

Expand All @@ -24,22 +22,31 @@ export const runExtension = async () => {
const { rootContainer, rawContainer, formatContainer, queryContainer } = buildContainers(shadowRoot);
rawContainer.appendChild(data);

const wrapper = async <T>(promise: Promise<T>): Promise<T> => {
rootContainer.classList.add('loading');
try {
return await promise;
} finally {
rootContainer.classList.remove('loading');
}
};

const toolbox = new ToolboxElement();
shadowRoot.appendChild(toolbox);

const response = await tokenize(data.innerText);

toolbox.onQueryChanged(async query => {
const { jq } = await import('@packages/jq');

const info = await jq(data.innerText, query);
if (info.type === 'error' && info.scope === 'jq') {
toolbox.setErrorMessage(info.error);
return;
}
await wrapper(
(async (query: string) => {
const info = await jq(data.innerText, query);
if (info.type === 'error' && info.scope === 'jq') {
toolbox.setErrorMessage(info.error);
return;
}

queryContainer.innerHTML = '';
queryContainer.appendChild(prepareResponse(info));
queryContainer.innerHTML = '';
queryContainer.appendChild(prepareResponse(info));
})(query)
);
});

toolbox.onTabChanged(tab => {
Expand All @@ -59,7 +66,7 @@ export const runExtension = async () => {
}
});

formatContainer.appendChild(prepareResponse(response));
await wrapper(tokenize(data.innerText).then(response => formatContainer.appendChild(prepareResponse(response))));
};

const prepareResponse = (response: TokenizerResponse): HTMLElement => {
Expand Down
27 changes: 27 additions & 0 deletions src/content-script/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,30 @@ pre {
}
}
}

.loader {
display: none;
}

.loading .loader {
display: block;
width: 30px;
height: 30px;
position: fixed;
top: calc(50% - 15px);
left: calc(50% - 15px);
aspect-ratio: 1;
border-radius: 50%;
border: 5px solid #3b3b3b; /* Light grey */
border-top: 5px solid #eeeeee; /* Blue */
animation: spin 2s linear infinite;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
10 changes: 8 additions & 2 deletions src/content-script/ui/containers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { createElement } from '@core/dom';

export const buildContainers = (root: ShadowRoot) => {
const rootContainer = document.createElement('div');
rootContainer.className = 'root-container';
const rootContainer = createElement({
element: 'div',
class: 'root-container',
});

rootContainer.appendChild(createElement({ element: 'div', class: 'loader' }));

const formatContainer = document.createElement('div');
formatContainer.className = 'formatted-json-container';
Expand Down
3 changes: 3 additions & 0 deletions src/core/background/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './tokenize';
export * from './models';
export * from './jq';
7 changes: 7 additions & 0 deletions src/core/background/jq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { JqParams } from '@core/background/models';
import { sendMessage } from '@core/browser';
import { TokenizerResponse } from '@packages/tokenizer';

export const jq = async (json: string, query: string): Promise<TokenizerResponse> => {
return sendMessage<JqParams, TokenizerResponse>({ action: 'jq', json, query });
};
16 changes: 16 additions & 0 deletions src/core/background/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TokenizerResponse } from '@packages/tokenizer';

export interface TokenizeParams {
action: 'tokenize';
json: string;
}

export interface JqParams {
action: 'jq';
json: string;
query: string;
}

export type Message = TokenizeParams | JqParams;

export { TokenizerResponse };
7 changes: 7 additions & 0 deletions src/core/background/tokenize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { TokenizeParams } from '@core/background/models';
import { sendMessage } from '@core/browser';
import { TokenizerResponse } from '@packages/tokenizer';

export const tokenize = async (json: string): Promise<TokenizerResponse> => {
return sendMessage<TokenizeParams, TokenizerResponse>({ action: 'tokenize', json });
};
4 changes: 2 additions & 2 deletions src/core/browser/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-deprecated
const getURL = chrome.runtime.getURL;
const { getURL, sendMessage } = chrome.runtime;

export { getURL };
export { getURL, sendMessage };
7 changes: 7 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
}
],
"host_permissions": ["*://*/*", "<all_urls>"],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"icons": {
"16": "icon16.png",
"32": "icon32.png",
Expand Down
1 change: 1 addition & 0 deletions tsup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default defineConfig({
entry: {
'content-script': 'src/content-script/main.ts',
faq: 'src/faq/faq.html',
background: 'src/background/background.ts',
},
splitting: false,
sourcemap: !production,
Expand Down

0 comments on commit 961744e

Please sign in to comment.