Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 130dd981 authored by Simon Chan's avatar Simon Chan
Browse files

refactor(tabby): reduce bundle size by 10M

parent db25dd91
Loading
Loading
Loading
Loading
+20 −6
Original line number Diff line number Diff line
@@ -7,15 +7,26 @@ const withMDX = require("@next/mdx")({
    },
});

const withBundleAnalyzer = require("@next/bundle-analyzer")({
    enabled: process.env.ANALYZE === "true",
});

const basePath = process.env.BASE_PATH ?? "";

const withPwa = require("@yume-chan/next-pwa")({
    dest: "public",
});

function pipe(value, ...callbacks) {
    for (const callback of callbacks) {
        value = callback(value);
    }
    return value;
}

/** @type {import('next').NextConfig} */
module.exports = withPwa(
    withMDX({
module.exports = pipe(
    {
        basePath,
        pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
        reactStrictMode: false,
@@ -27,7 +38,8 @@ module.exports = withPwa(
        publicRuntimeConfig: {
            basePath,
        },
        webpack(config, options) {
        webpack(config) {
            // Bundle Scrcpy Server
            config.module.rules.push({
                resourceQuery: /url/,
                type: "asset/resource",
@@ -49,10 +61,9 @@ module.exports = withPwa(
                enforce: "pre",
            });

            // config.experiments.topLevelAwait = true;

            return config;
        },
        // Enable Direct Sockets API
        async headers() {
            return [
                {
@@ -70,5 +81,8 @@ module.exports = withPwa(
                },
            ];
        },
    })
    },
    withBundleAnalyzer,
    withPwa,
    withMDX
);
+14 −22
Original line number Diff line number Diff line
@@ -10,8 +10,7 @@
        "lint": "next lint"
    },
    "dependencies": {
        "@angular/compiler": "^15.2.6",
        "@fluentui/react": "^8.107.5",
        "@fluentui/react": "^8.108.1",
        "@fluentui/react-file-type-icons": "^8.8.13",
        "@fluentui/react-hooks": "^8.6.20",
        "@fluentui/react-icons": "^2.0.200",
@@ -19,15 +18,15 @@
        "@griffel/react": "^1.5.7",
        "@yume-chan/adb": "workspace:^0.0.19",
        "@yume-chan/adb-backend-direct-sockets": "workspace:^0.0.9",
        "@yume-chan/adb-backend-proxy": "workspace:^0.0.9",
        "@yume-chan/adb-backend-proxy": "workspace:^0.0.19",
        "@yume-chan/adb-backend-webusb": "workspace:^0.0.19",
        "@yume-chan/adb-backend-ws": "workspace:^0.0.9",
        "@yume-chan/adb-credential-web": "workspace:^0.0.19",
        "@yume-chan/adb-scrcpy": "workspace:^0.0.19",
        "@yume-chan/android-bin": "workspace:^0.0.19",
        "@yume-chan/aoa": "workspace:^0.0.18",
        "@yume-chan/aoa": "workspace:^0.0.19",
        "@yume-chan/async": "^2.2.0",
        "@yume-chan/b-tree": "workspace:^0.0.16",
        "@yume-chan/b-tree": "workspace:^0.0.19",
        "@yume-chan/event": "workspace:^0.0.19",
        "@yume-chan/pcm-player": "workspace:^0.0.19",
        "@yume-chan/scrcpy": "workspace:^0.0.19",
@@ -36,35 +35,28 @@
        "@yume-chan/stream-extra": "workspace:^0.0.19",
        "@yume-chan/stream-saver": "^2.0.6",
        "@yume-chan/struct": "workspace:^0.0.19",
        "@yume-chan/tabby-tango": "workspace:^0.0.19",
        "@yume-chan/undici-browser": "5.21.2-mod.9",
        "@yume-chan/tabby-launcher": "workspace:^1.0.197-nightly.1",
        "fflate": "^0.7.4",
        "yaml": "^2.2.1",
        "mobx": "^6.7.0",
        "mobx-react-lite": "^3.4.3",
        "next": "13.3.0",
        "tabby-core": "^1.0.197-nightly.0",
        "tabby-settings": "^1.0.197-nightly.0",
        "tabby-terminal": "^1.0.197-nightly.0",
        "tabby-community-color-schemes": "^1.0.197-nightly.0",
        "tabby-web": "^1.0.197-nightly.0",
        "tabby-web-container": "^1.0.197-nightly.0",
        "next": "13.3.1",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "rxjs": "^7.8.0",
        "webm-muxer": "^2.2.3"
        "webm-muxer": "^3.0.3"
    },
    "devDependencies": {
        "@mdx-js/loader": "^2.2.1",
        "@mdx-js/react": "^2.2.1",
        "@next/mdx": "^13.2.4",
        "@next/bundle-analyzer": "^13.3.1",
        "@next/mdx": "^13.3.1",
        "@types/dom-webcodecs": "^0.1.6",
        "@types/node": "^18.15.3",
        "@types/react": "18.0.35",
        "@types/node": "^18.16.0",
        "@types/react": "18.0.38",
        "@yume-chan/next-pwa": "5.6.0-mod.2",
        "eslint": "^8.36.0",
        "eslint-config-next": "13.3.0",
        "prettier": "^2.8.4",
        "eslint": "^8.39.0",
        "eslint-config-next": "13.3.1",
        "prettier": "^2.8.8",
        "source-map-loader": "^4.0.1",
        "typescript": "^5.0.3"
    }
+0 −24
Original line number Diff line number Diff line
@@ -27,27 +27,3 @@ fs.writeFileSync(
    ),
    "utf8"
);

fs.writeFileSync(
    new URL(
        "../node_modules/tabby-web-container/dist/preload.mjs",
        import.meta.url
    ),
    "export {};\n" +
        fs
            .readFileSync(
                new URL(
                    "../node_modules/tabby-web-container/dist/preload.js",
                    import.meta.url
                ),
                "utf8"
            )
            .replaceAll(/__webpack_require__\.p \+ "(.+)"/g, (_, match) => {
                return `new URL("./${match}", import.meta.url).toString()`;
            })
            .replaceAll(/__webpack_require__/g, "__webpack_require_nested__")
            .replace(
                "var scriptUrl;",
                "var scriptUrl = import.meta.url.toString();"
            )
);
+6 −5
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ import {
    h265SearchConfiguration,
} from "@yume-chan/scrcpy";
import { action, makeAutoObservable, reaction } from "mobx";
import WebMMuxer from "webm-muxer";
import { ArrayBufferTarget, Muxer as WebMMuxer } from "webm-muxer";
import { saveFile } from "../../utils";

// https://ffmpeg.org/doxygen/0.11/avc_8c-source.html#l00106
@@ -234,7 +234,7 @@ export class MatroskaMuxingRecorder {
    public videoMetadata: ScrcpyVideoStreamMetadata | undefined;
    public audioCodec: ScrcpyAudioCodec | undefined;

    private muxer: WebMMuxer | undefined;
    private muxer: WebMMuxer<ArrayBufferTarget> | undefined;
    private videoCodecDescription: Uint8Array | undefined;
    private configurationWritten = false;
    private _firstTimestamp = -1;
@@ -351,7 +351,7 @@ export class MatroskaMuxingRecorder {
        this.running = true;

        const options: ConstructorParameters<typeof WebMMuxer>[0] = {
            target: "buffer",
            target: new ArrayBufferTarget(),
            type: "matroska",
            firstTimestampBehavior: "permissive",
            video: {
@@ -371,7 +371,7 @@ export class MatroskaMuxingRecorder {
            };
        }

        this.muxer = new WebMMuxer(options);
        this.muxer = new WebMMuxer(options as any);

        if (this._packetsFromLastKeyframe.length > 0) {
            for (const { type, packet } of this._packetsFromLastKeyframe) {
@@ -389,7 +389,8 @@ export class MatroskaMuxingRecorder {
            return;
        }

        const buffer = this.muxer.finalize()!;
        this.muxer.finalize()!;
        const buffer = this.muxer.target.buffer;
        const now = new Date();
        const stream = saveFile(
            // prettier-ignore
+4 −214
Original line number Diff line number Diff line
import { Adb } from "@yume-chan/adb";
import {
    AdbProxyBackend,
    AdbProxyServerInfo,
} from "@yume-chan/adb-backend-proxy";
import { useEffect } from "react";
import { Subject } from "rxjs";
import Yaml from "yaml";

export class NotImplementedSocket {
    connect$ = new Subject<void>();
    data$ = new Subject<Buffer>();
    error$ = new Subject<Error>();
    close$ = new Subject<Buffer>();

    async connect() {
        this.error$.next(new Error("Socket is not implemented in Web"));
    }
}

// Usage of connector is scattered around the Tabby codebase,
// but these is all methods that are required.
class WebConnector {
    constructor() {}

    async loadConfig(): Promise<string> {
        let config;

        const text = localStorage.getItem("tabby-config");
        if (text) {
            config = Yaml.parse(text);
        } else {
            config = {
                recoverTabs: false,
                web: {
                    preventAccidentalTabClosure: false,
                },
                terminal: {
                    fontSize: 11,
                },
            };
        }

        config.providerBlacklist = [
            ...(config.providerBlacklist ?? []),
            "settings:ProfilesSettingsTabProvider",
        ];
        config.commandBlacklist = [
            ...(config.commandBlacklist ?? []),
            "core:profile-selector",
        ];

        return Yaml.stringify(config);
    }

    async saveConfig(content: string): Promise<void> {
        localStorage.setItem("tabby-config", content);
    }

    getAppVersion(): string {
        return "1.0.197-nightly.0";
    }

    createSocket() {
        return new NotImplementedSocket();
    }
}

interface TabbyWeb {
    registerPluginModule(packageName: string, exports: unknown): void;
    bootstrap(options: unknown): Promise<void>;
}

interface TabbyPluginModule {
    pluginName: string;
}

interface GlobalExtension {
    __connector__: WebConnector | undefined;
    module: any;
    exports: any;
    Tabby: TabbyWeb;
    pluginModules: TabbyPluginModule[];
}

const globalExtension = globalThis as unknown as GlobalExtension;

async function start() {
    const connector = new WebConnector();
    globalExtension["__connector__"] = connector;

    await import("@angular/compiler");
    // @ts-expect-error
    await import("tabby-web-container/dist/preload.mjs");
    // @ts-expect-error
    await import("tabby-web-container/dist/bundle.js");

    async function webRequire(url: string | URL) {
        if (typeof url === "object") {
            url = url.toString();
        }

        console.log(`Loading ${url}`);
        const e = document.createElement("script");
        globalExtension["module"] = { exports: {} };
        globalExtension["exports"] = globalExtension["module"].exports;
        await new Promise((resolve) => {
            e.onload = resolve;
            e.src = url as string;
            document.head.appendChild(e);
        });
        return globalExtension["module"].exports;
    }

    async function prefetchURL(url: string | URL) {
        await (await fetch(url)).text();
    }

    const tabby = globalExtension.Tabby;

    const pluginInfos: {
        pluginName: string;
        packageName: string;
        url: URL;
    }[] = [
        {
            pluginName: "core",
            packageName: "tabby-core",
            url: new URL("tabby-core/dist/index.js", import.meta.url),
        },
        {
            pluginName: "settings",
            packageName: "tabby-settings",
            url: new URL("tabby-settings/dist/index.js", import.meta.url),
        },
        {
            pluginName: "terminal",
            packageName: "tabby-terminal",
            url: new URL("tabby-terminal/dist/index.js", import.meta.url),
        },
        {
            pluginName: "community-color-schemes",
            packageName: "tabby-community-color-schemes",
            url: new URL(
                "tabby-community-color-schemes/dist/index.js",
                import.meta.url
            ),
        },
        {
            pluginName: "web",
            packageName: "tabby-web",
            url: new URL("tabby-web/dist/index.js", import.meta.url),
        },
    ];

    await Promise.all(
        pluginInfos.map((pluginInfo) => prefetchURL(pluginInfo.url))
    );

    const pluginModules = [];
    for (const info of pluginInfos) {
        const result = await webRequire(info.url);
        result.pluginName = info.pluginName;
        tabby.registerPluginModule(info.packageName, result);
        pluginModules.push(result);
    }

    const TabbyTango = await webRequire(
        new URL("@yume-chan/tabby-tango/dist/index.js", import.meta.url)
    );
    TabbyTango.pluginName = "tango";

    window.addEventListener("message", (e) => {
        if ("type" in e.data && e.data.type === "adb") {
            const { port, version, maxPayloadSize, banner } =
                e.data as AdbProxyServerInfo;
            const backend = new AdbProxyBackend(port);
            const connection = backend.connect();
            TabbyTango.AdbState.value = new Adb(
                connection,
                version,
                maxPayloadSize,
                banner
            );
        }
    });
    window.parent.postMessage("adb", "*");

    pluginModules.push(TabbyTango);

    globalExtension["pluginModules"] = pluginModules.map((plugin) => {
        if ("default" in plugin) {
            plugin.default.pluginName = plugin.pluginName;
            return plugin.default;
        }
        return plugin;
    });

    const config = connector.loadConfig();
    await tabby.bootstrap({
        packageModules: pluginModules,
        bootstrapData: {
            config,
            executable: "web",
            isFirstWindow: true,
            windowID: 1,
            installedPlugins: [],
            userPluginsPath: "/",
        },
        debugMode: false,
        connector,
    });
}

function TabbyFrame() {
    useEffect(() => {
        // Only run at client side.
        start().catch((e) => {
        try {
            require("@yume-chan/tabby-launcher");
        } catch (e) {
            console.error(e);
        });
        }
    }, []);

    return (
Loading