Skip to content
Meeko
Type to search the guides and the API reference. Open it from any page with / or Ctrl K; move with the arrow keys and open a result with Enter.
Get started

Contents

On this page

defineMeekoModule

Declares a Meeko module that configures the project from code when loading configuration, with merged options and the meeko object.

macro Since 1.2.0 @meekojs/meeko/framework Source code
ts
import { defineMeekoModule } from "@meekojs/meeko/framework";

defineMeekoModule declares a module: code configuring the project, like Nuxt modules. Its setup adds components, auto-imports, layouts, application plugins, server middleware and handlers, <head> entries, public files and Vite plugins, and responds to build hooks. The project registers it by name in modules in meeko.config.ts (modules: ["@acme/hello"] or ["./modules/hello"]) and passes options in its meta.configKey key. A package default-exports it in its ./module subpath or root, and declares "keywords": ["meeko-module"] in package.json so the editor completes its name.

setup runs in Node when loading configuration (in meeko dev, meeko build, meeko check and the language server in a trusted workspace), once per configuration and process. It never reaches the browser.

Usage#

ts
// modules/hello/index.ts
import { defineMeekoModule } from "@meekojs/meeko/framework";

export interface HelloOptions { greeting: string }

// Types the `hello` key in meeko.config.ts; Meeko loads this augmentation automatically.
declare module "@meekojs/meeko/framework" {
  interface MeekoConfig { readonly hello?: Partial<HelloOptions> }
}

export default defineMeekoModule<HelloOptions>({
  meta: { name: "hello", configKey: "hello", compatibility: { meeko: ">=1.2" } },
  defaults: { greeting: "Hello" },
  setup(options, meeko) {
    meeko.addComponentsDir(new URL("./components", import.meta.url), { prefix: "Hello" });
    meeko.addPlugin({ src: new URL("./runtime/greeting.ts", import.meta.url), options: { greeting: options.greeting } });
    meeko.addHead({ meta: [{ name: "generator", content: "Meeko" }] });
  },
});
ts
// meeko.config.ts
import { defineMeekoConfig } from "@meekojs/meeko/framework";

export default defineMeekoConfig({
  modules: ["./modules/hello"],
  hello: { greeting: "Buenas" },
});

Type#

ts
function defineMeekoModule<Options extends object = Record<string, unknown>>(
  definition: MeekoModuleDefinition<Options>,
): MeekoModule<Options>

MeekoModuleDefinition

ts
interface MeekoModuleDefinition<Options extends object> {
	readonly meta: MeekoModuleMeta
	readonly defaults?: Partial<Options>
	setup(options: Options, meeko: MeekoModuleContext): void | Promise<void>
}

MeekoModuleMeta

ts
interface MeekoModuleMeta {
	readonly name: string
	readonly configKey?: string
	readonly compatibility?: { readonly meeko?: string }
}

MeekoModuleContext

ts
interface MeekoModuleContext {
  readonly rootDir: string;
  readonly dev: boolean;
  readonly config: Readonly<MeekoConfig>;
  readonly hooks: MeekoHookable;
  addLayer(dir: string | URL): void;
  addComponentsDir(
    dir: string | URL,
    options?: { readonly prefix?: string; readonly exclude?: readonly string[] },
  ): void;
  addLayout(name: string, file: string | URL): void;
  extendRoutes(extend: (routes: readonly RouteRecord[]) => readonly RouteRecord[]): void;
  addAutoImports(entries: ModuleAutoImport | readonly ModuleAutoImport[]): void;
  addAutoImportsDir(
    dir: string | URL,
    options: { readonly scope: "app" | "server" | "shared" },
  ): void;
  addPlugin(plugin: ModulePlugin | string | URL): void;
  addServerMiddleware(file: string | URL): void;
  addServerHandler(handler: ModuleServerHandler): void;
  addPublicFile(
    path: string,
    contents:
      string | Uint8Array | (() => string | Uint8Array | Promise<string | Uint8Array>),
    options?: ModulePublicFileOptions,
  ): void;
  addStylesheet(file: string | URL): void;
  addTypeTemplate(filename: string, contents: string): void;
  addHead(input: DocumentHead): void;
  addVitePlugin(plugin: object | readonly object[]): void;
  extendViteConfig(
    extend: (
      config: Record<string, any>,
      context: { readonly command: "serve" | "build"; readonly ssr: boolean },
    ) => void | Record<string, any>,
  ): void;
  warn(message: string): void;
}

Parameters#

Option Type Default Description
meta.name string required Unique module name; two modules with the same name are an error.
meta.configKey string none meeko.config.ts key containing module options; since 1.8, the only documented way to pass them. Cannot be a Meeko option or repeated across modules.
meta.compatibility.meeko npm range any Such as ">=1.2". Loading fails if the Meeko version is outside the range.
defaults Partial<Options> {} Default option values.
setup (options, meeko) => void | Promise<void> required Receives merged options and the meeko object. Can be asynchronous.

Options merge in this order: defaults, then config[meta.configKey]. Plain objects merge deeply; arrays are replaced.

Key types. For meeko.config.ts to type the key without importing the module, the module augments MeekoConfig (declare module "@meekojs/meeko/framework" { interface MeekoConfig { readonly hello?: … } }) in its default export file or one that file imports. Meeko writes .meeko/types/config.d.ts, importing types from each module in modules, so augmentation loads automatically in the editor and meeko check.

Return value#

A MeekoModule<Options>, to default-export. Without meta.name, throws TypeError: defineMeekoModule requires meta.name; without setup, TypeError: Meeko module <nombre> requires a setup function.

The meeko object in setup:

Member What it does
rootDir, dev, config Project folder, true in meeko dev and loaded configuration.
addLayer(dir) Adds a layer.
addComponentsDir(dir, { prefix }) Auto-imports the folder's .meeko files with a prefix.
addLayout(name, file) Since 1.5, a named layout.
extendRoutes(fn) Since 1.8, transforms the route manifest: fn(routes) returns the complete list, validated as a manifest.
addAutoImports(entries) / addAutoImportsDir(dir, { scope }) Auto-imports from a package, file or folder, with their scope ("app", "client", "server", "shared"). In addAutoImports, componentOnly: true restricts an app entry to component code and app/ modules that can also run in the browser; excludes it from <script server> and server/.
addPlugin({ src, mode?, options? }) An application plugin (definePlugin).
addServerMiddleware(file) / addServerHandler({ route, handler }) Server middleware (before the project's) and a handler for an exact route.
addPublicFile(path, contents) A public file.
addTypeTemplate(filename, contents) A generated .d.ts declaration, removed when the module is disabled.
addHead(input) document.head entries.
addVitePlugin(plugin) / extendViteConfig(fn) Vite plugins and configuration in meeko dev and meeko build.
hooks.hook(name, fn) ready, routes:resolved, build:before, prerender:routes, sitemap:resolved (since 1.8), build:done.
warn(message) A warning with the module name.

Examples#

Add files to the build with a hook#

ts
// modules/humans/index.ts
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { defineMeekoModule } from "@meekojs/meeko/framework";

export default defineMeekoModule<{ team: string[] }>({
  meta: { name: "humans", configKey: "humans" },
  defaults: { team: [] },
  setup(options, meeko) {
    meeko.hooks.hook("build:done", async ({ output }) => {
      await writeFile(join(output, "humans.txt"), options.team.join("\n"));
    });
  },
});

Server auto-imports#

ts
// modules/mailer/index.ts
import { defineMeekoModule } from "@meekojs/meeko/framework";

export default defineMeekoModule({
  meta: { name: "mailer" },
  setup(_options, meeko) {
    meeko.addAutoImports([{ name: "sendMail", from: new URL("./runtime/send.server.ts", import.meta.url), scope: "server" }]);
    if (meeko.dev) meeko.warn("emails are captured in Mailpit during development");
  },
});

Prerender more URLs#

ts
// modules/archive/index.ts
import { defineMeekoModule } from "@meekojs/meeko/framework";

export default defineMeekoModule({
  meta: { name: "archive" },
  setup(_options, meeko) {
    meeko.hooks.hook("prerender:routes", ({ urls }) => {
      for (const year of ["2025", "2026"]) urls.push(`/archive/${year}`);
    });
  },
});

Common mistakes#

A module without meta.name#

Incorrectts
export default defineMeekoModule({ meta: {}, setup() {} });

meeko check, meeko dev and meeko build stop when loading configuration with No se puede cargar el módulo de Meeko ./modules/hello: defineMeekoModule requires meta.name.

Correctts
export default defineMeekoModule({ meta: { name: "hello" }, setup() {} });

A Meeko version outside compatibility#

Incorrectts
export default defineMeekoModule({ meta: { name: "hello", compatibility: { meeko: ">=9" } }, setup() {} });

Configuration loading stops with El módulo de Meeko hello necesita Meeko >=9; este proyecto usa 1.7.0 (with the installed version). Declare the range in which you test the module.

Correctts
export default defineMeekoModule({ meta: { name: "hello", compatibility: { meeko: "^1.2.0" } }, setup() {} });

Secrets in plugin options#

addPlugin's options travel in the browser bundle if the plugin runs there: no error prevents this.

Incorrectts
meeko.addPlugin({ src: new URL("./runtime/analytics.ts", import.meta.url), options: { apiKey: process.env.ANALYTICS_SECRET } });
Correctts
meeko.addPlugin({ src: new URL("./runtime/analytics.server.ts", import.meta.url), options: { endpoint: "/api/collect" } });

Notes#

  • Where it runs. Only in Node when loading configuration. setup, its "server" plugins, middleware, handlers and server auto-imports never reach the browser: the Vite plugin rejects the build if the client graph reaches them, or if a browser-loaded file imports the module. Only meeko.config.ts should import it.
  • Paths. Values received by meeko can be an absolute path, a file: URL (new URL("./x", import.meta.url), typical in a package) or a project-root-relative path.
  • Hooks. Run in registration order and Meeko awaits each one. hook() returns a function to remove it.
  • Custom host. A Vite host using only the meeko() plugin receives modules' auto-imports, components, layers and plugins, but not their addVitePlugin, extendViteConfig, public files or hooks.
  • Development. Changing a module or its options requires restarting meeko dev, like any configuration change.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters

Edit on GitHub: docs/en/reference/macros/definemeekomodule.md