Documentation / Développement / Guides pratiques / Build and bundle a plugin

Build and bundle a plugin

meridian-plugin build bundles your plugin into a dist/ that the host can load without any npm install — runtime dependencies are inlined into the emitted JavaScript. This recipe runs the build and shows how to confirm the artifact is self-contained. For the reasoning behind the bundle boundary, see Bundling and distribution; for the command's flags, see the CLI reference.

Prerequisites

  • A plugin project that already compiles — scaffolded per Scaffold a plugin or an equivalent layout, with a plugin.yaml and its behavior/adapter/source modules in place.
  • Dependencies installed (npm install), including any runtime packages your behaviors import.

Run the build

  1. From the plugin root, run the build:

    npm run build          # → meridian-plugin build (the scaffold wires this up)
    

    Equivalently, invoke the CLI directly: meridian-plugin build [dir=.].

  2. Read the summary line. On success the CLI reports the plugin, its version, and the number of bundled modules, and exits zero:

    ✓ @acme/vitals@0.1.0 → dist/ (1 module(s) bundled, runtime deps included).
    

    A non-zero exit means esbuild or the declaration emit failed; the error names the module or tsconfig at fault.

What the build produces

meridian-plugin build (libs/plugin-cli/src/commands/build.ts) wipes and recreates dist/, then:

  • Bundles every module referenced by the manifest — each node behavior, each adapter, and each eventSource module: — with esbuild (format: esm, platform: node, target: node20, splitting: true). Your plugin's runtime dependencies are inlined into the output, so the published artifact carries no node_modules of its own. Code shared between modules is factored into dist/chunks/.
  • Leaves two import families external, because the host guarantees them: Node builtins (node:*) and the SDK (@meridian/*). These are never inlined.
  • Emits TypeScript declarations when the project has a tsconfig.build.json or tsconfig.json (tsc --emitDeclarationOnly --noCheck). These back a plugin's ./types export, consumed by any plugin that declares a dependsOn on yours. A project without a tsconfig simply emits none.
  • Copies plugin.yaml into dist/, rewriting every module: path from .ts to .js so it points at the bundled output.
  • Copies i18n/*.yaml into dist/i18n/, where the host reads them next to the manifest.

dist/ is exactly what you ship — there is no separate packaging step.

Cross-plugin type imports

Imports of a dependency's types (@scope/dep/types) are import type and erased at compile time, so they never enter the runtime bundle. They only need to resolve during your own type-check and declaration emit. See Generate types and Add vocabulary and types.

Verify the artifact is self-contained

  1. Inspect the tree — you should see the bundled JS, the rewritten manifest, and any i18n and declarations:

    ls -R dist
    
  2. Confirm your runtime dependencies were inlined rather than left as bare imports. The only imports that may remain in the emitted JS are node:* and @meridian/*:

    grep -rE "from \"[^.@]" dist --include="*.js"        # bare (non-relative) imports
    

    Every hit should be either a node: builtin or an @meridian/ sub-path. A package name from your dependencies appearing here means it was not bundled — see the caveat below.

Caveat: native dependencies cannot be bundled

A bundler inlines JavaScript; it cannot inline a native (compiled) npm dependency — anything shipping a .node binary or a build step. Avoid such packages in plugins. If one is genuinely required, it cannot travel in dist/ and must instead be provided by the host image, which puts the burden back on the operator and breaks the drop-in autonomy a bundled plugin is meant to have.

Next steps

75 documents8 sectionssource : /docs · généré au build