Skip to content

Commit b242d0d

Browse files
committed
support work with webpack worker
1 parent 3d581a2 commit b242d0d

8 files changed

Lines changed: 98 additions & 13 deletions

File tree

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ To upgrade this node using the module version of the code, we can directly impor
3737
```html
3838
<script type="module">
3939
import {upgradeElement} from './dist/main.mjs';
40-
upgradeElement(document.getElementById('upgrade-me'), './dist/worker/worker.mjs');
40+
upgradeElement(document.getElementById('upgrade-me'), './dist/worker/worker.js');
4141
</script>
4242
```
4343

@@ -51,6 +51,26 @@ The nomodule format exposes the global `MainThread`, and could upgrade the `div`
5151
</script>
5252
```
5353

54+
### With Webpack
55+
56+
In main.js :
57+
```js
58+
import { upgradeElementByWorker } from '@ampproject/worker-dom/dist/main.mjs';
59+
60+
const worker = new Worker(new URL('./worker.js', import.meta.url));
61+
upgradeElementByWorker(document.getElementById('upgrade-me'), worker);
62+
```
63+
In worker.js :
64+
65+
```js
66+
import { initWorkerDom } from '@ampproject/worker-dom/dist/worker/worker.mjs'
67+
68+
initWorkerDom().then(() => {
69+
const div = document.createElement('div');
70+
document.body.appendChild(div);
71+
})
72+
```
73+
5474
### AMP Distribution for `amp-script`
5575
5676
WorkerDOM has a special output variant that supplies additional hooks for safety features e.g. HTML sanitization. This variant is distributed under the amp folder for main and worker thread binaries:

config/rollup.worker-thread.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const ESModules = [
3434
input: 'output/worker-thread/index.js',
3535
output: {
3636
file: 'dist/worker/worker.mjs',
37-
format: 'iife',
37+
format: 'es',
3838
name: 'WorkerThread',
3939
sourcemap: true,
4040
},
@@ -51,7 +51,7 @@ const ESModules = [
5151
input: 'output/worker-thread/index.js',
5252
output: {
5353
file: 'dist/debug/worker/worker.mjs',
54-
format: 'iife',
54+
format: 'es',
5555
name: 'WorkerThread',
5656
sourcemap: true,
5757
},

src/main-thread/configuration.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ export type LongTaskFunction = (promise: Promise<any>) => void;
1515
export type HydrationFilterPredicate = (node: RenderableElement) => boolean;
1616

1717
export interface InboundWorkerDOMConfiguration {
18-
// ---- Required Values.
19-
authorURL: string;
20-
domURL: string;
18+
authorURL?: string;
19+
domURL?: string;
2120

2221
// ---- Optional Overrides
22+
// worker
23+
worker?: Worker;
2324
// Schedules mutation phase.
2425
mutationPump?: MutationPumpFunction;
2526
// Schedules long task.
@@ -43,9 +44,8 @@ export interface InboundWorkerDOMConfiguration {
4344
}
4445

4546
export interface WorkerDOMConfiguration {
46-
// ---- Required Values.
47-
authorURL: string;
48-
domURL: string;
47+
authorURL?: string;
48+
domURL?: string;
4949

5050
// ---- Optional, with defaults
5151
// Schedules mutation phase.
@@ -54,6 +54,8 @@ export interface WorkerDOMConfiguration {
5454
executorsAllowed: Array<number>;
5555

5656
// ---- Optional Overrides
57+
// worker
58+
worker?: Worker;
5759
// Schedules long task.
5860
longTask?: LongTaskFunction;
5961
// Sanitizer for DOM Mutations

src/main-thread/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { fetchAndInstall } from './install';
1+
import { fetchAndInstall, install } from './install';
22
import { ExportedWorker } from './exported-worker';
33

44
export function upgradeElement(baseElement: Element, domURL: string): Promise<ExportedWorker | null> {
@@ -11,3 +11,7 @@ export function upgradeElement(baseElement: Element, domURL: string): Promise<Ex
1111
}
1212
return Promise.resolve(null);
1313
}
14+
15+
export function upgradeElementByWorker(baseElement: Element, worker: Worker) {
16+
return install(Promise.resolve(['', '']), baseElement as HTMLElement, { worker });
17+
}

src/main-thread/install.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const ALLOWABLE_MESSAGE_TYPES = [MessageType.MUTATE, MessageType.HYDRATE];
1919
* @param debug
2020
*/
2121
export function fetchAndInstall(baseElement: HTMLElement, config: InboundWorkerDOMConfiguration): Promise<ExportedWorker | null> {
22+
if (!config.domURL || !config.authorURL) throw new Error('miss domURL or authorURL');
23+
2224
const fetchPromise = Promise.all([
2325
// TODO(KB): Fetch Polyfill for IE11.
2426
fetch(config.domURL).then((response) => response.text()),
@@ -49,7 +51,7 @@ export function install(
4951
const nodeContext = new NodeContext(stringContext, baseElement);
5052
const normalizedConfig = normalizeConfiguration(config);
5153
return fetchPromise.then(([domScriptContent, authorScriptContent]) => {
52-
if (domScriptContent && authorScriptContent && config.authorURL) {
54+
if ((domScriptContent && authorScriptContent && config.authorURL) || config.worker) {
5355
const workerContext = new WorkerContext(baseElement, nodeContext, domScriptContent, authorScriptContent, normalizedConfig);
5456
const mutatorContext = new MutatorProcessor(stringContext, nodeContext, workerContext, normalizedConfig, objectContext);
5557
workerContext.worker.onmessage = (message: MessageFromWorker) => {

src/main-thread/worker.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { NodeContext } from './nodes';
66
import { TransferrableKeys } from '../transfer/TransferrableKeys';
77
import { StorageLocation } from '../transfer/TransferrableStorage';
88
import { IframeWorker } from './iframe-worker';
9+
import { waitForMsg } from '../utils';
910

1011
// TODO: Sanitizer storage init is likely broken, since the code currently
1112
// attempts to stringify a Promise.
@@ -64,9 +65,25 @@ export class WorkerContext {
6465
Object.assign(self, workerDOM);
6566
}).call(self);
6667
${authorScript}
67-
//# sourceURL=${encodeURI(config.authorURL)}`;
68+
//# sourceURL=${encodeURI(config.authorURL ?? '')}`;
6869
if (!config.sandbox) {
69-
this[TransferrableKeys.worker] = new Worker(URL.createObjectURL(new Blob([code])));
70+
if (config.worker) {
71+
this[TransferrableKeys.worker] = config.worker;
72+
waitForMsg<any>(config.worker, 'initHydrateArgs').then(() => {
73+
const hydrateArgs = [
74+
strings,
75+
skeleton,
76+
cssKeys,
77+
globalEventHandlerKeys,
78+
[window.innerWidth, window.innerHeight],
79+
localStorageInit,
80+
sessionStorageInit,
81+
];
82+
config.worker?.postMessage({ data: JSON.stringify(hydrateArgs), type: 'initHydrateArgs' });
83+
});
84+
} else {
85+
this[TransferrableKeys.worker] = new Worker(URL.createObjectURL(new Blob([code])));
86+
}
7087
} else if (IS_AMP) {
7188
this[TransferrableKeys.worker] = new IframeWorker(URL.createObjectURL(new Blob([code])), config.sandbox.iframeUrl);
7289
}

src/utils.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,28 @@ export const enum NumericBoolean {
1010
FALSE = 0,
1111
TRUE = 1,
1212
}
13+
14+
export interface Msg<T extends any = any> {
15+
type: 'initHydrateArgs';
16+
data: T;
17+
}
18+
export function listenToMsgType<T>(target: Worker | typeof globalThis, type: Msg<T>['type'], listener: (data: Msg<T>) => any) {
19+
const _listener = (e: any) => {
20+
if (e.data?.type === type) listener(e.data);
21+
};
22+
target.addEventListener('message', _listener);
23+
return () => target.removeEventListener('message', _listener);
24+
}
25+
26+
export function waitForMsg<T>(target: Worker | typeof globalThis, type: Msg<T>['type']): Promise<Msg<T>> {
27+
return new Promise((res) => {
28+
const removeListener = listenToMsgType<T>(target, type, (data) => {
29+
removeListener();
30+
res(data);
31+
});
32+
});
33+
}
34+
35+
export function postMsg<T>(target: Worker | typeof globalThis, type: Msg<T>['type'], data?: T) {
36+
return target.postMessage({ type, data });
37+
}

src/worker-thread/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ import { DocumentFragment } from './dom/DocumentFragment';
4343
import { Element } from './dom/Element';
4444
import { rafPolyfill, cafPolyfill } from './AnimationFrame';
4545
import { HydrateFunction } from './hydrate';
46+
import { TransferrableKeys } from '../transfer/TransferrableKeys';
47+
import { waitForMsg, postMsg } from '../utils';
4648

4749
const globalScope: GlobalScope = {
4850
innerWidth: 0,
@@ -116,3 +118,16 @@ export const workerDOM = (function (postMessage, addEventListener, removeEventLi
116118
})(postMessage.bind(self) || noop, addEventListener.bind(self) || noop, removeEventListener.bind(self) || noop);
117119

118120
export const hydrate: HydrateFunction = initialize;
121+
122+
type SliceTuple<T extends any[]> = T extends [any, ...infer Last] ? Last : never;
123+
124+
export function initWorkerDom() {
125+
postMsg(self, 'initHydrateArgs');
126+
return waitForMsg<string>(self, 'initHydrateArgs').then((data) => {
127+
const hydrateArgs = JSON.parse(data.data) as SliceTuple<Parameters<HydrateFunction>>;
128+
Object.assign(self, { window: self });
129+
hydrate(workerDOM.document, ...hydrateArgs);
130+
workerDOM.document[TransferrableKeys.observe]();
131+
Object.assign(self, workerDOM);
132+
});
133+
}

0 commit comments

Comments
 (0)