CompMD Developer's Guide. Written for the @compmd packages at 0.7.0, and following them as they change. Download the book as PDF

21. The configuration's reference

Every key of compmd.config.json and every option of @compmd/vite's plugin: its type, its default, an example, and what is checked. Then what the plugin checks when the app builds, and the errors it gives. Chapter 9 teaches the configuration; this is the list to look things up in.

Where it is#

The configuration is one of these, never both:

  • compmd.config.json, beside the app's package.json;
  • the compmd key of package.json, as an object. A string there is a pack's document (a pack's own package.json names it so), never a configuration;
  • the plugin's config option, which takes the place of both, for a tool that serves a page of its own.

None is an empty configuration: every installed pack is available, strict, and nothing more. The schema, @compmd/vite/compmd.config.schema.json, gives an editor completion and checks as you type:

{ "$schema": "./node_modules/@compmd/vite/compmd.config.schema.json" }

readConfig from @compmd/vite/config checks it as the plugin does, without Vite, for any host (chapter 18). It finds every problem at once, and throws a ConfigError whose problems lists them.

The keys#

packs#

An object: by pack name, @scope/pack, or by folder path, ./components or ../shared/ui, each with its options ({} for none). Default: {}.

{ "packs": { "@acme/maps": { "trust": "permissive", "network": ["https://tiles.example.com"] }, "./components": { "trust": "trusted", "ambient": true } } }

List a pack only to give it more than the strict sandbox, to make it ambient, or to check its version: every installed pack is available without it. A name must be scoped; a folder is relative to the app's root (Vite's root), and must be a folder holding a pack, a package.json with a name and a document. A folder pack's name is its package.json's name, which the policy uses, and which its document's export.pack must equal (F022). Under "imports": "listed", these are the packs a document may import. Becomes policy.packs, and ambient.

packs.*.trust#

"strict", "permissive" or "trusted". Default: "strict".

{ "packs": { "@acme/reviews": { "trust": "permissive", "host": true } } }
  • strict: a sandbox with no network, which sees only its params, slots and box.
  • permissive: a frame of its own, with the network to network's origins, and host if host is true.
  • trusted: in the app's page, built with the app: for a folder pack only. A pack name with trusted is an error, since a registry pack is never the app's own code.

A document can never raise a pack's trust (chapter 8).

packs.*.network#

A list of origins a permissive pack's code may reach, each https://host[:port] (http: only for localhost, 127.0.0.1 and [::1]). Default: none.

{ "packs": { "@acme/maps": { "trust": "permissive", "network": ["https://tiles.example.com", "https://tiles.example.com:8443"] } } }

An origin alone: no path, query or credentials, no wildcard. Only a permissive pack takes it.

packs.*.host#

true or false: whether a permissive pack's components get host, the calls the app gives them (createSandbox's host). Default: false. Only a permissive pack takes it.

{ "packs": { "@acme/reviews": { "trust": "permissive", "host": true } } }

packs.*.ambient#

true or false: whether every document gets the pack's names without importing it. Default: false.

{ "packs": { "./components": { "trust": "trusted", "ambient": true } } }

An ambient pack's names have the lowest priority, so a document's own definitions and imports win. A document may still import it by name, to say what it depends on. Becomes the request's ambient.

packs.*.version#

A semver range the installed version must satisfy. Default: none, any version.

{ "packs": { "@kit/charts": { "version": "^2.0.0" } } }

Checked when the app builds: a pack at a version outside it stops the build. It says nothing about which version a document gets; the document's import does.

imports#

"any" or "listed". Default: "any".

{ "imports": "listed" }

any: a document may import any pack the app has, installed or from its registries. listed: only the packs in packs and the ambient ones; any other import is F026, saying the host allows only the packs its configuration lists. Becomes policy.imports.

deny#

A list of { "pack", "version"?, "reason" }: packs the app won't serve, all versions or those in the semver range version, with the reason readers see. Default: [].

{ "deny": [{ "pack": "@bad/pack", "version": "<2", "reason": "a flaw found in 1.x" }] }

pack is a scoped name; reason isn't empty. A denied import is F026, with the reason. A pack from a registry that is denied is never downloaded. deny is the app's own policy, not what keeps readers safe: the sandbox does that for every pack. Becomes policy.deny.

maxPackSize#

A size: a number of bytes, or a string, a number and B, KB, MB or GB ("5 MB", "2.5 MB"; a kilobyte is 1,000 bytes). Default: no limit for installed packs; 10 MB for a pack from a registry.

{ "maxPackSize": "5 MB" }
  • An installed pack whose files (its package.json, its documents and what they import) are larger is left out: its imports are F026, saying why, and the plugin warns when it builds. Becomes policy.refused.
  • A pack from a registry is checked by the unpacked size its registry's metadata states, before it is downloaded; one larger, or whose metadata states no size, isn't fetched, and is F026. Becomes registry.maxPackSize.

libraries#

A list of npm packages the app installs, which the frames offer component code by name, besides React, React DOM and Svelte: a name, "d3", or { "name", "imports" } for a package whose components import more names than its own. Default: [].

{ "libraries": ["d3", { "name": "d3-scale", "imports": ["d3-scale", "d3-scale-chromatic"] }] }

Each one is built once into the frames' libs/ (compmd/frames/<hash>/libs/), and the frames' import map gives components that copy. A library that isn't installed stops the build. Their names and versions go in the request's policy.libraries, so the processor reports a pack whose peerDependencies the app can't meet (W121). A document whose components import an offered library works only in hosts that offer it.

registries#

An object: by scope, "@kit", with "*" for every other scope, each the URL of an npm registry, named in full. Default: none, so a document's import of a pack the app doesn't install is F026.

{ "registries": { "@kit": "https://npm.kit.example/", "*": "https://registry.npmjs.org/" } }

Each URL is https: (http: only for localhost, 127.0.0.1 and [::1]), without credentials, query or fragment: a private registry's token belongs to the app's proxy, in its NpmRegistry's headers. A / is added at its end when it has none. Neither a document nor a pack can name a registry. Becomes virtual:compmd's registry, which resolvePacks takes (chapter 9).

fetch#

"host" or "direct": who reaches the registries. Default: "host".

{ "registries": { "*": "https://registry.npmjs.org/" }, "fetch": "direct" }
  • host: the page reaches the registries only through the app's proxy, at compmd/registry/, so they learn nothing about readers. The dev server and vite preview serve it; a deployed app serves it from its server or a worker (chapter 18).
  • direct: the reader's browser fetches from the registries named, without credentials, which then learn the reader's address and the names and versions of the packs a document imports. For an app with no server; tell your readers.

The plugin's options#

compmd(options), in vite.config.ts. All are optional.

folder#

The folder of the app, under Vite's base, where the bundle (the frames, their libraries and the packs' files) and the registry proxy are served. Default: "compmd/".

compmd({ folder: "sandbox/" });

Leading slashes are dropped and a trailing one added: "/sandbox" is sandbox/. virtual:compmd's sandbox.origin and registry follow it. Not with out.

out#

The folder, relative to Vite's root, the bundle is written into, with its manifest.json, for a desktop app's plugin to embed or serve, in place of the web output. Default: none, and the bundle is part of the web output, under folder.

compmd({ out: "src-tauri/compmd", source: "compmd://localhost/" });

With it, the dev server serves no bundle, since the app's plugin does, and writes the bundle when it starts; virtual:compmd's sandbox has no origin, which the page takes from the plugin (globalThis.__COMPMD__). The folder is build output: list it in the app's .gitignore. A Tauri app uses it.

source#

The custom scheme the bundle is served on, as a scheme, a host and a path ending in /: "compmd://localhost/". Default: none, for a bundle served over http or https.

WebKit reads 'self' in a sandboxed frame as nothing on a scheme other than http and https, so the frames' policies name source beside each 'self', and the manifest records it, for the plugin to serve the scheme the bundle was built for. buildBundle takes the same option.

config#

The configuration itself, as the parsed JSON, in place of compmd.config.json and package.json's compmd key: for a tool that serves a page of its own, as @compmd/pack's test page does. Default: none, and the plugin reads the files.

compmd({ config: { packs: { "./": {} } } });

It is checked as the file is, and its problems are listed under the plugin's config; folder packs in it are relative to Vite's root.

What the plugin checks#

At the start of every build and of the dev server, and in dev again when compmd.config.json, package.json or a file of a pack changes:

  1. The configuration, with every problem at once (below).
  2. The files. A configuration in both compmd.config.json and package.json is an error (keep one), and so is a file that isn't JSON.
  3. The packs. Each listed pack is installed (@kit/charts is listed in the configuration, but it isn't installed: npm install @kit/charts), at the version its version asks (@kit/charts is 1.4.0, which isn't ^2.0.0 as the configuration asks); each listed folder is a folder of the app, holding a pack; each library is installed; and each file a pack's document imports is in the pack, and doesn't leave it.
  4. The listed packs' documents, through the processor, in one document that imports all of each: an error stops the build (the listed packs have errors, each as file:line:column: code message), a warning is a Vite warning. F026 is a warning there, since it is how a left-out pack shows. A pack left out by maxPackSize is a warning too.

This configuration has a problem in nearly every line:

{
  "packs": {
    "@acme/maps": { "trust": "trusted", "network": ["https://tiles.example.com/v2"] },
    "charts": {}
  },
  "imports": "some",
  "maxPackSize": "lots"
}

The plugin, and readConfig, give them all:

compmd.config.json:
- packs["@acme/maps"].trust: only a folder in the app may be trusted, never a registry pack; a registry pack that needs more is `permissive`
- packs["@acme/maps"].network: only a permissive pack reaches the network
- packs["@acme/maps"].network: `https://tiles.example.com/v2` must be an origin alone, without a path
- packs["charts"]: a pack is a scoped name (`@scope/pack`) or a folder path (`./components`)
- imports: `any` or `listed`
- maxPackSize: bytes, or a size such as `"5 MB"`

A key the configuration doesn't have, or an option a pack doesn't take, is a problem too, whose message names the keys it takes.

In a build, a problem stops it. On the dev server, a change that brings one is logged, [compmd] …, and the app keeps what it had until the problem is fixed.

The plugin finds installed packs among the app's dependencies and devDependencies: each scoped one whose package.json has a compmd field or whose folder has index.comp.md. Each one's integrity comes from the nearest lock file above the app, pnpm-lock.yaml or package-lock.json; without one, a document that pins a pack's integrity gets F035.

Next: the diagnostics.