Search code examples
rustvitesveltekittauri

Is it possible to use Tauri's invoke function outside of a SvelteKit route?


I'm creating a Tauri application using SvelteKit and having difficulties using the invoke function outside of a .svelte file or rather inside of lib/* files.

lib/rust.ts

import { invoke } from '@tauri-apps/api';
    
export async function getClients(): Promise<any[]> {
    return await invoke('get_clients');
}

get_clients is properly declared via invoke_handler in my main.rs:

#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![get_clients])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

#[tauri::command]
fn get_clients(app_handle: tauri::AppHandle) -> Vec<String> {
    // gets clients from database
}

Whenever I try to start my project, I get the following error:

[vite] Error when evaluating SSR module /src/lib/clients.ts: failed to import "@tauri-apps/api"

Seemingly caused by this one:

Internal server error: window is not defined
      at file:///projectpath /node_modules/@tauri-apps/api/chunk-QSWLDHGO.js:1:9678
      at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
      at async Promise.all (index 0)
      at async ESMLoader.import (node:internal/modules/esm/loader:533:24)
      at async nodeImport (file:///D:/Code/Business/2023/zoom-list/node_modules/vite/dist/node/chunks/dep-79892de8.js:54039:21)
      at async eval (/src/lib/clients.ts:3:44)
      at async instantiateModule (file:///D:/Code/Business/2023/zoom-list/node_modules/vite/dist/node/chunks/dep-79892de8.js:53996:9)

SSR is disabled in src/+layout.ts:

export const prerender = true;
export const ssr = false;

Is this a limitation of Tauri or a bug?


Solution

  • I figured out a workaround!

    This bug is preventing me from shipping my Tauri + SvelteKit application, so I came up with this:

    $lib/tauriFix.ts

    let invokeFunction: any = () => console.log('invoking nothing!');
    
    if (!import.meta.env.SSR) {
        const { invoke } = await import('@tauri-apps/api');
        invokeFunction = invoke;
    }
    
    export async function invoke<T>(name: string, args?: any): Promise<T> {
        return await invokeFunction(name, args);
    }
    

    Since the error is probably caused by importing @tauri-apps/api, I made my own wrapper ("invokeFunction") for invoke which does nothing if in an SSR environment. However, if not in an SSR environment, I await import the invoke method from @tauri-apps and set invokeFunction to that. Finally, I export my own invoke method which interally just calls invokeFunction and returns the result of that. And that's (mostly) it!

    Now for this to work, we need top level await, which is not supported by default (at least it wasn't for me). Luckily, all you need to do is to modify your vite.config.ts to look like this:

    vite.config.ts

    export default defineConfig({
        //...
        build: {
            target: 'esnext'
        },
        //...
    });
    

    I did the same thing for a few more tauri functions, but it works in the exact same way.

    This is very ugly though, especially since you lose type all type information about the functions (unless you want to manually copy and maintain the interfaces from Tauri's source) and I hope this could be fixed someday.

    See more here: https://github.com/tauri-apps/tauri/issues/6554