Installation
npm install @studiometa/js-toolkit@nextThe next tag is not optional yet
4.0 is still a prerelease, so it publishes to the next dist-tag. Plain npm install @studiometa/js-toolkit installs 3.x, which is a different framework — the tag is what selects this one. next becomes latest when 4.0.0 ships.
Importing
The root barrel carries everything a page normally needs:
import { Base, registerComponent } from '@studiometa/js-toolkit';Utilities live on /utils:
import { clamp, damp } from '@studiometa/js-toolkit/utils';Test helpers live on /test:
import { mount, settle } from '@studiometa/js-toolkit/test';One subpath per export
Every public export also has a subpath of its own — 197 of them. The barrel is convenient; the subpath is precise:
import { Base } from '@studiometa/js-toolkit/Base';
import { useScroll } from '@studiometa/js-toolkit/useScroll';
import { clamp } from '@studiometa/js-toolkit/utils/clamp';Which one to reach for depends on how the code is delivered:
- With a bundler, prefer the barrel. Tree-shaking removes what you do not import, and the barrel keeps the import lines short.
- From an ESM CDN, with no build step, prefer the subpath. There is no bundler to shake the graph, so a barrel import downloads the barrel's whole module graph before the first line of your code runs.
The subpath layout is what keeps a dependency contained. morphdom is reachable through swap() only, so a page that never swaps content never downloads it.
No build step
v4 needs no compiler. Every decorator is sugar over a function that works without it, so a page loaded from an ESM CDN keeps the whole framework:
<script type="module">
import { Base } from 'https://esm.sh/@studiometa/js-toolkit@next/Base';
import { registerComponent } from 'https://esm.sh/@studiometa/js-toolkit@next/registerComponent';
class Counter extends Base {
static config = { name: 'Counter', refs: ['output'] };
count = 0;
onClick() {
this.count += 1;
this.$refs.output.textContent = String(this.count);
}
}
registerComponent(Counter);
</script>With a build step
Vite
Nothing to configure for the framework itself. Vite 8 transforms TypeScript with Oxc, which passes decorators through untouched — so if you use the decorators, add a transform that compiles them:
// vite.config.js
import { defineConfig } from 'vite';
import swc from '@rollup/plugin-swc';
export default defineConfig({
plugins: [
{
...swc({ swc: { jsc: { target: 'es2022', transform: { decoratorVersion: '2023-11' } } } }),
// Compile only the files that contain a decorator.
enforce: 'pre',
},
],
});TypeScript
Stage-3 decorators need no flag on TypeScript 5. Turn experimentalDecorators off:
{
"compilerOptions": {
"target": "ES2022",
"module": "Preserve",
"moduleResolution": "bundler",
"experimentalDecorators": false,
"useDefineForClassFields": true,
"strict": true
}
}See TypeScript for how to type a component's refs, options and events.
Autoloading
A page that declares its components in the markup does not have to import them all up front. Map each data-component token to a dynamic import and the registry downloads a chunk when an element needs it:
import { registerManifest } from '@studiometa/js-toolkit';
registerManifest({
Accordion: () => import('./components/Accordion.js'),
Map: { load: () => import('./components/Map.js'), mountStrategy: 'visible' },
});See Autoloading.