feat: basic sidecar functionality for rendering

This commit is contained in:
ByteAtATime 2025-06-11 09:35:29 -07:00
parent dac77c76b7
commit 91c1b4e811
14 changed files with 3305 additions and 94 deletions

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
sidecar/plugin-host.js

View file

@ -15,7 +15,9 @@
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-clipboard-manager": "~2.2.2", "@tauri-apps/plugin-clipboard-manager": "~2.2.2",
"@tauri-apps/plugin-opener": "^2" "@tauri-apps/plugin-opener": "^2",
"@tauri-apps/plugin-shell": "~2.2.1",
"msgpackr": "^1.11.4"
}, },
"devDependencies": { "devDependencies": {
"@internationalized/date": "^3.8.2", "@internationalized/date": "^3.8.2",
@ -26,6 +28,7 @@
"@sveltejs/vite-plugin-svelte": "^5.0.0", "@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2",
"@types/node": "^24.0.0",
"bits-ui": "^2.5.0", "bits-ui": "^2.5.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"svelte": "^5.0.0", "svelte": "^5.0.0",
@ -35,7 +38,8 @@
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"tw-animate-css": "^1.3.4", "tw-animate-css": "^1.3.4",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"vite": "^6.0.3" "vite": "^6.0.3",
"vite-plugin-node-polyfills": "^0.23.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [

1367
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

2
sidecar/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
plugin-host.js
dist/

27
sidecar/package.json Normal file
View file

@ -0,0 +1,27 @@
{
"name": "sidecar",
"version": "1.0.0",
"description": "",
"main": "plugin-host.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "esbuild plugin-host.ts --bundle --outfile=plugin-host.js --platform=node && pkg plugin-host.js --output app --public && pnpm rename",
"rename": "mv \"app$([[ \"$OSTYPE\" =~ msys|win32 ]] && echo .exe)\" \"../src-tauri/binaries/app-$(rustc -vV | awk '/host:/ {print $2}')$([[ \"$OSTYPE\" =~ msys|win32 ]] && echo .exe)\""
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.11.1",
"devDependencies": {
"@types/node": "^24.0.0",
"@types/react": "^19.1.7",
"@types/react-reconciler": "^0.32.0",
"@yao-pkg/pkg": "^6.5.1",
"esbuild": "^0.25.5"
},
"dependencies": {
"msgpackr": "^1.11.4",
"react": "^19.1.0",
"react-reconciler": "^0.32.0"
}
}

413
sidecar/plugin-host.ts Normal file
View file

@ -0,0 +1,413 @@
import { createInterface } from "readline";
import React from "react";
import Reconciler from "react-reconciler";
import { jsx } from "react/jsx-runtime";
import plugin from "./dist/emoji.txt";
import { pack, PackrStream } from "msgpackr";
let commitBuffer: { type: string; payload: any }[] = [];
const sendingStream = new PackrStream();
sendingStream.pipe(process.stdout);
function writeOutput(data: object) {
function removeSymbols(obj: object) {
if (typeof obj !== "object" || obj === null) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(removeSymbols);
}
const newObj = {};
for (const key in obj) {
newObj[key] = removeSymbols(obj[key]);
}
for (const symKey of Object.getOwnPropertySymbols(obj)) {
// do nothing if the key is a symbol
}
for (const key in newObj) {
if (typeof newObj[key] === "symbol") {
newObj[key] = undefined;
}
}
return newObj;
}
try {
const payload = pack(removeSymbols(data));
const header = Buffer.alloc(4);
header.writeUInt32BE(payload.length);
process.stdout.write(header);
process.stdout.write(payload);
} catch (e) {
writeOutput({ type: "log", payload: e.toString() });
}
}
function writeLog(message) {
writeOutput({ type: "log", payload: message });
}
process.on("unhandledRejection", (reason, promise) => {
writeLog(`--- UNHANDLED PROMISE REJECTION ---`);
writeLog(reason.stack || reason);
});
let instanceCounter = 0;
const instances: Map<number, any> = new Map();
function serializeProps(props) {
const serializable = {};
for (const key in props) {
if (key === "children" || typeof props[key] === "function") {
continue;
}
try {
JSON.stringify(props[key]);
serializable[key] = props[key];
} catch (error) {
serializable[key] = `[Circular Reference in prop '${key}']`;
}
}
return serializable;
}
const hostConfig = {
getPublicInstance(instance) {
return instance;
},
getRootHostContext(rootContainerInstance) {
return {};
},
getChildHostContext(parentHostContext, type, rootContainerInstance) {
return {};
},
createInstance(
type,
props,
rootContainer,
hostContext,
internalInstanceHandle
) {
const componentType =
typeof type === "string" ? type : type.name || "Anonymous";
const id = ++instanceCounter;
const stateNode = {
id,
type: componentType,
children: [],
props: serializeProps(props),
_internalFiber: internalInstanceHandle,
};
internalInstanceHandle.stateNode = stateNode;
instances.set(id, stateNode);
commitBuffer.push({
type: "CREATE_INSTANCE",
payload: { id, type: componentType, props: stateNode.props },
});
return stateNode;
},
createTextInstance(text) {
const id = ++instanceCounter;
const instance = { id, type: "TEXT", text };
instances.set(id, instance);
commitBuffer.push({ type: "CREATE_TEXT_INSTANCE", payload: instance });
return instance;
},
appendInitialChild(parentInstance, child) {
parentInstance.children.push(child);
commitBuffer.push({
type: "APPEND_CHILD",
payload: { parentId: parentInstance.id, childId: child.id },
});
},
appendChild(parentInstance, child) {
parentInstance.children.push(child);
commitBuffer.push({
type: "APPEND_CHILD",
payload: { parentId: parentInstance.id, childId: child.id },
});
},
appendChildToContainer(container, child) {
container.children.push(child);
commitBuffer.push({
type: "APPEND_CHILD",
payload: { parentId: container.id, childId: child.id },
});
},
insertBefore(parentInstance, child, beforeChild) {
const index = parentInstance.children.findIndex(
(c) => c.id === beforeChild.id
);
if (index !== -1) {
parentInstance.children.splice(index, 0, child);
commitBuffer.push({
type: "INSERT_BEFORE",
payload: {
parentId: parentInstance.id,
childId: child.id,
beforeId: beforeChild.id,
},
});
} else {
this.appendChild(parentInstance, child);
}
},
insertInContainerBefore(container, child, beforeChild) {
this.insertBefore(container, child, beforeChild);
},
removeChild(parentInstance, child) {
parentInstance.children = parentInstance.children.filter(
(c) => c.id !== child.id
);
commitBuffer.push({
type: "REMOVE_CHILD",
payload: { parentId: parentInstance.id, childId: child.id },
});
},
removeChildFromContainer(container, child) {
container.children = container.children.filter((c) => c.id !== child.id);
commitBuffer.push({
type: "REMOVE_CHILD",
payload: { parentId: container.id, childId: child.id },
});
},
commitUpdate(stateNode, updatePayload, type, oldProps, newProps) {
stateNode.props = serializeProps(newProps);
commitBuffer.push({
type: "UPDATE_PROPS",
payload: { id: stateNode.id, props: serializeProps(stateNode.props) },
});
},
prepareForCommit: () => null,
resetAfterCommit: (container) => {
if (commitBuffer.length > 0) {
writeOutput({
type: "BATCH_UPDATE",
payload: commitBuffer,
});
commitBuffer = [];
}
},
finalizeInitialChildren: () => false,
prepareUpdate: () => true,
shouldSetTextContent: () => false,
clearContainer: (container) => {
container.children = [];
commitBuffer.push({
type: "CLEAR_CONTAINER",
payload: { containerId: container.id },
});
},
detachDeletedInstance: () => {},
now: Date.now,
scheduleTimeout: setTimeout,
cancelTimeout: clearTimeout,
noTimeout: -1,
getCurrentUpdatePriority: () => 1,
setCurrentUpdatePriority: () => {},
resolveUpdatePriority: () => 1,
maySuspendCommit: () => false,
supportsMutation: true,
isPrimaryRenderer: true,
supportsPersistence: false,
supportsHydration: false,
};
const reconciler = Reconciler(hostConfig);
const createPluginRequire = () => (moduleName) => {
if (moduleName === "react") {
return React;
}
if (moduleName.startsWith("@raycast/api")) {
writeLog(`Plugin requested @raycast/api`);
const storage = new Map();
const LocalStorage = {
getItem: async (key) => storage.get(key),
setItem: async (key, value) => storage.set(key, value),
removeItem: async (key) => storage.delete(key),
clear: async () => storage.clear(),
};
const ListComponent = ({ children, ...rest }) =>
jsx("List", { ...rest, children });
const ListSectionComponent = ({ children, ...rest }) =>
jsx("ListSection", { ...rest, children });
const ListDropdownComponent = ({ children, ...rest }) =>
jsx("ListDropdown", { ...rest, children });
const ActionPanelComponent = ({ children, ...rest }) =>
jsx("ActionPanel", { ...rest, children });
const ActionPanelSectionComponent = ({ children, ...rest }) =>
jsx("ActionPanelSection", { ...rest, children });
ListComponent.Item = "ListItem";
ListComponent.Section = ListSectionComponent;
ListComponent.Dropdown = ListDropdownComponent;
ListDropdownComponent.Item = "ListDropdownItem";
ActionPanelComponent.Section = ActionPanelSectionComponent;
return {
LocalStorage,
environment: {
assetsPath: "/home/byte/code/raycast-linux/sidecar/dist/assets/",
},
getPreferenceValues: () => ({
primaryAction: "paste",
unicodeVersion: "14.0",
shortCodes: true,
}),
usePersistentState: (key, initialValue) => {
const [state, setState] = React.useState(initialValue);
const isLoading = false;
return [state, setState, isLoading];
},
List: ListComponent,
ActionPanel: ActionPanelComponent,
Action: {
Paste: "Action.Paste",
CopyToClipboard: "Action.CopyToClipboard",
OpenInBrowser: "Action.OpenInBrowser",
},
};
}
return require(moduleName);
};
const root = { id: "root", children: [] };
const onUncaughtError = (error, errorInfo) => {
writeLog(`--- REACT UNCAUGHT ERROR ---`);
writeLog(`Error: ${error.message}`);
if (errorInfo && errorInfo.componentStack) {
writeLog(
`Stack: ${errorInfo.componentStack.trim().replace(/\n/g, "\n ")}`
);
}
};
const onCaughtError = (error, errorInfo) => {
writeLog(`--- REACT CAUGHT ERROR ---`);
writeLog(`Error: ${error.message}`);
if (errorInfo && errorInfo.componentStack) {
writeLog(
`Stack: ${errorInfo.componentStack.trim().replace(/\n/g, "\n ")}`
);
}
};
const onRecoverableError = (error, errorInfo) => {
writeLog(`--- REACT RECOVERABLE ERROR ---`);
writeLog(`Error: ${error.message}`);
if (errorInfo && errorInfo.componentStack) {
writeLog(
`Stack: ${errorInfo.componentStack.trim().replace(/\n/g, "\n ")}`
);
}
};
const container = reconciler.createContainer(
root,
0,
null,
false,
null,
"",
onUncaughtError,
onCaughtError,
onRecoverableError
);
function runPlugin() {
const scriptText = plugin;
const pluginModule = { exports: {} } as {
exports: {
default: null;
};
};
const scriptFunction = new Function(
"require",
"module",
"exports",
"React",
scriptText
);
scriptFunction(
createPluginRequire(),
pluginModule,
pluginModule.exports,
React
);
const PluginRootComponent = pluginModule.exports.default;
if (PluginRootComponent) {
writeLog("Plugin loaded. Initializing React render...");
const AppElement = React.createElement(PluginRootComponent);
reconciler.updateContainer(AppElement, container, null, () => {
writeLog("Initial render complete.");
});
} else {
throw new Error("Plugin did not export a default component.");
}
}
const rl = createInterface({ input: process.stdin });
writeLog("Node.js Sidecar started successfully with React Reconciler.");
rl.on("line", (line) => {
try {
const command = JSON.parse(line);
if (command.action === "run-plugin") {
runPlugin();
} else if (command.action === "dispatch-event") {
const { instanceId, handlerName, args } = command.payload;
writeLog(
`Event received: instance ${instanceId}, handler ${handlerName}`
);
const stateNode = instances.get(instanceId);
if (!stateNode) {
writeLog(`Instance ${instanceId} not found.`);
return;
}
const props = stateNode._internalFiber.memoizedProps;
if (props && typeof props[handlerName] === "function") {
props[handlerName](...args);
} else {
writeLog(`Handler ${handlerName} not found on instance ${instanceId}`);
}
}
} catch (err) {
writeLog(`ERROR: ${err.message} \n ${err.stack}`);
writeOutput({ type: "error", payload: err.message });
}
});

1267
sidecar/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

41
src-tauri/Cargo.lock generated
View file

@ -880,6 +880,15 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "endi" name = "endi"
version = "1.1.0" version = "1.1.0"
@ -2992,6 +3001,7 @@ dependencies = [
"tauri-build", "tauri-build",
"tauri-plugin-clipboard-manager", "tauri-plugin-clipboard-manager",
"tauri-plugin-opener", "tauri-plugin-opener",
"tauri-plugin-shell",
] ]
[[package]] [[package]]
@ -3364,6 +3374,16 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shared_child"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e297bd52991bbe0686c086957bee142f13df85d1e79b0b21630a99d374ae9dc"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@ -3803,6 +3823,27 @@ dependencies = [
"zbus", "zbus",
] ]
[[package]]
name = "tauri-plugin-shell"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d5eb3368b959937ad2aeaf6ef9a8f5d11e01ffe03629d3530707bbcb27ff5d"
dependencies = [
"encoding_rs",
"log",
"open",
"os_pipe",
"regex",
"schemars",
"serde",
"serde_json",
"shared_child",
"tauri",
"tauri-plugin",
"thiserror 2.0.12",
"tokio",
]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.6.0" version = "2.6.0"

View file

@ -23,4 +23,5 @@ tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
tauri-plugin-clipboard-manager = "2" tauri-plugin-clipboard-manager = "2"
tauri-plugin-shell = "2"

View file

@ -9,6 +9,16 @@
"clipboard-manager:default", "clipboard-manager:default",
"clipboard-manager:allow-write-text", "clipboard-manager:allow-write-text",
"clipboard-manager:allow-write-html", "clipboard-manager:allow-write-html",
"clipboard-manager:allow-write-image" "clipboard-manager:allow-write-image",
"shell:allow-stdin-write",
{
"identifier": "shell:allow-spawn",
"allow": [
{
"name": "binaries/app",
"sidecar": true
}
]
}
] ]
} }

View file

@ -1,6 +1,7 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.run(tauri::generate_context!()) .run(tauri::generate_context!())

View file

@ -30,6 +30,7 @@
"icons/128x128@2x.png", "icons/128x128@2x.png",
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
] ],
"externalBin": ["binaries/app"]
} }
} }

View file

@ -1,23 +1,242 @@
<script lang="ts"> <script lang="ts">
import ResultsList from "$lib/components/ResultsList.svelte"; import { Command, type Child } from "@tauri-apps/plugin-shell";
import Toast from "$lib/components/Toast.svelte"; import { SvelteMap } from "svelte/reactivity";
import { Input } from "$lib/components/ui/input"; import { pack, unpack } from "msgpackr";
async function run() { interface UINode {
const { runPlugin } = await import("$lib"); id: number;
type: string;
props: Record<string, any>;
children: number[];
}
await runPlugin(); let uiTree: SvelteMap<number, UINode> = $state(new SvelteMap());
let rootNodeId: number | null = $state(null);
let sidecarLogs: string[] = $state([]);
let sidecarChild: Child | null = null;
$inspect(uiTree.get(1)?.children);
// For debugging, to see updates happen
let updateCounter = $state(0);
$effect(() => {
let receiveBuffer = Buffer.alloc(0); // Buffer to store incoming data chunks
// This function tries to parse full messages from the buffer
function processReceiveBuffer() {
// As long as there's enough data for a header, try to process it
while (receiveBuffer.length >= 4) {
const messageLength = receiveBuffer.readUInt32BE(0);
const totalLength = 4 + messageLength;
// Do we have the full message yet?
if (receiveBuffer.length >= totalLength) {
// Yes: extract the message payload
const messagePayload = receiveBuffer.subarray(4, totalLength);
// CRITICAL: Update the buffer to remove the message we just processed
receiveBuffer = receiveBuffer.subarray(totalLength);
try {
const message = unpack(messagePayload);
handleSidecarMessage(message);
} catch (e) {
console.error("Failed to unpack sidecar message:", e);
}
} else {
// No: break the loop and wait for more data
break;
}
}
}
async function connectAndRun() {
const command = Command.sidecar("binaries/app", undefined, {
encoding: "raw",
});
command.stdout.on("data", (chunk) => {
try {
receiveBuffer = Buffer.concat([receiveBuffer, Buffer.from(chunk)]);
processReceiveBuffer();
} catch (e) {
console.error("Failed to parse sidecar message:", chunk, e);
}
});
command.stderr.on("data", (line) => {
// Using a function to update the array is safer for state
sidecarLogs = [...sidecarLogs, `STDERR: ${line}`];
});
sidecarChild = await command.spawn();
sidecarLogs = [
...sidecarLogs,
`Sidecar spawned with PID: ${sidecarChild.pid}`,
];
if (sidecarChild) {
sidecarChild.write(JSON.stringify({ action: "run-plugin" }) + "\n");
}
}
connectAndRun();
return () => {
console.log("Component unmounting, killing sidecar...");
sidecarChild?.kill();
};
});
function sendToSidecar(message: object) {
if (sidecarChild) {
sidecarChild.write(JSON.stringify(message) + "\n");
}
}
/**
* Processes a single command from the sidecar. This function is designed
* to be called either for a standalone message or within a batched update.
*/
function processSingleCommand(command: any) {
switch (command.type) {
case "log":
console.log("SIDECAR:", command.payload);
sidecarLogs = [...sidecarLogs, command.payload];
break;
case "CREATE_TEXT_INSTANCE":
case "CREATE_INSTANCE": {
const { id, type, props } = command.payload;
uiTree.set(id, { id, type, props, children: [] });
break;
}
case "UPDATE_PROPS": {
const { id, props } = command.payload;
const node = uiTree.get(id);
if (node) {
const updatedNode = { ...node, props: { ...node.props, ...props } };
uiTree.set(id, updatedNode);
}
break;
}
case "APPEND_CHILD": {
const { parentId, childId } = command.payload;
if (parentId === "root") {
rootNodeId = childId;
} else {
const parentNode = uiTree.get(parentId);
if (parentNode) {
const newChildren = parentNode.children.filter(
(id) => id !== childId
);
newChildren.push(childId);
uiTree.set(parentId, { ...parentNode, children: newChildren });
}
}
break;
}
case "REMOVE_CHILD": {
const { parentId, childId } = command.payload;
const parentNode = uiTree.get(parentId);
if (parentNode) {
const newChildren = parentNode.children.filter(
(id) => id !== childId
);
uiTree.set(parentId, { ...parentNode, children: newChildren });
}
break;
}
case "INSERT_BEFORE": {
const { parentId, childId, beforeId } = command.payload;
const parentNode = uiTree.get(parentId);
if (parentNode) {
const cleanChildren = parentNode.children.filter(
(id) => id !== childId
);
const index = cleanChildren.indexOf(beforeId);
if (index !== -1) {
cleanChildren.splice(index, 0, childId);
uiTree.set(parentId, { ...parentNode, children: cleanChildren });
} else {
cleanChildren.push(childId);
uiTree.set(parentId, { ...parentNode, children: cleanChildren });
}
}
break;
}
}
}
/**
* The main message handler. It checks for a BATCH_UPDATE and processes it
* efficiently, or processes single messages otherwise.
*/
function handleSidecarMessage(message: any) {
updateCounter++;
if (message.type === "BATCH_UPDATE") {
for (const command of message.payload) {
processSingleCommand(command);
}
console.log(message.payload.length);
} else {
processSingleCommand(message);
}
}
function dispatchEvent(instanceId: number, handlerName: string, args: any[]) {
sendToSidecar({
action: "dispatch-event",
payload: { instanceId, handlerName, args },
});
} }
</script> </script>
<div class="flex flex-col items-stretch h-screen"> <main class="flex flex-grow flex-col">
<Input {#if rootNodeId}
class="rounded-none border-0 border-b focus-visible:border-b-foreground transition-colors duration-75" {@const rootNode = uiTree.get(rootNodeId)}
oninput={run} {#if rootNode?.type === "List"}
/> <div class="flex h-full flex-col">
<div class="grow"> <input
<ResultsList /> type="text"
</div> class="w-full border-b border-gray-300 px-4 py-3 text-lg focus:border-blue-500 focus:outline-none"
placeholder="Search Emojis..."
<Toast /> oninput={(e) =>
</div> dispatchEvent(rootNode.id, "onSearchTextChange", [
e.currentTarget.value,
])}
/>
<div class="flex-grow overflow-y-auto">
{#each rootNode.children as childId (childId)}
{@const childNode = uiTree.get(childId)}
{#if childNode?.type === "ListSection"}
<div>
<h3
class="px-4 pb-1 pt-2.5 text-xs font-semibold uppercase text-gray-500"
>
{childNode.props.title}
</h3>
{#each childNode.children as itemId (itemId)}
{@const itemNode = uiTree.get(itemId)}
{#if itemNode?.type === "ListItem"}
<div class="flex items-center gap-3 px-4 py-2">
<span class="text-lg">{itemNode.props.icon}</span>
<span>{itemNode.props.title}</span>
</div>
{/if}
{/each}
</div>
{/if}
{/each}
</div>
</div>
{/if}
{/if}
</main>

View file

@ -1,13 +1,14 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { sveltekit } from "@sveltejs/kit/vite"; import { sveltekit } from "@sveltejs/kit/vite";
import { nodePolyfills } from "vite-plugin-node-polyfills";
// @ts-expect-error process is a nodejs global // @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST; const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
plugins: [tailwindcss(), sveltekit()], plugins: [tailwindcss(), sveltekit(), nodePolyfills()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// //