One app, web and native
Share one capability list, keep native code out of the web bundle, and let the code stay the same.
On this page
How it fits together
Your project has up to three apps:
- The system app runs in the page. It composes capability plugins from this package.
- The web app builds the page, if you use
@moku-labs/web. - The native app runs in Node and packages the page with
@moku-labs/native.
The system app and the native app must agree on the capabilities. Keep that agreement in one file.
Example
export const systemPlugins = [
{ name: "store" },
{ name: "notification" },
{ name: "deep-link" }
];- Names from
config.systemof native, not the plugin names of this package.
import { createApp } from "@moku-labs/native";
import { systemPlugins } from "./system";
export const native = createApp({
config: {
app: { name: "MyApp", identifier: "com.example.myapp" },
web: { build: "bun run build", devCommand: "bun run dev", devUrl: "http://localhost:5173", dist: "dist" },
system: systemPlugins,
capabilities: { "deep-link": { mode: "scheme", scheme: "myapp" } }
}
});import { createApp } from "@moku-labs/system";
import { deepLinkPlugin } from "@moku-labs/system/deep-link";
import { notifyPlugin } from "@moku-labs/system/notify";
import { storePlugin } from "@moku-labs/system/store";
export const system = createApp({
plugins: [storePlugin, notifyPlugin, deepLinkPlugin],
pluginConfigs: {
store: { name: "my-app" },
deepLink: { schemes: ["myapp"] }
}
});Install the peers
The packager checks the @tauri-apps/* versions your package.json declares. For the list above:
bun add @tauri-apps/api @tauri-apps/plugin-store @tauri-apps/plugin-notification @tauri-apps/plugin-deep-linkBundle for the web
Each capability loads its Tauri provider through a lazy import. A web build never runs those chunks, but your bundler still has to resolve the specifiers. Install the optional peers, or mark them external:
await Bun.build({ entrypoints: ["src/main.ts"], outdir: "dist", external: ["@tauri-apps/*"] });export default { build: { rollupOptions: { external: /^@tauri-apps\// } } };Handle each answer once
Read the reason where the feature lives. The same branch serves both runtimes.
import type { SystemResult } from "@moku-labs/system";
function copyButtonState(result: SystemResult<void>): "done" | "hidden" | "retry" | "explain" {
if (result.ok) return "done";
if (result.reason === "unsupported") return "hidden";
if (result.reason === "denied") return "explain";
return "retry";
}
console.log(copyButtonState({ ok: false, provider: "web", reason: "unsupported" })); // "hidden"- Two names for one capability.
notifyhere isnotificationin native,clipboardisclipboard-manager,deepLinkisdeep-link. lifecycleandkeepAwakeare not listed in native. They need no native plugin.- An island must start the app. A capability call before
start()answersunavailable.
Related
- native quick start: package the page.
- How it works: the full name and permission table.