Skip to main content

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’installer typescript >= 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 fichier package.json. Ce n’est plus le cas, et désormais le fichier package.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 QuelqueChoseQuelqueChose } 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 par default.

Les versions précédentes de @svelte/package avaient également un export package.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 fichier package.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 :

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

package
{
	"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 de src/lib et re-construit le paquet
  • -i / --input — le dossier d’entrée qui contient tous les fichiers du paquet. Vaut par défaut src/lib
  • -o / --output — le dossier de sortie où les fichiers traités sont écrits. Le champ exports de votre fichier package.json devrait pointer vers des fichiers qui s’y trouvent, et le tableau files devrait inclure ce dossier. Vaut par défaut dist
  • -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éfaut true
  • --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 somethingsomething } 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

précédent suivant