Browse Source

Merge branch 'master' of ssh://code.fineres.com:7999/~dailer/fineui

es6
zsmj 2 years ago
parent
commit
f51f449222
  1. 2
      .eslintrc
  2. 15
      examples/worker_new.html
  3. 105
      examples/worker_new/index.js
  4. 80
      examples/worker_new/worker.js
  5. 6
      package.json
  6. 4
      src/base/single/editor/editor.multifile.js
  7. 7
      src/base/single/input/file.js
  8. 4
      src/core/2.base.js
  9. 8
      src/core/conflict.js
  10. 2
      tsconfig.json
  11. 2
      types/globals.d.ts
  12. 6
      typescript/core/base.ts
  13. 28
      typescript/core/worker/action/worker.action.ts
  14. 131
      typescript/core/worker/controller/worker.controller.ts
  15. 85
      typescript/core/worker/controller/worker.main_thread.controller.ts
  16. 21
      typescript/core/worker/controller/worker.worker_thread.controller.ts
  17. 200
      typescript/core/worker/worker.channel.ts
  18. 48
      typescript/core/worker/worker.core.ts
  19. 49
      typescript/core/worker/worker.main_thread.ts
  20. 28
      typescript/core/worker/worker.worker_thread.ts
  21. 19
      typescript/core/worker/workers.ts
  22. 3
      typescript/index.ts

2
.eslintrc

@ -26,7 +26,7 @@
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"overrides": [{
"files": ["src/*.js","src/**/*.js", "demo/*.js", "demo/**/*.js", "i18n/**/*.js", "i18n/*.js", "test/**/*.js", "test/*.js"],
"files": ["src/*.js","src/**/*.js", "demo/*.js", "demo/**/*.js", "i18n/**/*.js", "i18n/*.js", "test/**/*.js", "test/*.js", "examples/*.js", "examples/**/*.js"],
"extends": "plugin:@fui/es5",
"rules": {
"no-param-reassign": "off",

15
examples/worker_new.html

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="http://fanruan.design/fineui/2.0/fineui.min.css" />
<script src="http://fanruan.design/fineui/2.0/fineui.min.js"></script>
</head>
<body>
<div id="wrapper"></div>
<script src="./worker_new/index.js"></script>
</body>
</html>

105
examples/worker_new/index.js

@ -0,0 +1,105 @@
document.cookie = "test=demo";
// worker获取主线程资源
var CookieAction = BI.inherit(BI.Workers.WorkerBaseAction, {
addActionHandler: function() {
this.controller.addActionHandler("Cookie", this.getCookie.bind(this));
},
getCookie: function() {
return document.cookie;
}
});
// 调用worker计算
var FibonacciAction = BI.inherit(BI.Workers.WorkerBaseAction, {
addActionHandler: function() {},
getResult: function(times) {
return this.controller.requestPromise("Fibonacci", { times: times })
.then(function(v) {
console.log(v);
});
}
});
// 主线程与worker多次交互
const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, {
addActionHandler: function() {
this.controller.addActionHandler("HeartBeatChecked", this.recieveHeartBeatChecked.bind(this));
},
recieveHeartBeatChecked: function (payload) {
console.log("heartbeat: " + payload.time);
},
startHeatBeatCheck: function() {
return this.controller.request("HeartBeatCheckStart");
},
stopHeatBeatCheck: function() {
return this.controller.request("HeartBeatCheckStop");
}
});
var WorkerThreadWorker = BI.inherit(BI.Workers.MainThreadWorker, {
initActions: function() {
this.cookieAction = this.createAction(CookieAction);
this.fibonacciAction = this.createAction(FibonacciAction);
this.heartBeatCheckAction = this.createAction(HeartBeatCheckAction);
},
calculateFibonacci: function(times) {
this.fibonacciAction.getResult(times);
},
startHeatBeatCheck: function() {
this.heartBeatCheckAction.startHeatBeatCheck();
},
stopHeatBeatCheck: function() {
this.heartBeatCheckAction.stopHeatBeatCheck();
}
});
var mainThreadWorker = new WorkerThreadWorker({
workerUrl: "./worker_new/worker.js",
workerName: "demo"
});
BI.createWidget({
type: "bi.vertical",
element: "#wrapper",
vgap: 10,
hgap: 10,
items: [
{
type: "bi.button",
text: "点击计算斐波那契数列第40项",
width: 200,
handler: function() {
console.log("click");
mainThreadWorker.calculateFibonacci(40);
}
},
{
type: "bi.button",
text: "开始心跳",
width: 200,
handler: function() {
mainThreadWorker.startHeatBeatCheck();
}
},
{
type: "bi.button",
text: "停止心跳",
width: 200,
handler: function() {
mainThreadWorker.stopHeatBeatCheck();
}
}
]
});

80
examples/worker_new/worker.js

@ -0,0 +1,80 @@
self.importScripts("https://fanruan.design/fineui/fineui_without_jquery_polyfill.js");
var CookieAction = BI.inherit(BI.Workers.WorkerBaseAction, {
addActionHandler: function() {},
getCookie: function() {
return this.controller.requestPromise("Cookie");
}
});
function fibonacci(n) {
if (n === 1 || n === 2) {
return 1;
}
return fibonacci(n - 2) + fibonacci(n - 1);
}
var FibonacciAction = BI.inherit(BI.Workers.WorkerBaseAction, {
addActionHandler: function() {
this.controller.addActionHandler("Fibonacci", this.getResult.bind(this));
},
getResult: function(payload) {
return fibonacci(payload.times);
}
});
const HeartBeatCheckAction = BI.inherit(BI.Workers.WorkerBaseAction, {
addActionHandler: function() {
this.controller.addActionHandler("HeartBeatCheckStart", this.startHeatBeatCheck.bind(this));
this.controller.addActionHandler("HeartBeatCheckStop", this.stopHeatBeatCheck.bind(this));
},
startHeatBeatCheck: function() {
var self = this;
if (!this.timer) {
console.log("heart beat check started");
this.timer = setInterval(function() {
// 模拟请求
setTimeout(function() {
self.controller.request("HeartBeatChecked", {
time: new Date()
});
}, 50);
}, 5 * 1000);
} else {
console.log("heart beat has already started!");
}
},
stopHeatBeatCheck: function() {
console.log("heart beat check stopped");
clearInterval(this.timer);
}
});
var WorkerThreadWorker = BI.inherit(BI.Workers.WorkerThreadWorker, {
initActions: function() {
this.cookieAction = this.createAction(CookieAction);
this.fibonacciAction = this.createAction(FibonacciAction);
this.heartBeatCheckAction = this.createAction(HeartBeatCheckAction);
},
fetchCookie: function() {
return this.cookieAction.getCookie()
.then(function (v) {
console.log(v);
});
}
});
var workerThreadWorker = new WorkerThreadWorker();
workerThreadWorker.fetchCookie();

6
package.json

@ -1,6 +1,6 @@
{
"name": "fineui",
"version": "2.0.20220627090545",
"version": "2.0.20220701111336",
"description": "fineui",
"main": "dist/fineui_without_conflict.min.js",
"types": "dist/lib/index.d.ts",
@ -8,7 +8,7 @@
"@babel/core": "^7.17.4",
"@babel/polyfill": "7.6.0",
"@fui/babel-preset-fineui": "^2.0.0",
"@fui/eslint-plugin": "1.0.11",
"@fui/eslint-plugin": "1.0.15",
"@types/node": "15.6.1",
"autoprefixer": "9.6.1",
"babel-loader": "8.0.6",
@ -47,7 +47,7 @@
"source-map-loader": "0.2.4",
"style-loader": "0.23.1",
"terser-webpack-plugin": "4.2.3",
"typescript": "3.5.2",
"typescript": "3.9.2",
"webpack": "4.35.2",
"webpack-cli": "3.3.5",
"webpack-dev-server": "3.7.2",

4
src/base/single/editor/editor.multifile.js

@ -71,6 +71,10 @@ BI.MultifileEditor = BI.inherit(BI.Widget, {
this.file.reset();
},
setUrl: function (v) {
this.file.setUrl(v);
},
setMaxFileLength: function (v) {
this.file.setMaxFileLength(v);
},

7
src/base/single/input/file.js

@ -655,6 +655,13 @@
});
},
setUrl: function(v) {
this.options.url = v;
if (this.wrap) {
this.wrap.url = v;
}
},
setMaxFileLength: function (v) {
this.options.maxLength = v;
if (this.wrap) {

4
src/core/2.base.js

@ -524,6 +524,10 @@
isWindow: function (obj) {
return obj != null && obj == obj.window;
},
isPromise: function(obj) {
return !!obj && (BI.isObject(obj) || BI.isFunction(obj)) && BI.isFunction(obj.then);
}
});

8
src/core/conflict.js

@ -1,7 +1,7 @@
if (!window.$ && !window.jQuery) {
window.jQuery = window.$ = BI.jQuery;
if (!_global.$ && !_global.jQuery) {
_global.jQuery = _global.$ = BI.jQuery;
}
if (!window._) {
window._ = BI._;
if (!_global._) {
_global._ = BI._;
}

2
tsconfig.json

@ -28,5 +28,7 @@
"types/*.d.ts",
"src/*.js",
"src/**/*.js",
"examples/*.js",
"examples/**/*.js",
]
}

2
types/globals.d.ts vendored

@ -11,3 +11,5 @@ declare const Fix: Obj;
declare interface String {
replaceAll(regx: string, callback: (str: string) => void): string;
}
declare const _global: typeof window;

6
typescript/core/base.ts

@ -376,6 +376,12 @@ export interface _base {
getDate: (...args: (number | string)[]) => Date;
getTime: (...args: any[]) => number;
/**
* promise
* @param obj
*/
isPromise: (obj: any) => obj is Promise<any>;
}
type merge = {

28
typescript/core/worker/action/worker.action.ts

@ -0,0 +1,28 @@
import type { WorkerBaseController } from "../controller/worker.controller";
/**
*
*/
export class WorkerBaseAction {
/**
*
*/
protected controller: WorkerBaseController;
/**
* 线 action ,
*/
protected threadAction: any;
public constructor(controller: WorkerBaseController, threadAction: any) {
this.controller = controller;
this.threadAction = threadAction;
this.addActionHandler();
}
/**
*
*/
protected addActionHandler() {}
}

131
typescript/core/worker/controller/worker.controller.ts

@ -0,0 +1,131 @@
import type { IWorkerController, IWorkerMessage } from '../worker.core';
import { WorkerChannel } from '../worker.channel';
/**
*
*
* @class WorkerBaseController
*/
export class WorkerBaseController implements IWorkerController {
/**
* worker,
*/
protected worker: Worker;
/**
* Channel,
*/
protected channel: WorkerChannel;
/**
* Map
*/
protected actionHandlerMap: {
[propsName: string]: (payload: any) => any;
};
public constructor() {
this.actionHandlerMap = {};
}
/**
*
*
* @param actionType
* @param payload
*/
public request(actionType: string, payload: any): void {
if (this.channel) {
return this.channel.request(actionType, payload);
}
console.error('No channel.');
return;
}
/**
* Promise , then
*
* @param actionType
* @param payload
* @param [timeout] ; Worker , ,
*/
public requestPromise<T = any>(actionType: string, payload: any = '', timeout?: number): Promise<T> {
// 有 Channel 实例才能进行通信, 此时还没有实例化是浏览器不支持创建 worker
if (this.channel) {
return this.channel.requestPromise<T>(actionType, payload, timeout);
}
// 兼容上层调用的 .then().catch()
return Promise.reject(new Error('No channel.'));
}
/**
* ,
*
* @param actionType
* @param handler
*/
public addActionHandler(actionType: string, handler: (payload: any) => any): void {
if (this.hasActionHandler(actionType)) {
throw new Error(`Add action \`${actionType}\` handler repeat`);
}
this.actionHandlerMap[actionType] = handler;
}
/**
* , Channel
*
* @param message
* @returns
*/
public actionHandler(message: IWorkerMessage): Promise<any> {
const { actionType, payload } = message;
if (this.hasActionHandler(actionType)) {
// 执行指定的事务处理器, 并返回 Promise 封装的事务结果
try {
const actionResult = this.actionHandlerMap[actionType](payload);
// 对于 Promise 形式的结果, 需要进行 Promise 错误捕获
if (BI.isPromise(actionResult)) {
return actionResult.catch(error => Promise.reject(error));
}
// 对数据结果, 包装为 Promise
return Promise.resolve(actionResult);
} catch (error) {
// 继续抛出给外层
return Promise.reject(error);
}
} else {
throw new Error(`Not Found Session Handler \`${actionType}\`.`);
}
}
/**
* worker onmessage
*
* @param {(event: any) => void} onmessage
* @returns {() => void}
*/
public addOnmessageListener(onmessage: (event: any) => void): () => void {
this.worker.addEventListener('message', onmessage);
// 返回移除监听函数
return () => {
this.worker.removeEventListener('message', onmessage);
};
}
/**
*
*
* @param actionType
* @returns {boolean}
*/
protected hasActionHandler(actionType: string): boolean {
return !!this.actionHandlerMap[actionType];
}
}

85
typescript/core/worker/controller/worker.main_thread.controller.ts

@ -0,0 +1,85 @@
import { WorkerChannel } from "../worker.channel";
import type { IWorkerOptions } from "../worker.core";
import { WorkerBaseController } from "./worker.controller";
export class WorkerMainThreadController extends WorkerBaseController {
/**
* HTML Worker Class
*/
public static hasWorkerClass = !!_global.Worker;
/**
* new Worker, Wroker Class
*/
public canNewWorker: boolean = WorkerMainThreadController.hasWorkerClass;
/**
* 线 new Worker
*/
public timeBeforeNewWorker: number;
/**
* 线 new Worker
*/
public timeAfterNewWorker: number;
public constructor(options: IWorkerOptions) {
super();
if (!this.canNewWorker) {
// 都没有 Worker Class, 没法继续了
return;
}
this.newWorker(options);
}
/**
* Worker 线
*/
public terminate(): void {
this.worker?.terminate();
}
protected reportActionHandlerError(actionType: string, error: any): void {
console.error(`Worker aciton ${actionType}:`, error);
// 主线程的报错, 在 window.onerror 中可以拿到报错堆栈, 直接抛出即可
throw new Error(error);
}
/**
* Worker 线
*/
private newWorker(options: IWorkerOptions) {
this.timeBeforeNewWorker = Date.now();
try {
// 主线程通过 new Worker() 获取 Worker 实例
this.worker = new Worker(options.workerUrl, {
name: options.workerName,
});
/**
* worker
* window.onerror worker.onerror( Worker )
*/
this.worker.onerror = (error): void => {
console.error('Worker onerror:', error);
};
this.timeAfterNewWorker = Date.now();
// 实例化 Channel
this.channel = new WorkerChannel(this.worker, {
actionHandler: this.actionHandler.bind(this),
});
} catch (error) {
console.error('Init worker fail:', error);
// 创建 worker 失败, 标识改为不支持
this.canNewWorker = false;
}
}
}

21
typescript/core/worker/controller/worker.worker_thread.controller.ts

@ -0,0 +1,21 @@
import { WorkerChannel } from "../worker.channel";
import { WorkerBaseController } from "./worker.controller";
export class WorkerThreadController extends WorkerBaseController {
public constructor() {
super();
// Worker 线程中的全局环境 self 就是 Worker 实例
this.worker = self as any;
this.channel = new WorkerChannel(this.worker, {
actionHandler: this.actionHandler.bind(this),
});
}
protected reportActionHandlerError(actionType: string, error: any): void {
console.error(`Worker aciton ${actionType}:`, error);
// 正常抛出
throw new Error(error);
}
}

200
typescript/core/worker/worker.channel.ts

@ -0,0 +1,200 @@
import { IWorkerController, WorkerMessageType, IWorkerMessage } from './worker.core';
const COMMUNICATION_TIMEOUT = 30000;
/**
*
*/
export class WorkerChannel {
/**
* Web Worker
*/
private worker: Worker;
/**
*
*/
private controller: IWorkerController;
/**
* Map
*/
private sessionHandlerMap: {
[propsName: string]: Function;
};
public constructor(worker: Worker, controller: IWorkerController) {
this.worker = worker;
this.controller = controller;
this.sessionHandlerMap = {};
// 绑定 worker onmessage 事件的回调
this.worker.addEventListener('message', this.onmessage.bind(this));
}
/**
*
*
* @param sessionId Id
* @param payload
*/
public response(sessionId: string, actionType: string, payload: any): void {
this.postMessage({
messageType: WorkerMessageType.REPLY,
actionType,
payload,
sessionId,
});
}
/**
* ,
*
* @param actionType
* @param payload
*/
public request(actionType: string, payload: any): void {
const sessionId = this.generateSessionId();
this.postMessage({
messageType: WorkerMessageType.REQUEST,
actionType,
payload,
sessionId,
});
// 不等待结果, 还会收到响应, 添加个空的会话响应器
this.addSessionHandler(sessionId, () => {});
}
/**
* ,
*
* @param actionType
* @param payload
* @param timeout
* @returns {Promise<IWorkerMessage>} Promise
*/
public requestPromise<T>(actionType: string, payload: any, timeout = COMMUNICATION_TIMEOUT): Promise<T> {
const sessionId = this.generateSessionId();
const message = {
messageType: WorkerMessageType.REQUEST,
actionType,
payload,
sessionId,
};
// 请求封装为一个 Promise, 等待会话响应器进行 resolve
const PromiseFunction = (resolve: Function, reject: Function): any => {
// 启动请求超时计时器
const timeoutHandler = setTimeout(() => {
clearTimeout(timeoutHandler);
reject();
}, timeout);
const sessionHandler: Function = (message: IWorkerMessage) => {
// 会话回调函数, 开始处理响应
this.deleteSessionHandler(message.sessionId);
clearTimeout(timeoutHandler);
resolve(message.payload);
};
this.addSessionHandler(sessionId, sessionHandler);
// 开始发送请求
this.postMessage(message);
};
return new Promise(PromiseFunction);
}
/**
*
*
* , , ;
*
* @param event worker
*/
private onmessage(event: { data: IWorkerMessage }): void {
const { data: message } = event;
const { messageType, sessionId, actionType } = message;
// 接收到请求
if (messageType === WorkerMessageType.REQUEST) {
// 处理请求
this.controller.actionHandler(message)
.then(actionResult => {
// 响应请求
this.response(sessionId, actionType, actionResult);
});
}
// 接收到响应
if (messageType === WorkerMessageType.REPLY) {
// 处理响应
if (this.hasSessionHandler(sessionId)) {
this.sessionHandlerMap[sessionId](message);
} else {
throw new Error(`Session \`${sessionId}\` handler no exist`);
}
}
}
/**
* worker postMessage
* structured clone transfer 2
*
* @param message
*/
private postMessage(message: IWorkerMessage): void {
this.worker.postMessage(message);
}
/**
*
*
* @param sessionId Id
* @param handler
*/
private addSessionHandler(sessionId: string, handler: Function): void {
if (!this.hasSessionHandler(sessionId)) {
this.sessionHandlerMap[sessionId] = handler;
} else {
throw new Error(`SessionId \`${sessionId}\` already exist!`);
}
}
/**
*
*
* @param sessionId
*/
private deleteSessionHandler(sessionId: string): void {
if (this.hasSessionHandler(sessionId)) {
delete this.sessionHandlerMap[sessionId];
}
}
/**
* Id
*
* @returns Id
*/
private generateSessionId(): string {
const sessionId = `w_${BI.UUID()}`;
return sessionId;
}
/**
*
*
* @param sessionId Id
* @returns {boolean}
*/
private hasSessionHandler(sessionId: string): boolean {
return !!this.sessionHandlerMap[sessionId];
}
}

48
typescript/core/worker/worker.core.ts

@ -0,0 +1,48 @@
/**
*
*/
export const WorkerMessageType = {
REQUEST: 'REQUEST',
REPLY: 'REPLY',
};
/**
*
*/
export interface IWorkerMessage {
messageType: string;
actionType: string;
sessionId: string;
/**
*
*/
payload: any;
}
/**
* interface
*/
export interface IWorkerController {
/**
*
*/
actionHandler: (message: IWorkerMessage) => Promise<any>;
}
/**
* Worker创建配置
*/
export interface IWorkerOptions {
/**
* worker url
*/
workerUrl: string;
/**
* worker
*/
workerName: string;
}

49
typescript/core/worker/worker.main_thread.ts

@ -0,0 +1,49 @@
import type { WorkerBaseAction } from "./action/worker.action";
import { WorkerMainThreadController } from "./controller/worker.main_thread.controller";
import { IWorkerOptions } from "./worker.core";
/**
* 线Worker
*/
export abstract class MainThreadWorker {
/**
* Worker
*/
public name: string;
/**
* 线
*/
public controller: WorkerMainThreadController;
/**
* Worker
*/
protected isTerminated = false;
public constructor(options: IWorkerOptions) {
this.name = options.workerName;
this.controller = new WorkerMainThreadController(options);
this.initActions();
}
protected abstract initActions(): void;
/**
* worker
* action
*/
public terminate(): void {
this.controller.terminate();
// 设置终止标志位
this.isTerminated = true;
}
/**
* action
* @param Action action类
*/
protected createAction<T extends typeof WorkerBaseAction>(Action: T): InstanceType<T> {
return (new Action(this.controller, this)) as InstanceType<T>;
}
}

28
typescript/core/worker/worker.worker_thread.ts

@ -0,0 +1,28 @@
import type { WorkerBaseAction } from "./action/worker.action";
import { WorkerThreadController } from "./controller/worker.worker_thread.controller";
/**
* worker线程实例
*/
export abstract class WorkerThreadWorker {
/**
* Worker 线
*/
protected controller: WorkerThreadController;
public constructor() {
this.controller = new WorkerThreadController();
this.initActions();
}
protected abstract initActions(): void;
/**
* action
* @param Action action类
*/
protected createAction<T extends typeof WorkerBaseAction>(Action: T): InstanceType<T> {
return (new Action(this.controller, this)) as InstanceType<T>;
}
}

19
typescript/core/worker/workers.ts

@ -0,0 +1,19 @@
import { WorkerChannel } from "./worker.channel";
import { WorkerBaseController } from "./controller/worker.controller";
import { WorkerMessageType } from "./worker.core";
import { WorkerMainThreadController } from "./controller/worker.main_thread.controller";
import { WorkerThreadController } from "./controller/worker.worker_thread.controller";
import { WorkerBaseAction } from "./action/worker.action";
import { MainThreadWorker } from "./worker.main_thread";
import { WorkerThreadWorker } from "./worker.worker_thread";
export const Workers = {
WorkerChannel,
WorkerBaseController,
WorkerMainThreadController,
WorkerThreadController,
WorkerBaseAction,
MainThreadWorker,
WorkerThreadWorker,
WorkerMessageType,
};

3
typescript/index.ts

@ -188,6 +188,7 @@ import { VerticalStickyLayout } from "./core/wrapper/layout/sticky/sticky.vertic
import { HorizontalStickyLayout } from "./core/wrapper/layout/sticky/sticky.horizontal";
import { TableLayout } from "./core/wrapper/layout/layout.table";
import './shims-tsx';
import { Workers } from "./core/worker/workers";
export interface BI extends _func, _i18n, _base, _inject, _var, _web, _utils {
@ -382,10 +383,12 @@ export interface BI extends _func, _i18n, _base, _inject, _var, _web, _utils {
VerticalStickyLayout: typeof VerticalStickyLayout;
HorizontalStickyLayout: typeof HorizontalStickyLayout;
TableLayout: typeof TableLayout;
Workers: typeof Workers;
}
export default {
Decorators: decorator,
Workers,
};
export {
OB,

Loading…
Cancel
Save