Ionify is intentionally config-light. The goal is: a small, explicit config surface that maps to real pipeline behavior (graph versioning, CAS isolation, deps optimizer, federation contracts).

ionify.config.ts

Ionify looks for one of:

  • ionify.config.ts
  • ionify.config.mts
  • ionify.config.js / ionify.config.mjs / ionify.config.cjs

You can export an object or use defineConfig:

import { defineConfig } from "@ionify/ionify";

export default defineConfig({
  entry: "/src/main.tsx",
});

create-ionify

Scaffold a new project (optionally monorepo) via:

Common CLI flags:

  • --template basic|dashboard
  • --monorepo / --no-monorepo
  • --testing vitest|playwright|both
  • --pm pnpm|npm|yarn|bun
  • --yes (defaults)

Workspace identity (workspaces + monorepos)

Ionify automatically discovers the workspace root (pnpm/yarn/npm workspaces + Git submodules) and unifies state under the workspace .ionify/ directory.

Ionify also sets these environment variables for engine components and tooling:

  • IONIFY_WORKSPACE_ROOT
  • IONIFY_PROJECT_ROOT
  • IONIFY_STATE_DIR
  • IONIFY_WORKSPACE_ID
  • IONIFY_PROJECT_ID

Scaffolding extras

The scaffolder can optionally configure:

  • AI assistant setup: .cursorrules, .github/copilot-instructions.md, and docs/ai-prompts/
  • Performance budget enforcement: Lighthouse config + CI workflow + a performanceBudget section in ionify.config.ts
  • Visual regression testing: Percy/Chromatic setup + example tests + CI workflow

performanceBudget

When enabled by the scaffolder, performanceBudget is used by the generated CI/scripts to fail builds when budgets are exceeded.

import { defineConfig } from "@ionify/ionify";

export default defineConfig({
  // Example (preset-based)
  performanceBudget: "strict", // "moderate" | "relaxed" | false
});

Generated files typically include:

  • lighthouserc.json
  • .github/workflows/performance.yml
  • scripts/analyze-bundle.js

Visual regression testing

When enabled by the scaffolder, visual tests run on CI and catch UI diffs before merge.

Generated files typically include:

  • tests/visual/
  • .github/workflows/visual-tests.yml
  • percy.config.js (or Chromatic config)

DX metrics

Ionify build/dev output reflects real engine state (CAS-first hydration + deterministic planning).

  • ionify build prints: Modules in plan, CAS hits, transforms needed, and total time.
  • ionify analyze summarizes .ionify/ state (graph + packs + slimming when enabled).

root

root sets the project root directory Ionify uses for:

  • Resolving entry and imports
  • Locating env files (.env*)
  • Storing caches under .ionify/ (CAS + deps artifacts)

Notes:

  • In a monorepo/workspace, Ionify stores .ionify/ at the workspace root (shared across apps).

Defaults:

  • If you have an ionify.config.*, Ionify uses the config file’s directory
  • If you don’t, Ionify uses process.cwd()

Entry

  • entry: string or string[] (project-relative, /src/... is supported)

This feeds the graph and the build planner.

Resolve

export default {
  resolve: {
    alias: {
      "@core": "/core",
    },
    extensions: [".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx", ".json"],
    conditions: ["import", "module", "browser", "default"],
    mainFields: ["module", "jsnext:main", "jsnext", "main"],
  },
};

optimizeDeps (deps optimizer + packs)

Ionify’s deps optimizer serves node_modules through /@deps/* and caches deterministic artifacts under .ionify/deps/<depsHash>/.

Core options:

  • optimizeDeps.include: string[] Pre-optimize these on dev server start (background pre-warm).
  • optimizeDeps.exclude: string[] Skip optimization and pack selection for these deps.
  • optimizeDeps.bundleEsm: boolean (default true) Bundle ESM deps into self-contained files to reduce request waterfalls.
  • optimizeDeps.sourcemap: boolean (default false) Sourcemaps for optimized deps.

sharedChunks

  • optimizeDeps.sharedChunks: "auto" | boolean Build shared chunks when optimizing multiple dep entries as one graph.

Notes:

  • Vendor packs require sharedChunks !== false to be effective.
  • For best request reduction, keep bundleEsm=true and sourcemap=false.

vendor (preloader)

  • optimizeDeps.vendor: "auto" | string[] | false Build a vendor.<depsHash>.js preloader module to start fetching hot deps earlier.

This can improve cold-start waterfalls, but it does not collapse request count by itself.

vendorPacks (few-request mode)

vendorPacks is the real “few-request mode”: it bundles many deps into a small number of pack files, then Ionify rewrites imports to route through those packs.

Modes:

  • false Disable packs (default)
  • true Force one heuristic “app vendor” pack
  • "auto" Progressive layered vendor: build core first, then lazy-build feature packs in the background when the graph proves they’re needed
  • { [packName]: string[] } Manual packs

Auto-selection caps:

  • optimizeDeps.vendorPackMaxBytes: number (default: 600KB)
  • optimizeDeps.vendorPackMaxMembers: number (default: 25)

packSlimming (usage-driven)

  • optimizeDeps.packSlimming: "auto" | boolean

When enabled, Ionify writes a deterministic usage index (deps-usage.v1.json) and builds usage-minimized pack variants in the background. On warm reload, Ionify prefers the slim pack if it’s ready, otherwise falls back to the base pack or /@deps/* wrappers.

Example (recommended progressive config):

import { defineConfig } from "@ionify/ionify";

export default defineConfig({
  entry: "/src/main.tsx",
  optimizeDeps: {
    sharedChunks: "auto",
    vendorPacks: "auto",
    packSlimming: "auto",

    // Optional helpers
    vendor: "auto",
    include: ["lodash"],
  },
});

Example (manual packs: core/ui/data):

import { defineConfig } from "@ionify/ionify";

export default defineConfig({
  entry: "/src/main.tsx",
  optimizeDeps: {
    sharedChunks: true,
    vendorPacks: {
      core: [
        "react",
        "react-dom/*",
        "scheduler",
        "scheduler/*",
        "react/jsx-runtime",
        "react/jsx-dev-runtime",
        "react-router",
        "react-router-dom",
        "@remix-run/router",
        "react-refresh",
        "react-refresh/*",
      ],
      ui: ["@mui/*", "@radix-ui/*"],
      data: [
        "@tanstack/react-query",
        "@tanstack/react-query/*",
        "axios",
        "axios/*",
        "zod",
        "zod/*",
        "react-hook-form",
        "react-hook-form/*",
        "@hookform/resolvers",
        "@hookform/resolvers/*",
        "zustand",
        "zustand/*",
      ],
    },
    packSlimming: "auto",
  },
});

Federation

federation lets Ionify connect independently built apps without turning every app into a duplicate vendor bundle.

Core options:

  • federation.host: string Stable app identity written into the build manifest. Defaults to package.json#name or the project directory name.
  • federation.remotes: Record<string, string | RemoteConfig> Remote apps this host can import from.
  • federation.exposes: Record<string, string> Local modules this app exposes to other hosts.
  • federation.shared: Record<string, boolean | SharedConfig> Dependencies that should be treated as shared contracts instead of repeated payload.

Remote config:

  • entry: string Remote manifest/container entry URL.
  • external?: string | string[] Import specifier(s) Ionify preserves in source and production output. Defaults to the remote name.
  • version?: string Expected remote version.
  • integrity?: string Optional integrity metadata for the remote entry.
  • hash?: string Optional remote contract/content hash.

Shared config:

  • singleton?: boolean Use one runtime instance, usually for React and React DOM.
  • requiredVersion?: string Version range this app expects from the shared dependency.
  • version?: string Version this app provides.
  • strictVersion?: boolean Fail closed when the shared contract does not match.
  • eager?: boolean Include the shared module eagerly when that is the right runtime tradeoff.
  • shareScope?: string Isolate shared contracts by scope.

Example (host consuming a remote and sharing React once):

import { defineConfig } from "@ionify/ionify";

export default defineConfig({
  entry: "/src/main.tsx",
  federation: {
    host: "shell",
    remotes: {
      dashboard: {
        entry: "https://dashboard.example.com/ionify-federation.json",
        external: ["dashboard/", "dashboard"],
        version: "1.4.0",
      },
    },
    shared: {
      react: { singleton: true, requiredVersion: "^19.0.0" },
      "react-dom": { singleton: true, requiredVersion: "^19.0.0" },
    },
  },
});

Example (remote exposing modules to hosts):

import { defineConfig } from "@ionify/ionify";

export default defineConfig({
  entry: "/src/main.tsx",
  federation: {
    host: "dashboard",
    exposes: {
      "./Widget": "/src/federated/Widget.tsx",
      "./routes": "/src/federated/routes.ts",
    },
    shared: {
      react: { singleton: true, version: "19.0.0" },
      "react-dom": { singleton: true, version: "19.0.0" },
    },
  },
});

What this saves:

  • The host imports remote code through a stable external contract instead of bundling the remote app into the host.
  • Shared dependencies such as React are declared once as runtime contracts, so the host and remote do not each ship and initialize their own copy.
  • Federation metadata is folded into Ionify's graph/CAS model, so changed remote contracts invalidate precisely instead of forcing unrelated rebuild work.

Env handling (Vite-compatible)

Ionify loads env files (Vite-style):

  • .env
  • .env.local
  • .env.<mode>
  • .env.<mode>.local

envPrefix

envPrefix controls which env vars Ionify is allowed to expose to the browser via import.meta.env.*.

  • Default: ["VITE_", "IONIFY_"]
  • Ionify always exposes: MODE, NODE_ENV, plus DEV / PROD

Ionify auto-exposes env vars to the client as import.meta.env.* when they match envPrefix.

You can also declare types in your app (example from react-basic/src/ionify-env.d.ts):

declare global {
  interface ImportMetaEnv {
    readonly MODE: string;
    readonly NODE_ENV: string;
    readonly DEV: boolean;
    readonly PROD: boolean;
    readonly IONIFY_API_URL: string;
  }
}

define (compile-time constants)

export default {
  define: {
    __APP_VERSION__: JSON.stringify("1.0.0"),
    "process.env.NODE_ENV": JSON.stringify("development"),
  },
};

Engine-level flags (optional)

Ionify also supports env flags for pipeline selection:

  • IONIFY_PARSER=oxc|swc|hybrid
  • IONIFY_MINIFIER=oxc|swc|auto
  • IONIFY_OPTIMIZATION_LEVEL=0|1|2|3

Output directory (today)

Build output defaults to dist/. Override via CLI:

ionify build --out-dir dist