浏览代码

Feature/wsadapter (#1423) (#1391)

* WS adapter

* revert bundle

* add taipy designer entry

* extract bundle

* use ws adapter properly

* export wssendmessage

* dont export main app -> reduce bundle size

* ws message type to include string in d.ts

* manage external ws message

* add msg_type

* export correctly

* per Fred

* per Fabien
Dinh Long Nguyen 11 月之前
父节点
当前提交
8a444b6cff

+ 11 - 1
frontend/taipy-gui/base/src/app.ts

@@ -4,7 +4,8 @@ import { uploadFile } from "../../src/workers/fileupload";
 
 import { Socket, io } from "socket.io-client";
 import { DataManager, ModuleData } from "./dataManager";
-import { initSocket } from "./utils";
+import { initSocket } from "./socket";
+import { TaipyWsAdapter, WsAdapter } from "./wsAdapter";
 
 export type OnInitHandler = (taipyApp: TaipyApp) => void;
 export type OnChangeHandler = (taipyApp: TaipyApp, encodedName: string, value: unknown) => void;
@@ -25,6 +26,7 @@ export class TaipyApp {
     context: string;
     path: string | undefined;
     routes: Route[] | undefined;
+    wsAdapters: WsAdapter[];
 
     constructor(
         onInit: OnInitHandler | undefined = undefined,
@@ -43,6 +45,7 @@ export class TaipyApp {
         this.routes = undefined;
         this.path = path;
         this.socket = socket;
+        this.wsAdapters = [new TaipyWsAdapter()];
         // Init socket io connection
         initSocket(socket, this);
     }
@@ -51,6 +54,7 @@ export class TaipyApp {
     get onInit() {
         return this._onInit;
     }
+
     set onInit(handler: OnInitHandler | undefined) {
         if (handler !== undefined && handler.length !== 1) {
             throw new Error("onInit() requires one parameter");
@@ -61,6 +65,7 @@ export class TaipyApp {
     get onChange() {
         return this._onChange;
     }
+
     set onChange(handler: OnChangeHandler | undefined) {
         if (handler !== undefined && handler.length !== 3) {
             throw new Error("onChange() requires three parameters");
@@ -71,6 +76,7 @@ export class TaipyApp {
     get onNotify() {
         return this._onNotify;
     }
+
     set onNotify(handler: OnNotifyHandler | undefined) {
         if (handler !== undefined && handler.length !== 3) {
             throw new Error("onNotify() requires three parameters");
@@ -105,6 +111,10 @@ export class TaipyApp {
     }
 
     // Public methods
+    registerWsAdapter(wsAdapter: WsAdapter) {
+        this.wsAdapters.unshift(wsAdapter);
+    }
+
     getEncodedName(varName: string, module: string) {
         return this.variableData?.getEncodedName(varName, module);
     }

+ 1 - 1
frontend/taipy-gui/base/src/dataManager.ts

@@ -2,7 +2,7 @@ export type ModuleData = Record<string, VarName>;
 
 export type VarName = Record<string, VarData>;
 
-interface VarData {
+export interface VarData {
     type: string;
     value: unknown;
     encoded_name: string;

+ 9 - 0
frontend/taipy-gui/base/src/exports.ts

@@ -0,0 +1,9 @@
+import { WsAdapter } from "./wsAdapter";
+import { sendWsMessage } from "../../src/context/wsUtils";
+// import { TaipyApp } from "./app";
+
+export {
+    WsAdapter,
+    sendWsMessage,
+    // TaipyApp,
+};

+ 7 - 0
frontend/taipy-gui/base/src/packaging/package.json

@@ -0,0 +1,7 @@
+{
+  "name": "taipy-gui-base",
+  "version": "3.2.0",
+  "private": true,
+  "main": "./taipy-gui-base.js",
+  "types": "./taipy-gui-base.d.ts"
+}

+ 113 - 0
frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts

@@ -0,0 +1,113 @@
+import { Socket } from "socket.io-client";
+
+export type ModuleData = Record<string, VarName>;
+export type VarName = Record<string, VarData>;
+export interface VarData {
+    type: string;
+    value: unknown;
+    encoded_name: string;
+}
+declare class DataManager {
+    _data: Record<string, unknown>;
+    _init_data: ModuleData;
+    constructor(variableModuleData: ModuleData);
+    init(variableModuleData: ModuleData): ModuleData;
+    getEncodedName(varName: string, module: string): string | undefined;
+    getName(encodedName: string): [string, string] | undefined;
+    get(encodedName: string): unknown;
+    getInfo(encodedName: string): VarData | undefined;
+    getDataTree(): ModuleData;
+    getAllData(): Record<string, unknown>;
+    update(encodedName: string, value: unknown): void;
+}
+export type OnInitHandler = (taipyApp: TaipyApp) => void;
+export type OnChangeHandler = (taipyApp: TaipyApp, encodedName: string, value: unknown) => void;
+export type OnNotifyHandler = (taipyApp: TaipyApp, type: string, message: string) => void;
+export type OnReloadHandler = (taipyApp: TaipyApp, removedChanges: ModuleData) => void;
+export type Route = [string, string];
+export declare class TaipyApp {
+    socket: Socket;
+    _onInit: OnInitHandler | undefined;
+    _onChange: OnChangeHandler | undefined;
+    _onNotify: OnNotifyHandler | undefined;
+    _onReload: OnReloadHandler | undefined;
+    variableData: DataManager | undefined;
+    functionData: DataManager | undefined;
+    appId: string;
+    clientId: string;
+    context: string;
+    path: string | undefined;
+    routes: Route[] | undefined;
+    wsAdapters: WsAdapter[];
+    constructor(
+        onInit?: OnInitHandler | undefined,
+        onChange?: OnChangeHandler | undefined,
+        path?: string | undefined,
+        socket?: Socket | undefined
+    );
+    get onInit(): OnInitHandler | undefined;
+    set onInit(handler: OnInitHandler | undefined);
+    get onChange(): OnChangeHandler | undefined;
+    set onChange(handler: OnChangeHandler | undefined);
+    get onNotify(): OnNotifyHandler | undefined;
+    set onNotify(handler: OnNotifyHandler | undefined);
+    get onReload(): OnReloadHandler | undefined;
+    set onReload(handler: OnReloadHandler | undefined);
+    init(): void;
+    registerWsAdapter(wsAdapter: WsAdapter): void;
+    getEncodedName(varName: string, module: string): string | undefined;
+    getName(encodedName: string): [string, string] | undefined;
+    get(encodedName: string): unknown;
+    getInfo(encodedName: string): VarData | undefined;
+    getDataTree(): ModuleData | undefined;
+    getAllData(): Record<string, unknown> | undefined;
+    getFunctionList(): string[];
+    getRoutes(): Route[] | undefined;
+    update(encodedName: string, value: unknown): void;
+    getContext(): string;
+    updateContext(path?: string | undefined): void;
+    trigger(actionName: string, triggerId: string, payload?: Record<string, unknown>): void;
+    upload(encodedName: string, files: FileList, progressCallback: (val: number) => void): Promise<string>;
+    getPageMetadata(): any;
+}
+export type WsMessageType =
+    | "A"
+    | "U"
+    | "DU"
+    | "MU"
+    | "RU"
+    | "AL"
+    | "BL"
+    | "NA"
+    | "ID"
+    | "MS"
+    | "DF"
+    | "PR"
+    | "ACK"
+    | "GMC"
+    | "GDT"
+    | "AID"
+    | "GR";
+export interface WsMessage {
+    type: WsMessageType | str;
+    name: string;
+    payload: Record<string, unknown> | unknown;
+    propagate: boolean;
+    client_id: string;
+    module_context: string;
+    ack_id?: string;
+}
+export declare const sendWsMessage: (
+    socket: Socket | undefined,
+    type: WsMessageType | str,
+    name: string,
+    payload: Record<string, unknown> | unknown,
+    id: string,
+    moduleContext?: string,
+    propagate?: boolean,
+    serverAck?: (val: unknown) => void
+) => string;
+export declare abstract class WsAdapter {
+    abstract supportedMessageTypes: string[];
+    abstract handleWsMessage(message: WsMessage, app: TaipyApp): boolean;
+}

+ 46 - 0
frontend/taipy-gui/base/src/socket.ts

@@ -0,0 +1,46 @@
+import { Socket } from "socket.io-client";
+import { WsMessage, sendWsMessage } from "../../src/context/wsUtils";
+import { TaipyApp } from "./app";
+
+export const initSocket = (socket: Socket, taipyApp: TaipyApp) => {
+    socket.on("connect", () => {
+        if (taipyApp.clientId === "" || taipyApp.appId === "") {
+            taipyApp.init();
+        }
+    });
+    // Send a request to get App ID to verify that the app has not been reloaded
+    socket.io.on("reconnect", () => {
+        console.log("WebSocket reconnected");
+        sendWsMessage(socket, "AID", "reconnect", taipyApp.appId, taipyApp.clientId, taipyApp.context);
+    });
+    // try to reconnect on connect_error
+    socket.on("connect_error", (err) => {
+        console.log("Error connecting WebSocket: ", err);
+        setTimeout(() => {
+            socket && socket.connect();
+        }, 500);
+    });
+    // try to reconnect on server disconnection
+    socket.on("disconnect", (reason, details) => {
+        console.log("WebSocket disconnected due to: ", reason, details);
+        if (reason === "io server disconnect") {
+            socket && socket.connect();
+        }
+    });
+    // handle message data from backend
+    socket.on("message", (message: WsMessage) => {
+        // handle messages with registered websocket adapters
+        for (const adapter of taipyApp.wsAdapters) {
+            if (adapter.supportedMessageTypes.includes(message.type)) {
+                const messageResolved = adapter.handleWsMessage(message, taipyApp);
+                if (messageResolved) {
+                    return;
+                }
+            }
+        }
+    });
+    // only now does the socket tries to open/connect
+    if (!socket.connected) {
+        socket.connect();
+    }
+};

+ 0 - 117
frontend/taipy-gui/base/src/utils.ts

@@ -1,117 +0,0 @@
-import merge from "lodash/merge";
-import { Socket } from "socket.io-client";
-import { IdMessage, storeClientId } from "../../src/context/utils";
-import { WsMessage, sendWsMessage } from "../../src/context/wsUtils";
-import { TaipyApp } from "./app";
-import { DataManager, ModuleData } from "./dataManager";
-
-interface MultipleUpdatePayload {
-    name: string;
-    payload: { value: unknown };
-}
-
-interface AlertMessage extends WsMessage {
-    atype: string;
-    message: string;
-}
-
-const initWsMessageTypes = ["ID", "AID", "GMC"];
-
-export const initSocket = (socket: Socket, taipyApp: TaipyApp) => {
-    socket.on("connect", () => {
-        if (taipyApp.clientId === "" || taipyApp.appId === "") {
-            taipyApp.init();
-        }
-    });
-    // Send a request to get App ID to verify that the app has not been reloaded
-    socket.io.on("reconnect", () => {
-        console.log("WebSocket reconnected")
-        sendWsMessage(socket, "AID", "reconnect", taipyApp.appId, taipyApp.clientId, taipyApp.context);
-    });
-    // try to reconnect on connect_error
-    socket.on("connect_error", (err) => {
-        console.log("Error connecting WebSocket: ", err);
-        setTimeout(() => {
-            socket && socket.connect();
-        }, 500);
-    });
-    // try to reconnect on server disconnection
-    socket.on("disconnect", (reason, details) => {
-        console.log("WebSocket disconnected due to: ", reason, details);
-        if (reason === "io server disconnect") {
-            socket && socket.connect();
-        }
-    });
-    // handle message data from backend
-    socket.on("message", (message: WsMessage) => {
-        processWsMessage(message, taipyApp);
-    });
-    // only now does the socket tries to open/connect
-    if (!socket.connected) {
-        socket.connect();
-    }
-};
-
-const processWsMessage = (message: WsMessage, taipyApp: TaipyApp) => {
-    if (message.type) {
-        if (message.type === "MU" && Array.isArray(message.payload)) {
-            for (const muPayload of message.payload as [MultipleUpdatePayload]) {
-                const encodedName = muPayload.name;
-                const { value } = muPayload.payload;
-                taipyApp.variableData?.update(encodedName, value);
-                taipyApp.onChange && taipyApp.onChange(taipyApp, encodedName, value);
-            }
-        } else if (message.type === "ID") {
-            const { id } = message as unknown as IdMessage;
-            storeClientId(id);
-            taipyApp.clientId = id;
-            taipyApp.updateContext(taipyApp.path);
-        } else if (message.type === "GMC") {
-            const mc = (message.payload as Record<string, unknown>).data as string;
-            window.localStorage.setItem("ModuleContext", mc);
-            taipyApp.context = mc;
-        } else if (message.type === "GDT") {
-            const payload = message.payload as Record<string, ModuleData>;
-            const variableData = payload.variable;
-            const functionData = payload.function;
-            if (taipyApp.variableData && taipyApp.functionData) {
-                const varChanges = taipyApp.variableData.init(variableData);
-                const functionChanges = taipyApp.functionData.init(functionData);
-                const changes = merge(varChanges, functionChanges);
-                if (varChanges || functionChanges) {
-                    taipyApp.onReload && taipyApp.onReload(taipyApp, changes);
-                }
-            } else {
-                taipyApp.variableData = new DataManager(variableData);
-                taipyApp.functionData = new DataManager(functionData);
-                taipyApp.onInit && taipyApp.onInit(taipyApp);
-            }
-        } else if (message.type === "AID") {
-            const payload = message.payload as Record<string, unknown>;
-            if (payload.name === "reconnect") {
-                return taipyApp.init();
-            }
-            taipyApp.appId = payload.id as string;
-        } else if (message.type === "GR") {
-            const payload = message.payload as [string, string][];
-            taipyApp.routes = payload;
-        } else if (message.type === "AL" && taipyApp.onNotify) {
-            const payload = message as AlertMessage;
-            taipyApp.onNotify(taipyApp, payload.atype, payload.message);
-        }
-        postWsMessageProcessing(message, taipyApp);
-    }
-};
-
-const postWsMessageProcessing = (message: WsMessage, taipyApp: TaipyApp) => {
-    // perform data population only when all necessary metadata is ready
-    if (
-        initWsMessageTypes.includes(message.type) &&
-        taipyApp.clientId !== "" &&
-        taipyApp.appId !== "" &&
-        taipyApp.context !== "" &&
-        taipyApp.routes !== undefined
-    ) {
-        sendWsMessage(taipyApp.socket, "GDT", "get_data_tree", {}, taipyApp.clientId, taipyApp.context);
-    }
-};

+ 96 - 0
frontend/taipy-gui/base/src/wsAdapter.ts

@@ -0,0 +1,96 @@
+import merge from "lodash/merge";
+import { TaipyApp } from "./app";
+import { IdMessage, storeClientId } from "../../src/context/utils";
+import { WsMessage, sendWsMessage } from "../../src/context/wsUtils";
+import { DataManager, ModuleData } from "./dataManager";
+
+export abstract class WsAdapter {
+    abstract supportedMessageTypes: string[];
+
+    abstract handleWsMessage(message: WsMessage, app: TaipyApp): boolean;
+}
+
+interface MultipleUpdatePayload {
+    name: string;
+    payload: { value: unknown };
+}
+
+interface AlertMessage extends WsMessage {
+    atype: string;
+    message: string;
+}
+
+export class TaipyWsAdapter extends WsAdapter {
+    supportedMessageTypes: string[];
+    initWsMessageTypes: string[];
+    constructor() {
+        super();
+        this.supportedMessageTypes = ["MU", "ID", "GMC", "GDT", "AID", "GR", "AL"];
+        this.initWsMessageTypes = ["ID", "AID", "GMC"];
+    }
+    handleWsMessage(message: WsMessage, taipyApp: TaipyApp): boolean {
+        if (message.type) {
+            if (message.type === "MU" && Array.isArray(message.payload)) {
+                for (const muPayload of message.payload as [MultipleUpdatePayload]) {
+                    const encodedName = muPayload.name;
+                    const { value } = muPayload.payload;
+                    taipyApp.variableData?.update(encodedName, value);
+                    taipyApp.onChange && taipyApp.onChange(taipyApp, encodedName, value);
+                }
+            } else if (message.type === "ID") {
+                const { id } = message as unknown as IdMessage;
+                storeClientId(id);
+                taipyApp.clientId = id;
+                taipyApp.updateContext(taipyApp.path);
+            } else if (message.type === "GMC") {
+                const mc = (message.payload as Record<string, unknown>).data as string;
+                window.localStorage.setItem("ModuleContext", mc);
+                taipyApp.context = mc;
+            } else if (message.type === "GDT") {
+                const payload = message.payload as Record<string, ModuleData>;
+                const variableData = payload.variable;
+                const functionData = payload.function;
+                if (taipyApp.variableData && taipyApp.functionData) {
+                    const varChanges = taipyApp.variableData.init(variableData);
+                    const functionChanges = taipyApp.functionData.init(functionData);
+                    const changes = merge(varChanges, functionChanges);
+                    if (varChanges || functionChanges) {
+                        taipyApp.onReload && taipyApp.onReload(taipyApp, changes);
+                    }
+                } else {
+                    taipyApp.variableData = new DataManager(variableData);
+                    taipyApp.functionData = new DataManager(functionData);
+                    taipyApp.onInit && taipyApp.onInit(taipyApp);
+                }
+            } else if (message.type === "AID") {
+                const payload = message.payload as Record<string, unknown>;
+                if (payload.name === "reconnect") {
+                    taipyApp.init();
+                    return true;
+                }
+                taipyApp.appId = payload.id as string;
+            } else if (message.type === "GR") {
+                const payload = message.payload as [string, string][];
+                taipyApp.routes = payload;
+            } else if (message.type === "AL" && taipyApp.onNotify) {
+                const payload = message as AlertMessage;
+                taipyApp.onNotify(taipyApp, payload.atype, payload.message);
+            }
+            this.postWsMessageProcessing(message, taipyApp);
+            return true;
+        }
+        return false;
+    }
+    postWsMessageProcessing(message: WsMessage, taipyApp: TaipyApp) {
+        // perform data population only when all necessary metadata is ready
+        if (
+            this.initWsMessageTypes.includes(message.type) &&
+            taipyApp.clientId !== "" &&
+            taipyApp.appId !== "" &&
+            taipyApp.context !== "" &&
+            taipyApp.routes !== undefined
+        ) {
+            sendWsMessage(taipyApp.socket, "GDT", "get_data_tree", {}, taipyApp.clientId, taipyApp.context);
+        }
+    }
+}

+ 76 - 43
frontend/taipy-gui/base/webpack.config.js

@@ -1,56 +1,89 @@
 const path = require("path");
 const webpack = require("webpack");
-const resolveApp = relativePath => path.resolve(__dirname, relativePath);
+const CopyWebpackPlugin = require("copy-webpack-plugin");
+const resolveApp = (relativePath) => path.resolve(__dirname, relativePath);
 
 const moduleName = "TaipyGuiBase";
 const basePath = "../../../taipy/gui/webapp";
 const webAppPath = resolveApp(basePath);
+const taipyGuiBaseExportPath = resolveApp(basePath + "/taipy-gui-base-export");
 
-module.exports = {
-    target: "web",
-    entry: {
-        "default": "./base/src/index.ts",
-        "preview": "./base/src/index-preview.ts",
-    },
-    output: {
-        filename: (arg) => {
-            if (arg.chunk.name === "default") {
-                return "taipy-gui-base.js";
-            }
-            return "[name].taipy-gui-base.js";
-        },
-        chunkFilename: "[name].taipy-gui-base.js",
-        path: webAppPath,
-        globalObject: "this",
-        library: {
-            name: moduleName,
-            type: "umd",
+module.exports = [
+    {
+        target: "web",
+        entry: {
+            default: "./base/src/index.ts",
+            preview: "./base/src/index-preview.ts",
         },
-    },
-    optimization: {
-        splitChunks: {
-            chunks: 'all',
-            name: "shared",
+        output: {
+            filename: (arg) => {
+                if (arg.chunk.name === "default") {
+                    return "taipy-gui-base.js";
+                }
+                return "[name].taipy-gui-base.js";
+            },
+            chunkFilename: "[name].taipy-gui-base.js",
+            path: webAppPath,
+            globalObject: "this",
+            library: {
+                name: moduleName,
+                type: "umd",
+            },
+        },
+        optimization: {
+            splitChunks: {
+                chunks: "all",
+                name: "shared",
+            },
         },
+        module: {
+            rules: [
+                {
+                    test: /\.tsx?$/,
+                    use: "ts-loader",
+                    exclude: /node_modules/,
+                },
+            ],
+        },
+        resolve: {
+            extensions: [".tsx", ".ts", ".js", ".tsx"],
+        },
+        // externals: {
+        //     "socket.io-client": {
+        //         commonjs: "socket.io-client",
+        //         commonjs2: "socket.io-client",
+        //         amd: "socket.io-client",
+        //         root: "_",
+        //     },
+        // },
     },
-    module: {
-        rules: [
-            {
-                test: /\.tsx?$/,
-                use: "ts-loader",
-                exclude: /node_modules/,
+    {
+        entry: "./base/src/exports.ts",
+        output: {
+            filename: "taipy-gui-base.js",
+            path: taipyGuiBaseExportPath,
+            library: {
+                name: moduleName,
+                type: "umd",
             },
+            publicPath: "",
+        },
+        module: {
+            rules: [
+                {
+                    test: /\.tsx?$/,
+                    use: "ts-loader",
+                    exclude: /node_modules/,
+                },
+            ],
+        },
+        resolve: {
+            extensions: [".tsx", ".ts", ".js", ".tsx"],
+        },
+        plugins: [
+            new CopyWebpackPlugin({
+                patterns: [{ from: "./base/src/packaging", to: taipyGuiBaseExportPath }],
+            }),
         ],
     },
-    resolve: {
-        extensions: [".tsx", ".ts", ".js", ".tsx"],
-    },
-    // externals: {
-    //     "socket.io-client": {
-    //         commonjs: "socket.io-client",
-    //         commonjs2: "socket.io-client",
-    //         amd: "socket.io-client",
-    //         root: "_",
-    //     },
-    // },
-};
+];

+ 5 - 5
frontend/taipy-gui/package-lock.json

@@ -61,7 +61,7 @@
         "css-loader": "^7.1.0",
         "css-mediaquery": "^0.1.2",
         "dotenv-webpack": "^8.0.0",
-        "dts-bundle-generator": "^9.2.1",
+        "dts-bundle-generator": "^7.2.0",
         "eslint": "^8.57.0",
         "eslint-plugin-react": "^7.26.1",
         "eslint-plugin-react-hooks": "^4.2.0",
@@ -5918,12 +5918,12 @@
       }
     },
     "node_modules/dts-bundle-generator": {
-      "version": "9.5.1",
-      "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz",
-      "integrity": "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA==",
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-7.2.0.tgz",
+      "integrity": "sha512-pHjRo52hvvLDRijzIYRTS9eJR7vAOs3gd/7jx+7YVnLU8ay3yPUWGtHXPtuMBSlJYk/s4nq1SvXObDCZVguYMg==",
       "dev": true,
       "dependencies": {
-        "typescript": ">=5.0.2",
+        "typescript": ">=4.5.2",
         "yargs": "^17.6.0"
       },
       "bin": {

+ 2 - 1
frontend/taipy-gui/package.json

@@ -46,6 +46,7 @@
     "lint:fix": "npm run lint -- --fix",
     "coverage": "npm test -- --coverage",
     "types": "dts-bundle-generator -o packaging/taipy-gui.gen.d.ts src/extensions/exports.ts",
+    "types-base": "dts-bundle-generator -o base/src/packaging/taipy-gui-base.gen.d.ts base/src/exports.ts",
     "doc": "typedoc --plugin typedoc-plugin-markdown --excludeNotDocumented --disableSources src/extensions/exports.ts",
     "doc.json": "typedoc --json docs/taipy-gui.json src/extensions/exports.ts",
     "mkdocs": "typedoc --options typedoc-mkdocs.json"
@@ -97,7 +98,7 @@
     "css-loader": "^7.1.0",
     "css-mediaquery": "^0.1.2",
     "dotenv-webpack": "^8.0.0",
-    "dts-bundle-generator": "^9.2.1",
+    "dts-bundle-generator": "^7.2.0",
     "eslint": "^8.57.0",
     "eslint-plugin-react": "^7.26.1",
     "eslint-plugin-react-hooks": "^4.2.0",

+ 32 - 0
frontend/taipy-gui/webpack.config.js

@@ -34,6 +34,7 @@ const webAppPath = resolveApp(basePath);
 const reactManifestPath = resolveApp(basePath + "/" + reactBundle + "-manifest.json");
 const reactDllPath = resolveApp(basePath + "/" + reactBundle + ".dll.js")
 const taipyDllPath = resolveApp(basePath + "/" + taipyBundle + ".js")
+const taipyGuiBaseExportPath = resolveApp(basePath + "/taipy-gui-base-export");
 
 module.exports = (env, options) => {
     const envVariables = {
@@ -217,5 +218,36 @@ module.exports = (env, options) => {
         //         root: "_",
         //     },
         // },
+    },
+    {
+        entry: "./base/src/exports.ts",
+        output: {
+            filename: "taipy-gui-base.js",
+            path: taipyGuiBaseExportPath,
+            library: {
+                name: taipyGuiBaseBundleName,
+                type: "umd",
+            },
+            publicPath: "",
+        },
+        module: {
+            rules: [
+                {
+                    test: /\.tsx?$/,
+                    use: "ts-loader",
+                    exclude: /node_modules/,
+                },
+            ],
+        },
+        resolve: {
+            extensions: [".tsx", ".ts", ".js", ".tsx"],
+        },
+        plugins: [
+            new CopyWebpackPlugin({
+                patterns: [
+                    { from: "./base/src/packaging", to: taipyGuiBaseExportPath },
+                ],
+            }),
+        ],
     }];
 };

+ 3 - 0
taipy/__init__.py

@@ -30,5 +30,8 @@ if find_spec("taipy"):
     if find_spec("taipy.enterprise"):
         from taipy.enterprise._init import *
 
+    if find_spec("taipy.designer"):
+        from taipy.designer._init import *
+
     if find_spec("taipy._run"):
         from taipy._run import _run as run

+ 13 - 0
taipy/gui/gui.py

@@ -598,6 +598,12 @@ class Gui:
             setattr(g, "update_count", update_count)  # noqa: B010
             return None
 
+    def _handle_connect(self):
+        pass
+
+    def _handle_disconnect(self):
+        pass
+
     def _manage_message(self, msg_type: _WsType, message: dict) -> None:
         try:
             client_id = None
@@ -632,6 +638,8 @@ class Gui:
                         self.__handle_ws_app_id(message)
                     elif msg_type == _WsType.GET_ROUTES.value:
                         self.__handle_ws_get_routes()
+                    else:
+                        self._manage_external_message(msg_type, message)
                 self.__send_ack(message.get("ack_id"))
         except Exception as e:  # pragma: no cover
             if isinstance(e, AttributeError) and (name := message.get("name")):
@@ -652,6 +660,11 @@ class Gui:
             else:
                 _warn(f"Decoding Message has failed: {message}", e)
 
+    # To be expanded by inheriting classes
+    # this will be used to handle ws messages that is not handled by the base Gui class
+    def _manage_external_message(self, msg_type: _WsType, message: dict) -> None:
+        pass
+
     def __front_end_update(
         self,
         var_name: str,

+ 8 - 0
taipy/gui/server.py

@@ -111,6 +111,14 @@ class _Server:
             elif "type" in message:
                 gui._manage_message(message["type"], message)
 
+        @self._ws.on("connect")
+        def handle_connect():
+            gui._handle_connect()
+
+        @self._ws.on("disconnect")
+        def handle_disconnect():
+            gui._handle_disconnect()
+
     def __is_ignored(self, file_path: str) -> bool:
         if not hasattr(self, "_ignore_matches"):
             __IGNORE_FILE = ".taipyignore"