Color mode

This page is part of Plugins in Depth and covers packaging and verification.

Packaging and verification

4. Packaging for distribution

A plugin does not have to be published to npm to be used. A site can define one directly, for example:

text
site/
└─ extensions/
   └─ local-plugin.ts
ts
// site/extensions/local-plugin.ts
 
return definePlugin({
  name: "site-local",
 
  assets: [
    {
      pluginName: "site-local",
      kind: "style",
      moduleSpecifier: "/extensions/plugin.css",
    },
  ],
});

A site-local plugin uses the same contracts as a published one — dependency resolution, pipeline, hooks, diagnostics, renderers, and Page Types. createStyleAsset() and createClientEntry() only build @riebeckite/plugin-<name>/... specifiers, so for a package that does not use that name (an unpublished plugin, or a published package under a different name) specify a moduleSpecifier the host bundler can resolve directly.

To distribute a plugin as a reusable package, use packages/plugins/backlinks as a template. Recommended layout:

text
packages/plugins/example/
├─ index.ts          ← factory calling definePlugin, re-exports public parts
├─ components/       ← components (if any)
├─ client.ts         ← only when needed
├─ src/
│  ├─ remark.ts
│  ├─ rehype.ts
│  ├─ renderer.ts
│  └─ types.ts
├─ style.css         ← only when needed
├─ package.json
├─ README_ja.md
└─ README.md

A distributed plugin depends only on @riebeckite/core and declares its own subpaths (./client, ./components, ./style.css) in exports. Never import @riebeckite/core/src/** or reference monorepo paths:

ts
import {
  something,
} from "@riebeckite/core/src/...";

Not every plugin needs client.ts, style.css, or components/ — create only what it actually uses. For the package surface and current constraints, see "Public packages and import paths" in Framework Reference.

Publish built ESM plus type declarations and point exports at the built files; build them in a prepack script so packing and publishing ship fresh output. The repository's build script is not published, so supply a small build: bundle the entry points with esbuild (format: "esm", packages: "external", external: ["@riebeckite/*"]) and emit declarations with tsc --emitDeclarationOnly. See Distributing a Plugin outside this repository for a minimal manifest.

In NodeNext/ESM packages, keep imports resolvable by Node after the build. Do not rely on the development TypeScript loader accidentally resolving extensionless imports. A package's correctness must be checked not only from its source code but also from the built distribution form.

5. Verify

Diagram source
text
flowchart LR
    Check["check"]
    Doctor["doctor"]
    Inspect["inspect plugins"]
    Build["build"]
 
    Check --> Doctor
    Doctor --> Inspect
    Inspect --> Build
sh
npm exec riebeckite check               # validate config and plugin resolution
npm exec riebeckite doctor              # health check
npm exec riebeckite inspect plugins     # list resolved plugins
npm exec riebeckite build               # confirm it appears in the output

If a plugin does not resolve, start with check for capability or import errors. Look for:

  • import error
  • missing capability
  • duplicate provider
  • dependency cycle
  • invalid options

Also ask whether you really need a plugin — perhaps configuration or an app implementation suffices.

History

1 changesCollapseExpand
1 + ---
2 + title: Packaging and verification
3 + sidebar:
4 + label: Packaging and verification
5 + order: 30
6 + ---
7 +
8 + This page is part of [Plugins in Depth](../plugin-system.md) and covers packaging and verification.
9 +
10 + # Packaging and verification
11 +
12 + ## 4. Packaging for distribution
13 +
14 + A plugin does not have to be published to npm to be used. A site can define one
15 + directly, for example:
16 +
17 + ```text
18 + site/
19 + └─ extensions/
20 + └─ local-plugin.ts
21 + ```
22 +
23 + ```ts
24 + // site/extensions/local-plugin.ts
25 +
26 + return definePlugin({
27 + name: "site-local",
28 +
29 + assets: [
30 + {
31 + pluginName: "site-local",
32 + kind: "style",
33 + moduleSpecifier: "/extensions/plugin.css",
34 + },
35 + ],
36 + });
37 + ```
38 +
39 + A site-local plugin uses the same contracts as a published one — dependency
40 + resolution, pipeline, hooks, diagnostics, renderers, and Page Types.
41 + `createStyleAsset()` and `createClientEntry()` only build
42 + `@riebeckite/plugin-<name>/...` specifiers, so for a package that does not use
43 + that name (an unpublished plugin, or a published package under a different
44 + name) specify a `moduleSpecifier` the host bundler can resolve directly.
45 +
46 + To distribute a plugin as a reusable package, use `packages/plugins/backlinks`
47 + as a template. Recommended layout:
48 +
49 + ```text
50 + packages/plugins/example/
51 + ├─ index.ts ← factory calling definePlugin, re-exports public parts
52 + ├─ components/ ← components (if any)
53 + ├─ client.ts ← only when needed
54 + ├─ src/
55 + │ ├─ remark.ts
56 + │ ├─ rehype.ts
57 + │ ├─ renderer.ts
58 + │ └─ types.ts
59 + ├─ style.css ← only when needed
60 + ├─ package.json
61 + ├─ README_ja.md
62 + └─ README.md
63 + ```
64 +
65 + A distributed plugin depends only on `@riebeckite/core` and declares its own subpaths (`./client`, `./components`, `./style.css`) in `exports`. Never import `@riebeckite/core/src/**` or reference monorepo paths:
66 +
67 + ```ts
68 + import {
69 + something,
70 + } from "@riebeckite/core/src/...";
71 + ```
72 +
73 + Not every plugin needs `client.ts`, `style.css`, or `components/` — create only
74 + what it actually uses. For the package surface and current constraints, see "Public packages and import paths" in [Framework Reference](../../reference/README.md).
75 +
76 + Publish built ESM plus type declarations and point `exports` at the built files; build them in a `prepack` script so packing and publishing ship fresh output. The repository's build script is not published, so supply a small build: bundle the entry points with `esbuild` (`format: "esm"`, `packages: "external"`, `external: ["@riebeckite/*"]`) and emit declarations with `tsc --emitDeclarationOnly`. See [Distributing a Plugin outside this repository](../../reference/plugin-api.md#distributing-a-plugin-outside-this-repository) for a minimal manifest.
77 +
78 + In NodeNext/ESM packages, keep imports resolvable by Node after the build. Do not rely on the development TypeScript loader accidentally resolving extensionless imports. A package's correctness must be checked not only from its source code but also **from the built distribution form**.
79 +
80 + ## 5. Verify
81 +
82 + ```mermaid
83 + flowchart LR
84 + Check["check"]
85 + Doctor["doctor"]
86 + Inspect["inspect plugins"]
87 + Build["build"]
88 +
89 + Check --> Doctor
90 + Doctor --> Inspect
91 + Inspect --> Build
92 + ```
93 +
94 + ```sh
95 + npm exec riebeckite check # validate config and plugin resolution
96 + npm exec riebeckite doctor # health check
97 + npm exec riebeckite inspect plugins # list resolved plugins
98 + npm exec riebeckite build # confirm it appears in the output
99 + ```
100 +
101 + If a plugin does not resolve, start with `check` for capability or import errors. Look for:
102 +
103 + - import error
104 + - missing capability
105 + - duplicate provider
106 + - dependency cycle
107 + - invalid options
108 +
109 + Also ask whether you really need a plugin — perhaps configuration or an app implementation suffices.
110 +