Components
Some Motion Script components (Code, Latex) don't ship as npm dependencies.
Instead, ms add copies their source directly into your project — full
customizability, no black-box package to work around, at the cost of picking
up fixes manually. This is the same approach shadcn/ui
popularized for UI components.
motionscript.json
ms init writes a motionscript.json file to your project root and wires a
@/components/* path alias into tsconfig.json (for editor/typechecker
support — Vite resolves it directly from motionscript.json, not from
tsconfig):
ms init
{
"$schema": "https://motionscript.dev/schema/motionscript.json",
"project": "src/project.ts",
"aliases": { "components": "@/components" },
"paths": { "components": "src/components" }
}
ms add runs ms init for you the first time if motionscript.json doesn't
exist yet, so you rarely need to call it directly.
motionscript.lock.json
Alongside it, ms add writes a lockfile recording — for every file it installs
— which registry and version it came from, and a hash of the bytes written.
That one record answers the question copying a component otherwise makes
unanswerable: is this file yours or ours? Re-hashing the file on disk says
exactly whether you have edited it since, which is what ms list and ms diff
report and what stops ms add asking about files you never touched.
Commit it, for the reason you commit any lockfile. It is generated, so it is
kept separate from motionscript.json, which you write by hand.
ms add <name>
Copies a component's source into <paths.components>/<name>/ (by default
src/components/<name>/) and installs the npm packages it depends on:
ms add code
ms add code latex --yes # add more than one, skip prompts
Run ms add with no name for an interactive picker over everything the
default registry offers. Useful flags:
| Flag | Effect |
|---|---|
--yes | Skip prompts, accept defaults, don't ask before overwriting |
--overwrite | Overwrite existing files without asking |
--skip-install | Don't install npm dependencies after adding |
--registry <url> | Override the default registry (a URL or a local path) |
The copied files are yours — import them from the alias ms init set up
rather than from @motionscript/sdk:
import { Code, lines, word } from '@/components/code';
A component is a node type, and a document can only name a node type the
engine has been given. Add it to the nodes your project entry exports, and
hand those to the engine:
import { Code } from '@/components/code';
export const nodes = [Code];
From then on, any document opened on that engine can add a "type": "code"
node.
A component that needs more than one file lands in a folder of its own
(src/components/line-chart/), and its files keep importing each other. What
they never copy is the component's engine — the algorithms behind it stay a
real npm dependency, so a fix to those reaches you without a re-add, and so
there is only ever one copy of the classes the renderer identifies by type.
ms list and ms diff
Copying source means fixes arrive manually. These two are how you find out what is waiting.
ms list # what is installed, and whether you have edited it
ms list --check # ...and whether a newer version exists upstream
ms diff # what changed upstream, and on which side
ms diff line-chart # just this one
ms diff marks each file: changed upstream, changed locally, or changed on
both sides — the only state that needs you to decide something. Nothing is
ever taken automatically; an update is applied by adding the component again,
which prompts before overwriting anything you have changed.
Registries
A registry serves two endpoints: an index (registry.json, listing available
components) and one JSON file per component (<name>.json, the source files
plus npm dependencies). The default, unnamespaced registry is built into the
CLI (https://motionscript.dev/r) — that's what ms add code resolves
against.
Additional or private registries go under motionscript.json's registries
field, keyed by @namespace:
{
"registries": {
"@acme": {
"url": "https://registry.acme.dev/{name}.json",
"headers": { "Authorization": "Bearer ${ACME_TOKEN}" }
}
}
}
ms add @acme/some-component looks up the @acme entry, substitutes
some-component into the {name} placeholder, and expands ${VAR} in
headers (and params) against your environment — so a token like
ACME_TOKEN above never has to be written into the file. A registry url
can also be a local filesystem path, for testing a registry before it's
deployed anywhere.