Appearance
TypeScript
Lune generates TypeScript declaration files alongside every build. Your frontend gets full autocomplete and type safety derived directly from Crystal method signatures — no manual type definitions needed.
Generated files
Lune writes four files into frontend/lunejs/:
frontend/lunejs/
├── app/
│ ├── App.js # binding stubs (runtime)
│ └── App.d.ts # TypeScript declarations for your bindings
└── runtime/
├── runtime.js # quit, openURL, environment, on/once/off/emit
└── runtime.d.ts # TypeScript declarations for runtime functionsThese are regenerated automatically on every lune dev start and lune build.
App.d.ts — binding types
For each class and its @[Lune::Bind] methods, Lune generates an interface. For example:
crystal
class FileModule
include Lune::Bindable
@[Lune::Bind]
def read(path : String) : String
File.read(path)
end
@[Lune::Bind]
def exists(path : String) : Bool
File.exists?(path)
end
endGenerates:
ts
export interface FileModule {
Read(path: string): Promise<string>;
Exists(path: string): Promise<boolean>;
}
export interface Api {
FileModule: FileModule;
}
export declare const api: Api;
export default api;runtime.d.ts — runtime types
The runtime declarations include:
ts
export interface LuneEnvironment {
os: "darwin" | "linux" | "windows";
arch: string;
debug: boolean;
}
export interface LuneError {
code: string;
error: string;
}
export declare function on(name: string, cb: (data: unknown) => void): void;
export declare function once(name: string, cb: (data: unknown) => void): void;
export declare function off(name: string, cb?: (data: unknown) => void): void;
export declare function emit(name: string, data?: unknown): Promise<void>;
export declare function quit(): Promise<void>;
export declare function openURL(url: string): Promise<void>;
export declare function environment(): LuneEnvironment;
export declare function homeDir(): Promise<string>;
export declare function tempDir(): Promise<string>;
export declare function downloadsDir(): Promise<string>;
export declare function appDataDir(): Promise<string>;Setting up TypeScript in a scaffolded project
The --template vue scaffold creates a TypeScript project out of the box. For a vanilla project, you can add TypeScript support to Vite manually:
sh
npm install -D typescriptCreate a tsconfig.json at the frontend root:
json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true
},
"include": ["src"]
}Then rename your entry point from .js to .ts and update vite.config.js to vite.config.ts.
Importing with types
ts
import api from "../lunejs/app/App.js";
import { on, environment } from "../lunejs/runtime/runtime.js";
import type { LuneError } from "../lunejs/runtime/runtime.js";
// Fully typed — autocomplete works here
const result = await api.FileModule.Read("/tmp/hello.txt");
// environment() returns LuneEnvironment
const env = await environment();
if (env.os === "darwin") {
// macOS-specific code
}Typing event payloads
Events carry unknown data by default. Cast or validate at the call site:
ts
interface ProgressEvent {
done: number;
total: number;
}
on("progress", (data) => {
const { done, total } = data as ProgressEvent;
updateProgressBar(done / total);
});Handling errors with types
Use the LuneError interface from runtime.d.ts:
ts
import type { LuneError } from "../lunejs/runtime/runtime.js";
function isLuneError(e: unknown): e is LuneError {
return typeof e === "object" && e !== null && "code" in e;
}
try {
await api.FileModule.Read("/nonexistent");
} catch (e) {
if (isLuneError(e)) {
console.error(`[${e.code}] ${e.error}`);
}
}