Skip to content

Installation

bash
npm install @studiometa/js-toolkit

The package name in this documentation

v4 ships as @studiometa/js-toolkit 4.0. While it is in development it lives in the monorepo as @studiometa/js-toolkit-v4, and the code samples on this site import that name so their types resolve against the package that exists today. Read every @studiometa/js-toolkit-v4 as @studiometa/js-toolkit in your own project.

Importing

The root barrel carries everything a page normally needs:

js
import { 
Base
,
registerComponent
} from '@studiometa/js-toolkit-v4';

Utilities live on /utils:

js
import { 
clamp
,
damp
} from '@studiometa/js-toolkit-v4/utils';

Test helpers live on /test:

js
import { 
mount
,
settle
} from '@studiometa/js-toolkit-v4/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:

js
import { 
Base
} from '@studiometa/js-toolkit-v4/Base';
import {
useScroll
} from '@studiometa/js-toolkit-v4/useScroll';
import {
clamp
} from '@studiometa/js-toolkit-v4/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:

html
<script type="module">
  import { Base } from 'https://esm.sh/@studiometa/js-toolkit@4/Base';
  import { registerComponent } from 'https://esm.sh/@studiometa/js-toolkit@4/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:

js
// 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:

json
{
  "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:

js
import { registerManifest } from '@studiometa/js-toolkit';

registerManifest({
  Accordion: () => import('./components/Accordion.js'),
  Map: { load: () => import('./components/Map.js'), mountStrategy: 'visible' },
});

See Autoloading.

MIT Licensed