Devtools

AOT Compilation

Learn how Ahead-of-Time (AOT) compilation works in mion using the Vite plugin and virtual modules.

mion's Vite plugin provides Ahead-of-Time (AOT) compilation that pre-compiles critical runtime functions at build time. Instead of writing files to disk, AOT caches are generated as Vite virtual modules that get embedded directly into your server and client bundles.

Benefits

  • Performance: Pre-compile JIT functions, pure functions, and router methods before deployment
  • Faster Startup: Eliminate runtime compilation overhead by loading pre-built caches
  • Secure Environments: Run in environments like Cloudflare Workers that do not allow eval or new Function
  • Lazy Loading: When aot: true is set, the heavy @mionkit/run-types package is NOT loaded at all
  • Smaller Bundles: With proper tree-shaking, run-types is excluded from production bundles in AOT mode
  • Zero Disk Artifacts: Caches live as virtual modules — no generated files to commit or manage
  • How It Works

    mion uses Just-In-Time (JIT) compilation to generate validation and serialization functions at runtime. While this provides great flexibility, it has two main drawbacks:

    1. Startup overhead: Functions are compiled when the application starts
    2. Security restrictions: Some environments (like Cloudflare Workers) don't allow dynamic code generation

    AOT compilation solves both issues by pre-compiling these functions during your Vite build process.

    The Build Pipeline

    The mion Vite plugin generates AOT caches through this pipeline:

    1. buildStart — The plugin spawns a vite-node child process that runs your server start script
    2. Router initialization — Your server starts, registers all routes, and compiles JIT functions
    3. Cache capture — The router detects AOT compilation mode and emits all caches via IPC
    4. Virtual modules — The plugin stores the caches and serves them as Vite virtual modules
    5. Bundle embedding — When your app imports a virtual module, the cached code is embedded in the bundle

    Virtual Modules

    The plugin generates these virtual modules that you can import in your application:

    ModuleDescription
    virtual:mion-aot/jit-fnsPre-compiled JIT validation/serialization functions
    virtual:mion-aot/pure-fnsPre-compiled pure functions cache
    virtual:mion-aot/router-cacheRouter methods metadata cache
    virtual:mion-aot/cachesCombined module — imports and registers all 3 caches
    virtual:mion-server-pure-fnsServer pure functions extracted from client code

    For TypeScript support, add the virtual module type definitions to your tsconfig.json:

    tsconfig.json
    {
      "compilerOptions": {
        "types": ["@mionkit/devtools/virtual-modules"]
      }
    }
    

    Cache

    The plugin caches AOT results on disk (in Vite's node_modules/.vite/ by default) so subsequent builds skip the expensive vite-node step when your server source hasn't changed. The cache is invalidated automatically when source files change, the devtools version updates, or AOT options change.

    Set MION_AOT_FORCE=true to force regeneration regardless of the cache.

    Important: What Gets Compiled

    AOT only compiles routes and types that are initialized when running your start script.

    The AOT build process works by executing your start script and capturing all routes that get registered. This means:

    • Routes not loaded by the start script will not be compiled
    • Types not exposed in route/middleFn parameters or return types will not be compiled
    • Dynamically created types at runtime will not be compiled
    import {Routes, route} from '@mionkit/router';
    import {runType} from '@mionkit/run-types';
    
    interface User {
        id: string;
        name: string;
    }
    
    interface AuditLog {
        action: string;
        timestamp: Date;
    }
    
    declare function findUser(id: string): User;
    
    const routes = {
        // ✅ User type WILL be compiled (exposed in return type)
        getUser: route((ctx, id: string): User => {
            return findUser(id);
        }),
    
        // ✅ string parameter WILL be compiled
        saveUser: route((ctx, name: string): void => {
            // ...
        }),
    } satisfies Routes;
    
    // ❌ AuditLog type will NOT be compiled!
    // It's not used in any route parameter or return type
    async function logAction(log: AuditLog): Promise<void> {
        const validate = await runType<AuditLog>().createJitFunction('isType');
        // This will fail in secure environments (no JIT available)
    }
    
    // ❌ This route won't be compiled if it's in a file
    // that is not imported by the start script
    // routes-admin.ts (not imported)
    const adminRoutes = {
        deleteUser: route((ctx, id: string): void => {
            /* ... */
        }),
    };
    
    Solution: Ensure your start script imports and registers ALL routes you need. If you use runType<T>() directly, make sure T is also used in at least one route parameter or return type.

    Using AOT Caches

    Server Usage

    Import the combined caches module before initializing the router. The virtual:mion-aot/caches module automatically registers all caches:

    // Import cache exports from your AOT package
    import {routerCache, jitFnsCache, pureFnsCache} from 'my-api-aot';
    // Import the cache loading function from @mionkit/core
    import {addAOTCaches} from '@mionkit/core';
    // Now initialize your router - it will use the pre-compiled functions
    import {initMionRouter} from '@mionkit/router';
    import {routes as myRoutes} from './aot-routes-example.ts';
    
    // Load the pre-compiled caches BEFORE initializing the router
    addAOTCaches(jitFnsCache, pureFnsCache);
    export const myApi = await initMionRouter(myRoutes);
    
    Important: You must import the AOT caches before initializing the router. The router checks for pre-compiled functions during initialization and uses them instead of generating new ones.

    Enabling Strict AOT Mode

    When you set aot: true in the router options, the router operates in strict AOT mode:

    • No run-types loading: The heavy @mionkit/run-types package is NOT loaded at all
    • Fail-fast validation: If any route/middleFn is missing from the AOT cache, an AOTCacheError is thrown immediately
    • Smaller bundles: With proper tree-shaking, run-types is excluded from production bundles
    await initMionRouter(routes, {
      aot: true,  // Strict AOT mode - run-types will NOT be loaded
    });
    
    When aot: true is set, ALL routes and middleFns MUST be present in the AOT cache. If any are missing, the router will throw an AOTCacheError instead of falling back to JIT compilation.

    Client Usage

    AOT caches are less important on the client than on the server. Browsers don't have the same restrictions as secure server environments, and the mion client can request any missing JIT function from the server on demand. For larger APIs, it's often better to NOT load AOT caches in the client initially. This reduces bundle size and lets the client fetch only the JIT functions it actually needs.

    If you still want to preload AOT caches (for offline support or reduced latency), load them before initializing the client:

    // Load custom AOT caches (optional for client)
    import {jitFnsCache, pureFnsCache} from 'my-api-aot';
    import {addAOTCaches} from '@mionkit/core';
    
    addAOTCaches(jitFnsCache, pureFnsCache);
    
    // Then initialize the client
    import {initClient} from '@mionkit/client';
    import type {MyApi} from './aot-routes-example.ts';
    
    const {routes, middleFns} = initClient<MyApi>({baseURL: 'http://localhost:3000'});
    

    Complete Server Example

    // Load AOT caches first
    import {routerCache, jitFnsCache, pureFnsCache} from 'my-api-aot';
    import {addAOTCaches} from '@mionkit/core';
    // Then initialize router and server
    import {initMionRouter} from '@mionkit/router';
    import {initHttp} from '@mionkit/node';
    import {routes} from './aot-routes-example.ts';
    
    // Load the pre-compiled caches BEFORE initializing the router
    addAOTCaches(jitFnsCache, pureFnsCache);
    export const myApi = await initMionRouter(routes);
    
    initHttp({port: 3000});
    console.log('Server running on port 3000');
    

    Secure Environments (Cloudflare Workers, etc.)

    AOT compilation is essential for running mion in secure environments that restrict dynamic code execution. Environments like Cloudflare Workers, Deno Deploy, and some enterprise environments prohibit:

    • eval()
    • new Function()
    • Other forms of dynamic code generation

    mion's JIT compilation normally uses new Function() to generate validation and serialization functions. AOT compilation pre-generates these functions as static code, eliminating the need for runtime code generation.