Packaging
Vous pouvez utiliser SvelteKit pour construire des applications ainsi que des librairies de
composants, grâce au paquet @sveltejs/package
(npx sv create
possède une option vous permettant
de mettre cela en place pour vous).
Lorsque vous créez une application, le contenu de src/routes
est la partie qui est exposée au
public ; src/lib
contient la librairie interne de votre application.
Une librairie de composants a exactement la même structure qu’une application SvelteKit, à
l’exception du fait que src/lib
est la partie exposée au public, et que votre fichier
package.json
est utilisée pour publier le paquet. src/routes
peut ainsi devenir un site de
documentation ou une démo accompagnant la librairie, ou bien juste un bac à sable dont vous vous
servez lors de vos développements.
Éxecuter la commande svelte-package
importée depuis @sveltejs/package
va prendre le contenu de
src/lib
et générer un dossier dist
(qui peut être configuré) contenant les choses
suivantes :
- Tous les fichiers de
src/lib
. Les composants Svelte seront pré-processés, les fichiers TypeScript seront transpilés vers JavaScript. - Les définitions de type (les fichiers
.d.ts
) sont générées pour les fichiers Svelte, JavaScript et TypeScript. Pour cela, vous avez besoin d’installertypescript >= 4.0.0
. Les définitions de type sont placées à côté de leur implémentation, les fichiers.d.ts
écrit à la main sont copiés tels quels. Vous pouvez désactiver la génération de types, mais nous vous le déconseillons fortement — les personnes utilisant votre librairie pourrait utiliser TypeScript, raison pour laquelle ils pourraient avoir besoin de ces fichiers de définition de type.
La version 1 de
@sveltejs/package
générait un fichierpackage.json
. Ce n’est plus le cas, et désormais le fichierpackage.json
de votre projet sera validé et utilisé. Si vous utilisez toujours la version 1, voir cette PR pour obtenir des instructions de migration.
Anatomie d’un package.json
Puisque désormais vous construisez une librairie à destination du public, le contenu de votre
fichier package.json
va prendre une place plus importante. À travers ce fichier, vous configurez
les points d’entrée de votre paquet, les fichiers que publiez sur npm, et quelles dépendances votre
librairie possède. Analysons les champs les plus importants de ce fichier un par un.
name
C’est le nom de votre paquet. Il sera possible d’installer votre paquet en utilisant ce nom, et
votre paquet sera visible à l’adresse https://npmjs.com/package/<name>
.
{
"name": "votre-librairie"
}
Apprenez-en plus ici.
license
Chaque paquet devrait avoir un champ de licence afin d’informer les gens de la manière dont ils
peuvent s’en servir. La licence MIT
est une licence très populaire dont les termes de distribution
et de réutilisation sont très permissifs.
{
"license": "MIT"
}
Apprenez-en plus ici. Notez
que vous devriez également inclure un fichier LICENSE
dans votre paquet.
files
Ce champ précise à npm quels fichiers doivent être empaquetés et envoyés sur npm. Il doit contenier
votre dossier d’output (dist
par défaut). Vos fichiers package.json
, README
et LICENSE
seront toujours inclus, vous n’avez donc pas besoin de les préciser.
{
"files": ["dist"]
}
Pour exclure les fichiers non nécessaires (comme les fichiers de tests unitaires, ou les modules qui
ne sont importés que dans src/routes
etc.), vous pouvez les ajouter au fichier .npmignore
. Ceci
va permettre de générer des paquets plus petits et plus rapides à installer.
Apprenez-en plus ici.
exports
Le champ exports
contient les points d’entrée de votre paquet. Si vous mettez en place un nouveau
projet de librairie via npx sv create
, ce champ est défini comme étant un seul export, la racine
de votre paquet :
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"svelte": "./dist/index.js"
}
}
}
Ceci précise aux outillages et notamment aux bundlers que votre paquet n’a qu’un seul point d’entrée, la racine, et que tout devrait être importé depuis ce point d’entrée, comme ceci :
import { import QuelqueChose
QuelqueChose } from 'votre-librairie';
Les clés types
et svelte
sont des conditions
d’export. Elles précisent aux outillages
quels fichiers importer lorsqu’ils traitent l’import votre-librairie
:
- TypeScript voit la condition
types
et cherche le fichier de définitions. Si vous ne publiez pas de définitions de types, vous pouvez omettre cette condition. - Les outillages conscients de Svelte vont voir la condition
svelte
et comprendre qu’il s’agit d’une librairie de composants Svelte. Si vous publiez une librairie qui n’exporte aucune composant Svelte et qui pourrait tout aussi bien fonctionner dans des projets non-Svelte (par exemple une librairie de stores Svelte), vous pouvez remplacer cette condition pardefault
.
Les versions précédentes de
@svelte/package
avaient également un exportpackage.json
. Ce n’est plus le cas dans la mise en place par défaut car tous les outillages peuvent maintenant s’accomoder d’un fichierpackage.json
qui ne serait pas explicitement exporté.
Vous pouvez ajuster exports
à votre convenance et fournir plus de points d’entrée. Par exemple, si
à la place d’un fichier src/lib/index.js
qui ré-exporterait des composants vous souhaitez exposer
un composant src/lib/Foo.svelte
directement, vous pourriez créer le dictionnaire d’exports
suivant...
{
"exports": {
"./Foo.svelte": {
"types": "./dist/Foo.svelte.d.ts",
"svelte": "./dist/Foo.svelte"
}
}
}
... et ce qui viendrait consommer votre librairie pourrait importer le composant comme ceci :
import type Foo = SvelteComponent<Record<string, any>, any, any>
const Foo: LegacyComponentType
Foo from 'votre-librarie/Foo.svelte';
Ayez conscience que faire cela nécessite plus d’attention si vous fournissez les définitions de type. Apprenez-en plus sur cet inconvénient ici.
En général, chaque clé du dictionnaire d’exports est le chemin que l’utilisateur ou utilisatrice devra utiliser pour importer quelque chose de votre paquet, et la valeur est le chemin vers le fichier qui sera importé ou un dictionnaire de conditions d’exports qui contient ces chemins de fichier.
In general, each key of the exports map is the path the user will have to use to import something from your package, and the value is the path to the file that will be imported or a map of export conditions which in turn contains these file paths.
Apprenez-en plus sur les exports
ici.
svelte
Ce champ est un champ historique qui permettait aux outillages de reconnaître les librairies de
composants Svelte. Il n’est plus nécessaire lorsque vous utilisez la condition
d’export svelte
, mais pour des raisons de rétro-compatibilité
avec des outillages anciens qui n’auraient pas encore conscience des conditions d’export, il est
pertinent de le garder accessible. Il devrait pointer vers votre point d’entrée racine.
{
"svelte": "./dist/index.js"
}
sideEffects
Le champ sideEffects
du fichier package.json
est utilisé par les bundlers pour déterminer si un
module peut contenir du code ayant des effets de bord. Un module est considéré comme ayant des
effets de bord s’il fait des changements observables depuis des scripts extérieurs à ce module
lorsqu’il est importé. Par exemple, des effets de bord peuvent être la modification de variables
globales ou de prototypes d’objets standard de JavaScript. Puisqu’un effet de bord peut
potentiellement affecter le comportement d’autres parties de l’application, ces fichiers/modules
seront inclus dans le bundle final que leurs exports soient utilisés ou non dans l’application. Il
s’agit d’une bonne pratique pour éviter les effets de bord dans votre code.
Définir le champ sideEffects
dans votre package.json
peut aider le bundler à être plus agressif
dans l’élimination d’exports non utilisés qui ne seraient donc pas intégrés à votre bundle final, un
processus appelé “tree-shaking”. Ceci produit des bundles plus petits et plus efficaces. Les
bundlers choisissent de gérer les effets de bord pointés par sideEffects
de différentes manières.
Bien que cela ne soit pas requis par Vite, nous recommandons que les librairies déclarent que tous
les fichiers CSS ont des effets de bord afin que votre librairie soit compatible avec
webpack. Voici la
configuration qui est définie lors de la mise en place de nouveaux projets :
{
"sideEffects": ["**/*.css"]
}
Si les scripts de votre librairie ont des effets de bord, assurez-vous de mettre à jour le champ
sideEffects
. Tous les scripts sont marqués comme n’ayant pas d’effets de bord par défaut dans un projet nouvellement créé. Si un fichier avec des effets de bord est marqué à tort comme n’ayant pas d’effet de bord, cela peut rendre inutilisables certaines fonctionnalités.
Si votre paquet possède des fichiers ayant des effets de bord, vous pouvez les préciser dans un tableau :
{
"sideEffects": [
"**/*.css",
"./dist/sideEffectfulFile.js"
]
}
Ceci va considérer comme ayant des effets de bord uniquement les fichiers déclarés dans ce champ.
TypeScript
Vous devriez fournir les définitions de type de votre librairie même si vous n’utilisez pas
TypeScript, afin que les personnes qui utilisent TypeScript aient une autocomplétion correcte
lorsqu’elles utilisent votre librairie. @sveltejs/package
rend le processus de génération de types
globalement opaque. Par défaut, lorsque vous empaquetez votre librairie, les définitions de type
sont auto-générées pour les fichiers JavaScript, TypeScript et Svelte. Tout ce que vous avez à faire
est vous assurer que la condition types
dans le dictionnaire
d’exports pointe vers les fichiers appropriés. Lorsque vous
initialisez un projet de librairie via npx sv create
, ceci est automatiquement fait pour
l’export racine.
Si toutefois vous avez d’autres exports que l’export racine — par exemple si vous souhaitez fournir
un import votre-librairie/foo
— vous devez vous charger de fournir correctement les définitions de
type. Malheureusement, par défaut TypeScript ne va pas résoudre la condition types
pour un
export tel que { "./foo": { "types": "./dist/foo.d.ts", ... }}
. À la place, il va chercher un
fichier foo.d.ts
relatif à la racine de votre librairie (c-à-d votre-librairie/foo.d.ts
au lieu
de votre-librairie/dist/foo.d.ts
). Pour régler ce problème, vous avez deux options :
La première option est d’obliger les gens utilisant votre librairie à définir l’option
moduleResolution
de leur fichier tsconfig.json
(ou jsconfig.json
) à la valeur bundler
(disponible depuis TypeScript 5, ce qui est la meilleure option et celle recommandée pour le futur),
node16
ou nodenext
. Ceci impose à TypeScript de se servir du dictionnaire d’exports pour
résoudre correctement les types.
La deuxième option est d’"abuser” de la fonctionnalité typesVersions
de TypeScript pour connecter
les types. C’est un champ du fichier package.json
que TypeScript utilise pour vérifier différentes
définitions de types dépendant de la version de TypeScript, et qui contient également un
dictionnaire de chemins permettant cela. Nous utilisons cette fonctionnalité de dictionnaire pour
obtenir le fonctionnement que nous souhaitons. Pour l’export foo
mentionné plus haut, le champ
typesVersions
correspondant ressemble à ceci :
{
"exports": {
"./foo": {
"types": "./dist/foo.d.ts",
"svelte": "./dist/foo.js"
}
},
"typesVersions": {
">4.0": {
"foo": ["./dist/foo.d.ts"]
}
}
}
La clé >4.0
précise à TypeScript de vérifier dans le dictionnaire fourni que la version de
TypeScript utilisée est supérieure à 4 (ce qui en pratique devrait toujours être vrai). Ce
dictionnaire dit à TypeScript que les définitions de type pour votre-librairie/foo
se trouvent
dans ./dist/foo.d.ts
, ce qui reproduit la condition exports
. Vous avez également le joker *
à
votre disposition pour rendre disponibles plusieurs définitions de types à la fois sans vous
répéter. Notez que si vous choisissez d’utiliser typesVersions
, vous devez y déclarer tous les
imports de type, même celui concernant l’import racine (défini en tant que "index.d.ts": [..]
).
Vous pouvez en apprendre plus sur cette fonctionnalité ici.
Bonnes pratiques
Vous devriez éviter d’utiliser des modules spécifiques à SvelteKit comme $app/environment
dans vos
paquets, à moins que n’ayez l’intention qu’ils ne soient utilisables que par d’autres projets
SvelteKit. Par ex. plutôt que d’utiliser import { browser } from '$app/environment'
, vous pourriez
utiliser import { BROWSER } from 'esm-env'
(voir la documentation de
esm-env
). Vous pourriez également vouloir fournir des
choses comme l’URL courante ou une action de navigation en tant que prop plutôt que se servir
directement de $app/state
, $app/navigation
, etc. Écrire votre application avec cette philosophie
plus générique va également la rendre plus simple à utiliser lors de tests, de démos, et ainsi de
suite.
Assurez-vous d’ajouter des aliases via svelte.config.js
(et non
vite.config.js
ou tsconfig.json
) afin qu’ils soient traités par svelte-package
.
Vous devriez accorder un soin particulier au fait de décider si les changements que vous faites sur
votre paquet sont une résolution de bug, un nouvelle fonctionnalité, ou un changement majeur pouvant
casser des choses (breaking change), et mettre à jour la version du paquet de manière appropriée.
Notez que si vous supprimez de votre librairie tout chemin de exports
ou toute condition export
y figurant, cette modification devrait être considérée comme un breaking change.
{
"exports": {
".": {
"types": "./dist/index.d.ts",
// changer `svelte` en `default` est un breaking change :
"svelte": "./dist/index.js"
"default": "./dist/index.js"
},
// supprimer cette ligne est un breaking change :
"./foo": {
"types": "./dist/foo.d.ts",
"svelte": "./dist/foo.js",
"default": "./dist/foo.js"
},
// l'ajout de cette ligne ne pose pas de souci :
"./bar": {
"types": "./dist/bar.d.ts",
"svelte": "./dist/bar.js",
"default": "./dist/bar.js"
}
}
}
Source maps
Vous pouvez créer des dictionnaires de déclarations (des fichiers d.ts.map
) en définissant
"declarationMap": true
dans votre fichier tsconfig.json
. Ceci va permettre aux éditeurs comme VS
Code de trouver le fichier .ts
ou .svelte
originel lorsque vous utilisez des fonctionnalités
comme Aller à la défininion. Cela signifie que vous avez aussi besoin de publier vos fichiers
sources en plus de votre dossier de distribution de sorte que le chemin relatif au sein des fichiers
de déclaration mène à un fichier sur le disque. En supposant que vous ayez tout le code de votre
librairie dans le dossier src/lib
comme suggéré par le CLI Svelte, ceci peut se faire simplement
en ajoutant src/lib
à files
dans votre fichier package.json
:
{
"files": [
"dist",
"!dist/**/*.test.*",
"!dist/**/*.spec.*",
"src/lib",
"!src/lib/**/*.test.*",
"!src/lib/**/*.spec.*"
]
}
Options
svelte-package
accepte les options suivantes :
-w
/--watch
— surveille les changements sur les fichiers desrc/lib
et re-construit le paquet-i
/--input
— le dossier d’entrée qui contient tous les fichiers du paquet. Vaut par défautsrc/lib
-o
/--output
— le dossier de sortie où les fichiers traités sont écrits. Le champexports
de votre fichierpackage.json
devrait pointer vers des fichiers qui s’y trouvent, et le tableaufiles
devrait inclure ce dossier. Vaut par défautdist
-t
/--types
— si oui ou non créer des définitions de type (des fichiers.d.ts
). Nous recommandons fortement de le faire car cela améliore globalement la qualité de l’écosytème. Vaut par défauttrue
--tsconfig
— le chemin vers un fichier tsconfig ou jsconfig. Lorsque non fourni,svelte-package
va chercher un fichier tsconfig/jsconfig à l’étage supérieur du workspace actuel.
Publier
Pour publier le paquet généré :
npm publish
Inconvénients
Tous les imports de fichiers relatifs doivent être précisés entièrement, pour adhérer à l’algorithme
ESM de Node. Ceci signifie que pour un fichier comme src/lib/something/index.js
, vous devez
inclure le nom du fichier avec l’extension :
import { import something
something } from './something/index.js';
Si vous utilisez TypeScript, vous devez importer les fichiers .ts
de la même manière, mais en
utilisant un suffixe .js
, et non .ts
. (Ceci est une décision de design de TypeScript, en
dehors de notre contrôle.) Définir "moduleResolution": "NodeNext"
dans votre tsconfig.json
ou
jsconfig.json
vous aidera à faire cela.
Tous les fichiers à l’exception des fichiers Svelte (qui sont préprocessés) et les fichiers TypeScript (qui sont transpilés vers JavaScript) sont copiés tels quels.
Modifier cette page sur Github llms.txt