Skip to content

Commit

Permalink
feat(worker): support shared worker in MFE (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
unadlib authored Oct 30, 2024
1 parent 0d7abc9 commit f9dfbe7
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 12 deletions.
8 changes: 7 additions & 1 deletion examples/basic/app2/src/bootstrap.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { expose, getMeta } from '@ringcentral/mfe-react';
import { expose, getMeta, getWorkerName } from '@ringcentral/mfe-react';
import { getGlobalTransport, PickListeners } from '@ringcentral/mfe-transport';
import { useSentry } from '@ringcentral/mfe-sentry';
import {
useLogger,
ConsoleTransport,
StorageTransport,
} from '@ringcentral/mfe-logger';
import { testValue } from '@example/app3/src/testValue';

import type App3 from '@example/app3/src/bootstrap';
import App from './App';

console.log('testValue in main thread', testValue);
const worker = new SharedWorker(new URL('./worker', import.meta.url), {
name: getWorkerName('app2'),
});

(window as any)._log2 = useLogger({
name: 'app2',
transports: [
Expand Down
3 changes: 3 additions & 0 deletions examples/basic/app2/src/bootstrapWorker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { testValue } from '@example/app3/src/testValue';

console.log('testValue in worker thread', testValue);
1 change: 1 addition & 0 deletions examples/basic/app2/src/worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import('./bootstrapWorker');
3 changes: 2 additions & 1 deletion examples/basic/app3/site.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"$schema": "../../../node_modules/@ringcentral/mfe-shared/site-schema.json",
"name": "@example/app3",
"exposes": {
"./src/bootstrap": "./src/bootstrap"
"./src/bootstrap": "./src/bootstrap",
"./src/testValue": "./src/testValue"
},
"shared": {
"react": {
Expand Down
1 change: 1 addition & 0 deletions examples/basic/app3/src/testValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const testValue = 'module federation test value';
16 changes: 14 additions & 2 deletions examples/basic/app3/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const {
GenerateManifestWebpackPlugin,
} = require('@ringcentral/mfe-service-worker/dist/webpack-plugin/generate-manifest-webpack-plugin');

module.exports = {
const config = {
entry: './src/index',
mode: 'development',
devServer: {
Expand All @@ -26,7 +26,7 @@ module.exports = {
},
},
output: {
publicPath: 'auto',
publicPath: 'http://localhost:3003/',
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
Expand Down Expand Up @@ -64,3 +64,15 @@ module.exports = {
}),
],
};

module.exports = [
config,
{
...config,
target: 'webworker',
output: {
path: path.resolve(__dirname, 'dist/worker'),
publicPath: 'http://localhost:3003/',
},
},
];
73 changes: 67 additions & 6 deletions packages/builder/src/make.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,36 @@ export const makeRemoteScript = (
new Promise((resolve) => {
const _global = getGlobal();
if ((_global as any)[name]) return resolve((_global as any)[name]);
if (_global.SharedWorkerGlobalScope) {
// decode rule mapping with `core/src/meta.ts` getWorkerName function
let remoteEntry = '';
try {
remoteEntry = JSON.parse(atob(_global.name.split('#')[1]))[name].entry;
} catch (e) {
console.error(
`[MFE] Failed to parse remote entry for ${name}, please check that the worker name must be wrapped using 'getWorkerName'.`
);
throw e;
}
const url = new URL(remoteEntry);
const pathname = url.pathname.split('/');
pathname.splice(-1, 0, 'worker');
// replace the default remote with the worker remote
// e.g. http://localhost:3000/remoteEntry.js -> http://localhost:3000/worker/remoteEntry.js
const workerRemote = `${url.origin}${pathname.join('/')}`;
importScripts(workerRemote);
}
const toBeResolved = () => {
resolve(
const container =
(_global as any)[name] ??
_global[identifierContainer].dynamicImport({
dependency: name,
defaultRemote,
name: packageName,
version: version || '',
dependencyVersion,
})
);
});
resolve(container);
};
if (
!_global[identifierContainer] ||
Expand Down Expand Up @@ -75,7 +95,48 @@ export const makeBannerScript = (
retryDelay: number;
}
) => {
class MemoryStorage implements Storage {
private _data: Map<string, any> = new Map();

getItem(key: string) {
return this._data.get(key);
}

setItem(key: string, value: any) {
this._data.set(key, value);
}

removeItem(key: string) {
this._data.delete(key);
}

get length() {
return this._data.size;
}

key(idx: number) {
return [...this._data.keys()][idx];
}

keys() {
return [...this._data.keys()];
}

ready() {
return true;
}

clear() {
this._data.clear();
}
}

const _global = getGlobal();
if (_global.WorkerGlobalScope) {
// use window instead of self in worker
(_global as any).window = _global;
}
_global.localStorage = _global.localStorage || new MemoryStorage();
_global[identifierContainer] = _global[identifierContainer] ?? {};
_global[identifierContainer].main =
_global[identifierContainer].main ?? mfeConfig.name;
Expand All @@ -85,7 +146,7 @@ export const makeBannerScript = (
_global[identifierContainer].defaultMode ?? mfeConfig.defaultMode ?? '*';
const { main, defaultMode } = _global[identifierContainer];
const storageKey = [identifier, main].join(':');
const mode = localStorage.getItem(storageKey) ?? defaultMode;
const mode = _global.localStorage.getItem(storageKey) ?? defaultMode;
const _prefix =
typeof mfeConfig.prefix === 'object'
? mfeConfig.prefix[mode]
Expand All @@ -104,7 +165,7 @@ export const makeBannerScript = (
if (_registry) Object.assign(mfeConfig, { registry: _registry });
_global[identifierContainer].registry =
_global[identifierContainer].registry ??
localStorage.getItem(`${storageKey}:registry`) ??
_global.localStorage.getItem(`${storageKey}:registry`) ??
mfeConfig.registry ??
'*';
_global[identifierContainer].registryType =
Expand Down Expand Up @@ -133,7 +194,7 @@ export const makeBannerScript = (
_global[identifierContainer].styles || {};
_global[identifierContainer].loads = _global[identifierContainer].loads || {};
_global[identifierContainer].storage =
_global[identifierContainer].storage ?? localStorage;
_global[identifierContainer].storage ?? _global.localStorage;
_global[identifierContainer]._toBeResolvedUpdateStorage =
_global[identifierContainer]._toBeResolvedUpdateStorage === undefined
? new Set()
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { getEntry } from './getEntry';
import { ExposeOptions } from './interface';

// container for module federation dynamic import
const getContainer = (name: string) => (window as Record<string, any>)[name];
const getContainer = (name: string) =>
(globalThis as Record<string, any>)[name];

export const getModule = async ({
name,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export {
export { satisfiesVersion } from '@ringcentral/mfe-shared';

export { identifierAttribute, customElementName } from './constants';
export { getMeta } from './meta';
export { getMeta, getWorkerName } from './meta';
export { loadApp } from './loadApp';
export {
uuid,
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ export const getMeta = (name?: string) => {
rendered: Object.keys(renderContainers),
};
};

export const getWorkerName = (name: string) => {
const metaData = getMeta();
const dependencies = metaData
? metaData.data.modules[metaData.data.main]?.dependencies ?? {}
: {};
return `${name}#${btoa(JSON.stringify(dependencies))}`;
};
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {
expose,
dynamicLoad,
getMeta,
getWorkerName,
GlobalTransport,
globalTransport,
getGlobalTransport,
Expand Down
10 changes: 10 additions & 0 deletions packages/shared/src/injectScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ export const injectScript = ({
}) => Promise<void>;
}): Promise<void> => {
return new Promise((resolve, reject) => {
if (globalThis.SharedWorkerGlobalScope) {
try {
importScripts(url);
resolve();
} catch (e) {
reject(e);
console.error(`[MFE] Script Error: ${url}`);
}
return;
}
const element = document.createElement('script');
element.src = url;
element.async = true;
Expand Down

0 comments on commit f9dfbe7

Please sign in to comment.