Five entry points. The three plugin subpaths run in Node during development only, and each exports one function under the same name.
| subpath | exports | runs in |
|---|---|---|
nanostores-devtools |
connectDevtools, trackStores, untrack |
browser |
nanostores-devtools/vite |
nanostoresDevtools |
Node, dev only |
nanostores-devtools/webpack |
nanostoresDevtools |
Node, dev only |
nanostores-devtools/rspack |
nanostoresDevtools |
Node, dev only |
nanostores-devtools/runtime |
internal, injected by the plugin | browser |
nanostores-devtools/runtime is internal. The plugin injects an import of it into your modules during development, so it appears in your module graph. Never import it yourself.
connectDevtools(options?)
Opens the bridge and returns a DevtoolsHandle. Never throws, never needs await, and safe to call during server-side rendering. Called a second time while connected, it warns once and hands back the first handle, which is what makes a hot reload safe.
const handle = connectDevtools({ name: "my-app" });
if (!handle.connected) {
console.info("No Redux DevTools extension on this page.");
}
| option | default | what it does |
|---|---|---|
name |
"nanostores" |
the entry in the extension’s dropdown |
serializers |
[] |
your own rules for converting values, checked before ours |
platformSerializers |
true |
our own rules for Headers, FormData and other platform classes |
trace |
true |
capture a stack at each direct write |
traceLimit |
10 |
how many stack frames to capture |
maxAge |
500 |
how many rows the extension keeps |
lifecycleEvents |
true |
draw the mount, unmount, register, unregister and hot reload rows |
throttle |
[] |
hold these stores to one row a second |
autoThrottle |
10 |
writes a second above which a store is throttled |
maxValueDepth |
5 |
levels drawn below a class instance |
maxValueMembers |
100 |
members drawn per shape below a class instance |
autoThrottle is the one default that drops rows: a store above 10 writes a second is held to one row a second for the rest of the session, and we warn once, naming the store. Pass your own threshold, or false to keep every row. throttle takes names as the tree writes them, "src/model.ts/$remaining", or a function over them.
A serializer draws a value the panel cannot read on its own, such as a MouseEvent, whose every field sits behind a getter. A rule is { match, convert }, and the first match in the array wins.
The plugin reads four comments next to a store, where a rename cannot lose them: // @nanostores-devtools:ignore keeps every store the statement below it makes out of the devtools, // @nanostores-devtools:throttle and // @nanostores-devtools:no-throttle set its row rate, and // @nanostores-devtools:max-members 25 caps how much of one binding the scan walks.
handle.disconnect() closes the bridge and lets the next connectDevtools() open a fresh one. It is there for a page that tears its app down and builds another one.
What each connectDevtools option costs has the full rules for the options, the serializers and the four comments.
trackStores(group, stores)
Registers stores the plugin cannot reach, under a top-level key you name.
group- the home those stores sit under. Name it after a file to land on the node the plugin already uses for that file.stores- an object ofname -> store. The key is the name the tree draws.
A second registration for the same group/name replaces the store held there, with no warning. A clash here is almost always a hot reload, and we cannot tell the two apart.
untrack(group)
Removes a group and every store in it. Draws one unregister row.
nanostoresDevtools(options?)
The bundler plugin. Exported from /vite, /webpack and /rspack, and the same function behind all three. It reads script files only: .js, .ts and the rest of that family, and nothing under node_modules.
| option | default | what it does |
|---|---|---|
fileKey |
the home unchanged | rewrites the path shown as a store’s home |
adoptFactories |
true |
wrap named calls we do not recognise: true or false |
storeTypes |
the packages we ship | which kind a package’s export makes |
maxDepth |
10 |
steps into a top-level binding the scan walks, 1 or more |
projectRoot |
Vite’s own workspace root | what a file outside the Vite root is measured from, Vite only |
adoptFactories catches a codebase that wraps store creation: a call whose result is stored under a name is registered whatever function it names, so const theme = persistentAtom("theme", "dark") reaches the tree from a file that never imports "nanostores". storeTypes gives that store its kind, and we ship a map of the packages from the Smart Stores list in the nanostores README. maxDepth counts a property, an index and a Map key alike. projectRoot is a Vite option; webpack and Rspack always climb up from context instead.
What each plugin option costs has what each one buys and what it costs.
Types
type DevtoolsHandle = {
readonly connected: boolean; // false when no extension was on the page
disconnect: () => void;
};
type Serializer = {
match: (value: unknown) => boolean;
convert: (value: unknown) => unknown;
};
// the shape a `throttle` rule is handed, once per registration and never per write
type ThrottleTarget = {
readonly home: string;
readonly name: string; // the name the tree draws, without the file and line a clash adds
readonly type: "atom" | "map" | "deepMap" | "computed" | "batched" | "unknown";
};
type ThrottleOption = readonly string[] | ((store: ThrottleTarget) => boolean);
// what `/webpack` and `/rspack` take. `/vite` takes the same four plus `projectRoot?: string`
type BundlerPluginOptions = {
fileKey?: (path: string) => string;
adoptFactories?: boolean;
// package name, then export name, then the kind that export makes
storeTypes?: Readonly<Record<string, Readonly<Record<string, ThrottleTarget["type"]>>>>;
maxDepth?: number;
};
DevtoolsOptions is exported too, and holds the eleven keys of the table above.