How to create dynamic registries in jsrepo.
The final frontier of reusable code.
If you have ever used shadcn to install components from v0 you are familiar with dynamic registries. They allow the server to serve different files to the CLI based on the route that was requested.
This is useful if your product generates code that you want to distribute through the jsrepo CLI.
To understand how to effectively create a dynamic registry in jsrepo you need to have a deeper understanding of the way that the manifest file works.
The jsrepo-manifest.json
includes all the blocks in your registry and their locations relative to the manifest file.
This means that you need to ensure that the files needed for your registry are served from the path that is inferred from the jsrepo-manifest.json
.
For example take this manifest entry for a button component:
{
name: 'ui',
blocks: [
{
name: 'button',
directory: 'src/ui/button',
category: 'ui',
tests: false,
subdirectory: true,
list: true,
files: ['button.svelte', 'index.ts'],
localDependencies: ['utils/utils'],
dependencies: [],
devDependencies: [
'@lucide/svelte@^0.475.0',
'bits-ui@1.3.2',
'tailwind-variants@^0.3.1'
],
_imports_: {
'$lib/utils/utils.js': '{{utils/utils}}.js'
}
}
]
},
The directory that the files for button live in is src/ui/button
because the directory
prop is set to src/ui/button
.
Because of this jsrepo expects the files button.svelte
and index.ts
to exist at src/ui/button/button.svelte
and src/ui/button/index.ts
respectively.
Any remote or local dependencies that need to be resolved should also be added to each block. In the case of the button component shown above. It has 3 remote dependencies and 1 local dependency.
Remote dependencies exist under dependencies
or devDependencies
and give the name of the package optionally followed by the version.
Local dependencies exist under localDependencies
and reference other blocks in the registry and they are listed by <category>/<block>
. In this case utils/utils
is referring to the utils
block in the utils
category.
Local dependencies should also come with corresponding mappings in _imports_
. The _imports_
key maps literal import statements to a template that is replaced before adding blocks to your project. This allows users to put blocks anywhere in their project without breaking their code.
If you want dynamic blocks to depend on other blocks you will need to be able to add keys to _imports_
to prevent breaking their code. Here is an example of how you might resolve the utils
import from the button component above:
// here we just replace everything except for the extension
// so it turns into {{utils/utils}}.js
import { cn } from '$lib/utils/utils.js';
// turns into {{utils/utils}}
import { cn } from '$lib/utils/utils';
// turns into {{utils/utils}}
import { cn } from '../utils/utils';
// if utils was a subdirectory with a file index.js
// turns into {{utils/utils}}/index.js
import { cn } from '../utils/utils/index.js';
Essentially the path to the local dependency is insignificant but the file or extension after it must be included.
Type definitions for the manifest can be acquired via the JS API:
import type { Manifest } from 'jsrepo';
With all of this considered you can pretty easily create a dynamic jsrepo registry in a few minutes and start distributing components through the jsrepo CLI.