Guide d'utilisation
Le README explique pourquoi speedster-js est fait comme il est.
Ce guide explique comment s'en servir. Pour essayer sans rien installer, le
bac à sable compile et exécute un
composant .spd dans le navigateur, avec six exemples et une URL partageable.
| Mise en place | Un projet qui démarre, ci-dessous |
| Composants | Le langage .spd, référence complète |
| Réactivité | Ce qui se met à jour, et pourquoi |
| Composition | Propriétés, fragments, contenu transmis |
| Formulaires | bind: sous toutes ses formes |
| Mouvement | Transitions et actions |
| Internationalisation | Messages, langues, formats |
| Accessibilité | Ce qui est vérifié au build |
| Routeur | Routes de fichiers, gabarits, fournisseurs |
| Rendu serveur | SSR, hydratation, îlots |
| Outillage | Build, types, éditeur |
| Rapport de build | Poids, problèmes, accessibilité — à la demande |
Mise en place
speedster-js s'appuie sur Bun pour le build. Rien n'y est
spécifique au-delà du plugin : le compilateur produit du TypeScript ordinaire, et
transform est exporté séparément pour brancher un autre empaqueteur.
Le plus court chemin :
bunx @ajustor/speedster-cli init mon-app && cd mon-app && bun install && bun run dev
speedster init écrit tout ce que les étapes 1 à 6 décrivent à la main, y compris
les réglages qu'on oublie. Le reste de cette page explique ce qu'il produit.
0. Les paquets
Les paquets sont publiés sur npm, sous le scope @ajustor. Aucun jeton ni
.npmrc n'est nécessaire.
Sans passer par npm. Pour développer le framework lui-même, ou consommer votre arbre de travail sans attendre une publication,
bun linkdans chaque paquet puisbun link @ajustor/speedsterdans le projet consommateur suffit. Ni jeton ni tag.
1. Les dépendances
bun add @ajustor/speedster
bun add -d @types/bun typescript
Un seul nom à installer et à mettre à jour. @ajustor/speedster porte le runtime et
l'outillage, et bun lie aussi les binaires de ses dépendances : speedster,
speedster-check et speedster-i18n sont donc disponibles sans être installés à la
main.
Ce paquet unique ne change rien au poids livré : bun run size compare les deux
chemins d'import et les trouve identiques à l'octet près. Ce qui grossit, c'est
node_modules, puisque l'outillage vient avec.
Les paquets détaillés restent publiés, si vous préférez ne prendre que le nécessaire :
bun add @ajustor/speedster-runtime
bun add -d @ajustor/speedster-plugin @ajustor/speedster-check @ajustor/speedster-cli
@ajustor/speedster-runtime est la seule dépendance d'exécution. Le
compilateur, le plugin, le vérificateur et l'internationalisation tournent au
build et ne partent jamais dans le navigateur.
1 bis. L'éditeur
La coloration, la complétion et les diagnostics des .spd viennent d'une extension
VS Code, distribuée en .vsix — le Marketplace n'a pas de mode privé. Elle est
attachée à chaque release du dépôt :
gh release download --repo Ajustor/speedster-js --pattern '*.vsix'
code --install-extension speedster-js-*.vsix
Pour un autre éditeur, le serveur de langage s'utilise directement en LSP :
speedster-language-server --stdio. Voir Outillage.
2. tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noEmit": true,
"skipLibCheck": true,
"types": ["@ajustor/speedster/spd", "bun"],
"paths": { "$messages": ["./src/lang/index.ts"] }
},
"include": ["src/**/*.ts", "src/**/*.spd"]
}
Ce jeu d'options est exactement celui que le service de langage utilise par défaut, et c'est la raison de le recopier tel quel : un projet plus permissif que l'éditeur verrait du rouge sur du code qui compile, un projet plus strict verrait du vert sur du code qui ne compile pas.
types: ["bun"] suppose @types/bun en dépendance de développement — sans le
paquet, tsc s'arrête sur TS2688.
La ligne types fait accepter import Carte from './Carte.spd' par tsc. Elle ne
vérifie rien : le typage exact des propriétés vient de speedster-check, décrit
dans Outillage.
La ligne paths est celle du catalogue de messages. Déclarez-la même sans
internationalisation : c'est l'alias que le compilateur cherche par défaut, et
l'ajouter après coup est l'oubli classique — t perd son type dans l'éditeur et le
build échoue sur Could not resolve: "$messages". Voir
Internationalisation.
3. Le build
// build.ts
import { speedster } from '@ajustor/speedster/plugin'
const built = await Bun.build({
entrypoints: ['src/main.ts'],
outdir: 'dist',
target: 'browser',
minify: true,
define: { 'process.env.NODE_ENV': '"production"' },
plugins: [speedster()],
})
if (!built.success) throw new AggregateError(built.logs, 'build impossible')
define n'est pas décoratif : c'est lui qui retire les vérifications de
développement du runtime. Sans cette ligne, elles partent dans le bundle livré.
4. La page
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mon application</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/main.js"></script>
</body>
</html>
5. Le point d'entrée
// src/main.ts
import { mount } from '@ajustor/speedster'
import App from './App.spd'
mount(App, { target: document.getElementById('app')! })
mount retourne une fonction de démontage, qui retire exactement ce que le
composant occupait — le contenu préexistant de la cible est préservé.
6. Le premier composant
<!-- src/App.spd -->
<script lang="ts">
let compteur = 0
</script>
<button type="button" on:click={() => compteur++}>
Cliqué {compteur} fois
</button>
<style>
button { font: inherit }
</style>
bun run build.ts
C'est tout. Aucune déclaration de réactivité : compteur est réaffecté, donc c'est
un état ; le texte le lit, donc il se met à jour. La suite est dans
Composants et Réactivité.
Développer
speedster init écrit un dev.ts qui construit à la demande, surveille src/ et
recharge la page à chaque sauvegarde.
Ce n'est pas du remplacement à chaud, et la nuance compte. Le framework fournit l'enveloppe — un composant garde la même fonction par identifiant, et ses instances vivantes sont remontées en place — mais aucun transport : rien dans le runtime ni dans le plugin ne surveille de fichier ni ne prévient le navigateur. Le serveur du modèle apporte le maillon manquant sous sa forme honnête : un rechargement complet, donc l'état est perdu. Un vrai remplacement à chaud demanderait de servir chaque module séparément, pour que seul celui qui a changé soit réévalué — ce que le build en un seul paquet ne permet pas.
// dev.ts — un serveur de développement minimal
import { speedster } from '@ajustor/speedster-plugin'
Bun.serve({
port: 3000,
development: true,
async fetch(request) {
const url = new URL(request.url)
if (url.pathname === '/main.js') {
const built = await Bun.build({
entrypoints: ['src/main.ts'],
target: 'browser',
plugins: [speedster()],
})
return new Response(built.outputs[0]!.stream(), {
headers: { 'content-type': 'text/javascript' },
})
}
return new Response(Bun.file('index.html'))
},
})
Vérifier
bunx speedster check src # types + accessibilité
bunx speedster check src --a11y error # accessibilité bloquante
À lancer avant de livrer, et en intégration continue : le code de sortie vaut 1
s'il reste une erreur.
Exécuter un .spd directement
Pour un test ou un script, bunfig.toml permet d'importer un composant sans passer
par un build :
[test]
preload = ["@ajustor/speedster-plugin/preload"]
Les pièges du début
Trois choses surprennent, et une seule fois :
- La réactivité se fonde sur l'affectation.
taches.push(x)ne met rien à jour ;taches = [...taches, x]si. Le compilateur le signale au build — voir Réactivité. - Un composant n'est pas un élément.
on:clicksur<Carte />est refusé : transmettez un gestionnaire en propriété. Un composant peut n'avoir aucune racine, ou plusieurs. - Les avertissements d'accessibilité sont réels. Ils ne cassent rien par
défaut, mais chacun décrit un mur pour quelqu'un.
<!-- a11y-ignore-next -->existe pour les cas que la règle ne pouvait pas savoir.