未验证 提交 d4ee10eb 编写于 作者: P Peter Pan 提交者: GitHub

fix browser compatibility (#903)

* feat: check browser compatibility

* fix: fix browser compatibility of esmodule in web worker
上级 44834335
......@@ -10,6 +10,7 @@
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script nomodule src="%PUBLIC_URL%/_dist_/assets/js/compatibility.js"></script>
<script type="module" src="%PUBLIC_URL%/_dist_/index.js"></script>
</body>
</html>
/**
* Copyright 2020 Baidu Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
document.body.innerHTML =
'<p style="text-align: center;margin: 20px auto;">Your browser is not supported by VisualDL. <br /> Please refer to <a href="https://github.com/PaddlePaddle/VisualDL" target="_blank" rel="noreferrer noopener">VisualDL Document</a> for more information.</p>';
window.addEventListener('error', e => e.preventDefault());
......@@ -16,8 +16,7 @@
import {useEffect, useState} from 'react';
import type {InitializeData} from '~/worker';
import {WebWorker} from '~/worker';
import WebWorker from '~/worker';
const BASE_URI: string = import.meta.env.SNOWPACK_PUBLIC_BASE_URI;
......@@ -31,8 +30,7 @@ const useWorker = <D, P = unknown, E extends Error = Error>(name: string, params
const [result, setResult] = useState<WorkerResult<D, E>>({});
useEffect(() => {
const worker = new WebWorker(`${BASE_URI}/_dist_/worker/${name}.js`, {type: 'module'});
worker.emit<InitializeData>('INITIALIZE', {env: import.meta.env});
const worker = new WebWorker(`${BASE_URI}/_dist_/worker/${name}.js`);
worker.on('INITIALIZED', () => {
setResult({worker});
worker.emit('RUN', params);
......
......@@ -26,7 +26,7 @@ import type {
} from '~/resource/high-dimensional';
import {PCA, UMAP, tSNE} from '~/resource/high-dimensional';
import {WorkerSelf} from '~/worker';
import type {Runner} from '~/worker/types';
import type {tSNEOptions} from '~/resource/high-dimensional/tsne';
type InfoStepData = {
......@@ -41,86 +41,90 @@ type InfoParamsData = {
};
export type InfoData = InfoStepData | InfoResetData | InfoParamsData;
const workerSelf = new WorkerSelf();
workerSelf.emit('INITIALIZED');
workerSelf.on<CalculateParams>('RUN', ({reduction, params}) => {
switch (reduction) {
case 'pca':
return pca(params as PCAParams);
case 'tsne':
return tsne(params as TSNEParams);
case 'umap':
return umap(params as UMAPParams);
default:
return null as never;
}
});
function pca(data: PCAParams) {
const {vectors, variance, totalVariance} = PCA(data.input, data.dim, data.n);
workerSelf.emit<PCAResult>('RESULT', {
vectors: vectors as Vectors,
variance,
totalVariance
});
}
function tsne(data: TSNEParams) {
const t_sne = new tSNE({
dimension: data.n,
perplexity: data.perplexity,
epsilon: data.epsilon
});
const reset = () => {
t_sne.setData(data.input, data.dim);
workerSelf.emit<TSNEResult>('RESULT', {
vectors: t_sne.solution as [number, number, number][],
step: t_sne.step
const runner: Runner = worker => {
const pca = (data: PCAParams) => {
const {vectors, variance, totalVariance} = PCA(data.input, data.dim, data.n);
worker.emit<PCAResult>('RESULT', {
vectors: vectors as Vectors,
variance,
totalVariance
});
};
reset();
const tsne = (data: TSNEParams) => {
const t_sne = new tSNE({
dimension: data.n,
perplexity: data.perplexity,
epsilon: data.epsilon
});
workerSelf.on<InfoData>('INFO', infoData => {
const type = infoData.type;
switch (type) {
case 'step': {
t_sne.run();
return workerSelf.emit<TSNEResult>('RESULT', {
vectors: t_sne.solution as [number, number, number][],
step: t_sne.step
});
}
case 'reset': {
return reset();
}
case 'params': {
const data = (infoData as InfoParamsData).data;
if (data?.perplexity != null) {
t_sne.setPerplexity(data.perplexity);
const reset = () => {
t_sne.setData(data.input, data.dim);
worker.emit<TSNEResult>('RESULT', {
vectors: t_sne.solution as [number, number, number][],
step: t_sne.step
});
};
reset();
worker.on<InfoData>('INFO', infoData => {
const type = infoData.type;
switch (type) {
case 'step': {
t_sne.run();
return worker.emit<TSNEResult>('RESULT', {
vectors: t_sne.solution as [number, number, number][],
step: t_sne.step
});
}
case 'reset': {
return reset();
}
if (data?.epsilon != null) {
t_sne.setEpsilon(data.epsilon);
case 'params': {
const data = (infoData as InfoParamsData).data;
if (data?.perplexity != null) {
t_sne.setPerplexity(data.perplexity);
}
if (data?.epsilon != null) {
t_sne.setEpsilon(data.epsilon);
}
return;
}
return;
default:
return null as never;
}
});
};
const umap = (data: UMAPParams) => {
const result = UMAP(data.n, data.neighbors, data.input, data.dim);
if (result) {
worker.emit<UMAPResult>('RESULT', {
vectors: result.embedding as [number, number, number][],
epoch: result.nEpochs,
nEpochs: result.nEpochs
});
}
worker.on('INFO', () => {
worker.emit('INITIALIZED');
});
};
worker.on<CalculateParams>('RUN', ({reduction, params}) => {
switch (reduction) {
case 'pca':
return pca(params as PCAParams);
case 'tsne':
return tsne(params as TSNEParams);
case 'umap':
return umap(params as UMAPParams);
default:
return null as never;
}
});
}
function umap(data: UMAPParams) {
const result = UMAP(data.n, data.neighbors, data.input, data.dim);
if (result) {
workerSelf.emit<UMAPResult>('RESULT', {
vectors: result.embedding as [number, number, number][],
epoch: result.nEpochs,
nEpochs: result.nEpochs
});
}
workerSelf.on('INFO', () => {
workerSelf.emit('INITIALIZED');
});
}
worker.emit('INITIALIZED');
};
export default runner;
......@@ -17,18 +17,21 @@
import {parseFromBlob, parseFromString} from '~/resource/high-dimensional';
import type {ParseParams} from '~/resource/high-dimensional';
import {WorkerSelf} from '~/worker';
import type {Runner} from '~/worker/types';
const workerSelf = new WorkerSelf();
workerSelf.emit('INITIALIZED');
workerSelf.on<ParseParams>('RUN', async data => {
if (data) {
if (data.from === 'string') {
return workerSelf.emit('RESULT', parseFromString(data.params));
const runner: Runner = worker => {
worker.on<ParseParams>('RUN', async data => {
if (data) {
if (data.from === 'string') {
return worker.emit('RESULT', parseFromString(data.params));
}
if (data.from === 'blob') {
return worker.emit('RESULT', await parseFromBlob(data.params));
}
}
if (data.from === 'blob') {
return workerSelf.emit('RESULT', await parseFromBlob(data.params));
}
}
workerSelf.emit('RESULT', null);
});
worker.emit('RESULT', null);
});
worker.emit('INITIALIZED');
};
export default runner;
......@@ -15,68 +15,50 @@
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type {InitializeData, WorkerMessage, WorkerMessageEvent} from '~/worker/types';
export type {InitializeData};
type WorkerMessageType<T> = WorkerMessage<T>['type'];
type Handler<T> = (data: T) => unknown;
type Listeners = Partial<Record<WorkerMessageType<any>, Handler<any>[]>>;
import type {
Handler,
IWorker,
InitializeData,
Listeners,
WorkerMessage,
WorkerMessageEvent,
WorkerMessageType
} from '~/worker/types';
import {
callListener,
checkWorkerModuleSupport,
pushFunctionToListener,
removeFunctionFromListener,
runner
} from '~/worker/utils';
import EventEmitter from 'eventemitter3';
const BASE_URI: string = import.meta.env.SNOWPACK_PUBLIC_BASE_URI;
export default class WebWorker implements IWorker {
private listeners: Listeners = {};
private onceListeners: Listeners = {};
private worker: Worker | null = null;
private emitter: EventEmitter | null = null;
env = import.meta.env;
interface IWorker {
emit<T>(type: WorkerMessageType<T>, data?: T): void;
on<T>(type: WorkerMessageType<T>, handler: Handler<T>): void;
off<T>(type: WorkerMessageType<T>, handler?: Handler<T>): void;
once<T>(type: WorkerMessageType<T>, handler: Handler<T>): void;
}
constructor(name: string) {
const workerPath = `${BASE_URI}/_dist_/worker`;
function handlerInListener<T>(listeners: Listeners, type: WorkerMessageType<T>, handler: Handler<T>) {
return listeners[type]!.findIndex(f => Object.is(f, handler));
}
function pushFunctionToListener<T>(listeners: Listeners, type: WorkerMessageType<T>, handler: Handler<T>) {
if (!listeners[type]) {
listeners[type] = [];
}
if (handlerInListener(listeners, type, handler) !== -1) {
return;
}
listeners[type]!.push(handler);
}
function removeFunctionFromListener<T>(listeners: Listeners, type: WorkerMessageType<T>, handler?: Handler<T>) {
if (listeners[type]) {
if (!handler) {
delete listeners[type];
if (checkWorkerModuleSupport()) {
this.worker = new Worker(`${workerPath}/worker.js`, {type: 'module'});
this.worker.addEventListener('message', this.listener.bind(this));
this.emit<InitializeData>('INITIALIZE', {name, env: import.meta.env});
} else {
const index = handlerInListener(listeners, type, handler);
if (index !== -1) {
listeners[type]!.splice(index, 1);
}
this.emitter = new EventEmitter();
this.emitter.addListener('message', this.listener.bind(this));
window.setTimeout(() => {
runner(name, this);
}, 200);
}
}
}
function callListener<T>(this: IWorker, listeners: Listeners, data: WorkerMessage<T>) {
listeners[data.type]?.forEach(handler => {
try {
handler(data.data);
} catch (e) {
const error = e instanceof Error ? e : new Error(e);
this.emit('ERROR', error);
}
});
}
export class WorkerSelf implements IWorker {
private listeners: Listeners = {};
private onceListeners: Listeners = {};
constructor() {
self.addEventListener('message', this.listener.bind(this));
this.on<InitializeData>('INITIALIZE', ({env}) => {
self.__snowpack_env__ = env;
});
}
private listener<T>(e: WorkerMessageEvent<T>) {
callListener.call(this, this.onceListeners, e.data);
......@@ -85,10 +67,14 @@ export class WorkerSelf implements IWorker {
}
emit<T>(type: WorkerMessageType<T>, data?: T) {
self.postMessage({
type,
data
} as WorkerMessage<T>);
if (this.worker) {
this.worker.postMessage({
type,
data
} as WorkerMessage<T>);
} else if (this.emitter) {
this.emitter.emit('message', {data: {type, data}});
}
}
on<T>(type: WorkerMessageType<T>, handler: Handler<T>) {
......@@ -103,40 +89,10 @@ export class WorkerSelf implements IWorker {
once<T>(type: WorkerMessageType<T>, handler: Handler<T>) {
pushFunctionToListener(this.onceListeners, type, handler);
}
}
export class WebWorker extends Worker implements IWorker {
private listeners: Listeners = {};
private onceListeners: Listeners = {};
constructor(...args: ConstructorParameters<typeof Worker>) {
super(...args);
this.addEventListener('message', this.listener.bind(this));
}
private listener<T>(e: WorkerMessageEvent<T>) {
callListener.call(this, this.onceListeners, e.data);
callListener.call(this, this.listeners, e.data);
delete this.onceListeners[e.data.type];
}
emit<T>(type: WorkerMessageType<T>, data?: T) {
this.postMessage({
type,
data
} as WorkerMessage<T>);
}
on<T>(type: WorkerMessageType<T>, handler: Handler<T>) {
pushFunctionToListener(this.listeners, type, handler);
}
off<T>(type: WorkerMessageType<T>, handler?: Handler<T>) {
removeFunctionFromListener(this.listeners, type, handler);
removeFunctionFromListener(this.onceListeners, type, handler);
}
once<T>(type: WorkerMessageType<T>, handler: Handler<T>) {
pushFunctionToListener(this.onceListeners, type, handler);
terminate() {
if (this.worker) {
this.worker.terminate();
}
}
}
......@@ -14,21 +14,25 @@
* limitations under the License.
*/
type WorkerMessageType<T extends string, D = void> = {
type: T;
data: D;
};
/* eslint-disable @typescript-eslint/no-explicit-any */
type Env = Record<string, string>;
export type InitializeData = {
env: Record<string, string>;
name: string;
env: Env;
};
type WorkerMessageData<T extends string, D = void> = {
type: T;
data: D;
};
type InitializeMessage = WorkerMessageType<'INITIALIZE', InitializeData>;
type InitializedMessage = WorkerMessageType<'INITIALIZED'>;
type RunMessage<T> = WorkerMessageType<'RUN', T>;
type ResultMessage<T> = WorkerMessageType<'RESULT', T>;
type InfoMessage<T> = WorkerMessageType<'INFO', T>;
type ErrorMessage<E extends Error = Error> = WorkerMessageType<'ERROR', E>;
type InitializeMessage = WorkerMessageData<'INITIALIZE', InitializeData>;
type InitializedMessage = WorkerMessageData<'INITIALIZED'>;
type RunMessage<T> = WorkerMessageData<'RUN', T>;
type ResultMessage<T> = WorkerMessageData<'RESULT', T>;
type InfoMessage<T> = WorkerMessageData<'INFO', T>;
type ErrorMessage<E extends Error = Error> = WorkerMessageData<'ERROR', E>;
export type WorkerMessage<T> =
| InitializeMessage
......@@ -39,3 +43,17 @@ export type WorkerMessage<T> =
| ErrorMessage;
export type WorkerMessageEvent<T> = MessageEvent<WorkerMessage<T>>;
export type WorkerMessageType<T> = WorkerMessage<T>['type'];
export type Handler<T> = (data: T) => unknown;
export type Listeners = Partial<Record<WorkerMessageType<any>, Handler<any>[]>>;
export interface IWorker {
env: Env;
emit<T>(type: WorkerMessageType<T>, data?: T): void;
on<T>(type: WorkerMessageType<T>, handler: Handler<T>): void;
off<T>(type: WorkerMessageType<T>, handler?: Handler<T>): void;
once<T>(type: WorkerMessageType<T>, handler: Handler<T>): void;
}
export type Runner = (worker: IWorker) => void;
/**
* Copyright 2020 Baidu Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type {Handler, IWorker, Listeners, Runner, WorkerMessage, WorkerMessageType} from '~/worker/types';
export function checkWorkerModuleSupport() {
let supports = false;
const tester = {
get type() {
// it's been called, it's supported
supports = true;
return 'module' as const;
}
};
try {
// We use "blob://" as url to avoid an useless network request.
// This will either throw in Chrome
// either fire an error event in Firefox
// which is perfect since
// we don't need the worker to actually start,
// checking for the type of the script is done before trying to load it.
new Worker('blob://', tester);
} finally {
return supports;
}
}
export function handlerInListener<T>(listeners: Listeners, type: WorkerMessageType<T>, handler: Handler<T>) {
return listeners[type]!.findIndex(f => Object.is(f, handler));
}
export function pushFunctionToListener<T>(listeners: Listeners, type: WorkerMessageType<T>, handler: Handler<T>) {
if (!listeners[type]) {
listeners[type] = [];
}
if (handlerInListener(listeners, type, handler) !== -1) {
return;
}
listeners[type]!.push(handler);
}
export function removeFunctionFromListener<T>(listeners: Listeners, type: WorkerMessageType<T>, handler?: Handler<T>) {
if (listeners[type]) {
if (!handler) {
delete listeners[type];
} else {
const index = handlerInListener(listeners, type, handler);
if (index !== -1) {
listeners[type]!.splice(index, 1);
}
}
}
}
export function callListener<T>(this: IWorker, listeners: Listeners, data: WorkerMessage<T>) {
listeners[data.type]?.forEach(handler => {
try {
handler(data.data);
} catch (e) {
const error = e instanceof Error ? e : new Error(e);
this.emit('ERROR', error);
}
});
}
export async function runner(name: string, worker: IWorker) {
const {default: runner}: {default: Runner} = await import(name);
runner(worker);
}
......@@ -16,33 +16,29 @@
import * as funcs from '@visualdl/wasm';
import type {InitializeData} from '~/worker';
import {WorkerSelf} from '~/worker';
import type {Runner} from '~/worker/types';
import initWasm from '@visualdl/wasm';
const workerSelf = new WorkerSelf();
type FuncNames = Exclude<keyof typeof funcs, 'default'>;
async function init(env: Record<string, string>) {
const PUBLIC_PATH = env.SNOWPACK_PUBLIC_PATH;
const runner: Runner = async worker => {
const PUBLIC_PATH = worker.env.SNOWPACK_PUBLIC_PATH;
await initWasm(`${PUBLIC_PATH}/wasm/visualdl.wasm`);
workerSelf.emit('INITIALIZED');
workerSelf.on<{name: FuncNames; params: unknown[]}>('RUN', ({name, params}) => {
worker.on<{name: FuncNames; params: unknown[]}>('RUN', ({name, params}) => {
try {
// eslint-disable-next-line @typescript-eslint/ban-types
const result = (funcs[name] as Function)(...params);
workerSelf.emit('RESULT', result);
worker.emit('RESULT', result);
} catch (e) {
if (e.message !== 'unreachable') {
throw e;
}
}
});
}
workerSelf.on<InitializeData>('INITIALIZE', ({env}) => {
init(env);
});
worker.emit('INITIALIZED');
};
export default runner;
/**
* Copyright 2020 Baidu Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type {
Handler,
IWorker,
InitializeData,
Listeners,
WorkerMessage,
WorkerMessageEvent,
WorkerMessageType
} from '~/worker/types';
import {callListener, pushFunctionToListener, removeFunctionFromListener, runner} from '~/worker/utils';
class WorkerSelf implements IWorker {
private listeners: Listeners = {};
private onceListeners: Listeners = {};
env = {};
constructor() {
self.addEventListener('message', this.listener.bind(this));
}
private listener<T>(e: WorkerMessageEvent<T>) {
callListener.call(this, this.onceListeners, e.data);
callListener.call(this, this.listeners, e.data);
delete this.onceListeners[e.data.type];
}
emit<T>(type: WorkerMessageType<T>, data?: T) {
self.postMessage({
type,
data
} as WorkerMessage<T>);
}
on<T>(type: WorkerMessageType<T>, handler: Handler<T>) {
pushFunctionToListener(this.listeners, type, handler);
}
off<T>(type: WorkerMessageType<T>, handler?: Handler<T>) {
removeFunctionFromListener(this.listeners, type, handler);
removeFunctionFromListener(this.onceListeners, type, handler);
}
once<T>(type: WorkerMessageType<T>, handler: Handler<T>) {
pushFunctionToListener(this.onceListeners, type, handler);
}
}
const worker = new WorkerSelf();
worker.once<InitializeData>('INITIALIZE', ({name, env}) => {
worker.env = env;
runner(name, worker);
});
......@@ -19,7 +19,6 @@ declare global {
webkitAudioContext: AudioContext;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_hmt: any[];
__snowpack_env__: Record<string, string>;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册