Skip to content

Installation

bash
npm install @studiometa/js-toolkit@next

The 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:

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

Utilities live on /utils:

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

Test helpers live on /test:

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

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

html
<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:

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