C12 101 - Première prise en main
- Date de publication
- Authors
- Estéban Soubiran
Introduction
C12
est un outil permettant de charger intelligemment des configurations dans un projet pour le paramétrer en utilisant les valeurs de l'utilisateur.
Il se base sur des outils comme defu
et rc9
pour proposer une solution simple et efficace.
Retrouver le code source de cet article c12-101-first-hand
UnJS, c'est Quoi ?
UnJS, c'est un écosystème d'outils JavaScript. L'objectif est de fournir des outils qui ne font qu'une seule chose mais qui la font très bien et qui peuvent être combinés entre eux pour créer des outils plus complexes.
UnJS suit la philosophie UNIX : "Faites une chose et faites le bien". Ainsi, la plupart des outils UnJS sont des outils avec très peu de fonctionnalités mais dont la force réside dans la modularité avec d'autres outils. À l'origine du projet, il y a Pooya "Pi0" Parsa développeur chez NuxtLabs et leader de Nuxt des premiers commits à son lancement officiel !
Chacun des projets est maintenu par son auteur et des mainteneurs désignés.
Pour en savoir plus, nous pouvons lire leur gouvernance.
La Problématique
Imaginons que nous voulions créer le prochain framework Vue.js.
Lire aussi :
Le prochain framework Vue.js
Comment écrire le prochain framework et détrôner Nuxt ? C'est ce que nous allons voir !
Pour cela, nous allons devoir utiliser différents outils comme Vue.js, et Nitro. Chacun de ses outils, nous allons devoir les configurer pour qu'ils fonctionnent ensemble. Dans le même temps, nous allons devoir permettre à l'utilisateur de configurer notre framework et les outils qu'il utilise pour qu'il puisse les personnaliser selon ses besoins.
Par exemple, il est important pour l'utilisateur de pouvoir changer le port sur lequel le serveur va écouter.
Pour configurer cela, l'utilisateur peut saisir sa configuration dans un fichier de configuration, dans des fichiers RC et même dans le package.json
de son projet. Ainsi, nous allons devoir charger l'ensemble de ces configurations et les combiner dans le bon ordre pour que l'utilisateur puisse configurer correctement notre framework.
Installation
Pour commencer à jouer avec c12
, nous allons l'installer. Pour cela, commençons par initialiser un nouveau projet :
mkdir c12-101
cd c12-101
npm init -y
Ensuite, nous allons installer c12
:
npm install c12
Pour faciliter l'exécution de nos fichiers TypeScript, nous allons installer jiti
:
npm install -D jiti
Pour finir, nous allons créer le dossier src
qui contiendra toutes nos sources :
mkdir src
Chargement des Configurations
Pour charger l'ensemble de ces configurations et les assembler, nous allons pouvoir utiliser c12
et apprendre à le configurer petit à petit. Dans l'ensemble de ce tutoriel, nous allons faire semblant de vouloir créer une application s'appelant tnux
.
Pour commencer, nous allons créer un fichier config.ts
dans le dossier src
:
touch src/config.ts
Ensuite, nous allons pouvoir utiliser la fonction loadConfig
de c12
pour charger la configuration :
import { loadConfig } from 'c12'
export async function loadTnuxConfig() {
const { config } = await loadConfig({
name: 'tnux', // Permet de définir le nom des clés et fichiers de configuration qui seront recherchés par C12.
})
return config
}
Ensuite, nous allons pouvoir utiliser cette fonction dans notre fichier index.ts
:
import { loadTnuxConfig } from './config'
loadTnuxConfig().then((config) => {
console.dir(config, { depth: null })
})
Parfait ! Nous avons réussi à charger la configuration de notre application tnux
. Maintenant, nous allons pouvoir commencer à la configurer.
Pour exécuter notre script index.ts
, nous allons devoir utiliser jiti
pour exécuter notre fichier TypeScript. Pour cela, nous allons devoir ajouter un script dans notre package.json
:
{
"scripts": {
"start": "jiti src/index.ts"
}
}
Pour le moment, la function ne retourne qu'un simple objet vide.
Configuration par Défaut
Commençons par mettre en place notre configuration par défaut pour notre application. Pour cela, nous allons pouvoir la définir directement dans la fonction loadConfig
:
// ...
export async function loadTnuxConfig() {
const { config } = await loadConfig({
name: 'tnux',
defaults: {
app: {
name: 'tnux',
version: '0.0.1',
},
vite: {
port: 3000,
},
tailwind: {
themes: {
colors: {
primary: 'blue',
secondary: 'gray',
},
},
},
},
})
return config
}
Maintenant, nous pouvons exécuter notre script pour voir le résultat et sans surprise. Il s'agit de notre configuration par défaut ! 😎
Nous chargeons ici une configuration totalement arbitraire. L'idée est d'avoir une configuration que nous allons pouvoir personnaliser par la suite grâce aux différents points de chargement dont nous avons parlé.
Notre configuration par défaut fonctionne ! Nous allons pouvoir commencer à charger une configuration utilisateur.
Configuration du Package.json
La première configuration que nous allons charger est celle présente dans le package.json
du projet. Pour trouver cette configuration, c12
va utiliser la clé tnux
que nous avons défini lors du paramétrage de c12
.
Dans notre fichier package.json
, nous pouvons ajouter une peu de configuration :
{
// ...
"tnux": {
"app": {
"baseUrl": "/tnux"
}
}
}
Maintenant, nous allons pouvoir exécuter notre script pour voir le résultat :
npm start
Et nous obtenons le résultat suivant 😟 :
{
app: { name: 'tnux', version: '0.0.1' },
vite: { port: 3000 },
tailwind: { themes: { colors: { primary: 'blue', secondary: 'gray' } } }
}
Il manque la clé baseURL
!
Par défaut, c12
ne charge pas la configuration du package.json
. Pour cela, nous allons devoir ajouter une option supplémentaire à notre fonction loadConfig
:
// ...
export async function loadTnuxConfig() {
const { config } = await loadConfig({
name: 'tnux',
packageJson: true, // Permet de charger la configuration du package.json.
defaults: {
// ...
},
})
return config
}
En ré-exécutant notre fichier, nous obtenons maintenant le bon résultat :
{
app: { name: 'tnux', version: '0.0.1', baseURL: '/tnux' }, // La clé baseURL est bien présente.
vite: { port: 3000 },
tailwind: { themes: { colors: { primary: 'blue', secondary: 'gray' } } }
}
Configuration par Ficher RC Global
Il est possible d'utiliser un fichier RC global pour paramétrer son application. Cette fonctionnalité est pratique pour permettre de configurer l'ensemble de ses applications avec la même configuration initiale.
Pour commencer, nous allons créer un fichier RC global comprenant une petite configuration. Pour cela, créons le fichier global-rc.ts
:
import { writeUser } from 'rc9'
writeUser({
app: {
name: 'tnux-from-rc',
},
})
Lire aussi :
Bien sûr, nous ne devons pas oublier de l'installer :
npm i -D rc9
Puis lançons notre script avec jiti src/global-rc.ts
. Une fois de plus, pour que le fichier RC soit chargé, nous devons mettre à jour notre configuration :
// ...
export async function loadTnuxConfig() {
const { config } = await loadConfig({
name: 'tnux',
globalRC: true, // Permet de charger la configuration d'un fichier RC globals.
defaults: {
// ...
},
})
return config
}
Nous pouvons alors voir apparaître name: 'tnux-from-rc'
dans notre configuration :
{
app: { name: 'tnux-from-rc', version: '0.0.1', baseURL: '/tnux' },
vite: { port: 3000 },
tailwind: { themes: { colors: { primary: 'blue', secondary: 'gray' } } }
}
Le nom du fichier chargé est .<name>rc
. Il est possible de le changer avec l'option rcFile
.
Configuration par Fichier RC Local
Pour tester le chargement d'un fichier local, commençons par le créer en le nommant .tnuxrc
et ajoutons-y dedans une nouvelle version :
app.version=0.2.0
Chargeons notre configuration et ça fonctionne 🎉, nous pouvons observer la version 0.2.0
.
{
app: { name: 'tnux', version: '0.2.0', baseURL: '/tnux' },
vite: { port: 3000 },
tailwind: { themes: { colors: { primary: 'blue', secondary: 'gray' } } }
}
Cette fonctionnalité est notamment utile pour toucher à la configuration sans avoir à toucher le fichier de configuration de l'utilisateur.
C'est avec cette technique que Nuxt Studio est en mesure d'injecter le module @nuxthq/studio
dans l'Actions GitHub permettant de déployer le site sans demander à l'utilisateur de réaliser une installation !
Nous pouvons voir cela dans leur script de déploiement :
- name: Create .nuxtrc
run: echo 'modules[]=@nuxthq/studio' > .nuxtrc
Cette ligne permet d'ajouter le module dans ceux de l'application uniquement lors du déploiement de celle-ci puisque le fichier .nuxtrc
est chargé lors de la création de la configuration.
Configuration par Fichier TS
La manière la plus naturelle d'ajouter de la configuration à un projet est de le faire via un fichier de configuration dédié et c'est ce que nous allons voir dans cette partie.
C12
supporte le chargement d'un fichier de configuration nommé <name>.config
.
Il n'est pas obligatoire que le fichier de configuration soit en TypeScript.
Dans notre cas, créons un fichier tnux.config.ts
dans lequel nous allons mettre à jour le port de démarrage de l'application :
export default {
vite: {
port: 5678,
},
}
Lançons notre configuration à l'aide de npm run start
et observons le résultat :
{
app: { name: 'tnux', version: '0.2.0', baseURL: '/tnux' },
vite: { port: 5678 },
tailwind: { themes: { colors: { primary: 'blue', secondary: 'gray' } } }
}
Le port a bien été mis à jour !
Configuration par Options pour Sur-Charger
Maintenant que nous avons vu tous les moyens donnés à l'utilisateur pour configuration son application, il reste une dernière clé à connaître pour garder la main sur la configuration de notre projet. Il s'agit de la clé overrides
.
Supposons que l'on veuille à tout prix que le port de notre application soit 1234
, il est possible de le faire en ajoutant une cette configuration lorsque nous appelons loadConfig
:
// ...
export async function loadTnuxConfig() {
const { config } = await loadConfig({
name: 'tnux',
overrides: {
vite: {
port: 1234,
},
},
defaults: {
// ...
},
})
return config
}
Grâce à cette configuration, nous aurons toujours le dernier mot sur le port qu'utilisera notre application.
Variables d'Environnement
C12
permet de charger un fichier .env
dans les variables d'environnement grâce à dotenv
. Pour cela, nous devons lui indiquer de la faire :
// ...
export async function loadTnuxConfig() {
const { config } = await loadConfig({
name: 'tnux',
dotenv: true, // Permet d'activer le chargement du fichier .env
defaults: {
// ...
},
})
return config
}
Ensuite, nous devons créer notre fichier .env
:
AWESOME_ENV=Loaded from .env using c12
Enfin, nous allons afficher cette variable pour nous assurer qu'elle est bien chargée :
// ...
loadTnuxConfig().then((config) => {
// ...
console.log(process.env.AWESOME_ENV)
})
Et voilà! En lançant notre script, nous obtenons le résultat suivant :
{
app: { name: 'tnux', version: '0.2.0', baseURL: '/tnux' },
vite: { port: 3000 },
tailwind: { themes: { colors: { primary: 'blue', secondary: 'gray' } } }
}
Loaded from .env using c12 // Il s'agit bien du contenu que nous avons placé dans notre fichier .env
Configuration par Environnement
Une fonction supplémentaire offerte par c12
est la possibilité de sur-charger sa configuration en fonction de l'environnement dans lequel l'application est lancée.
Supposons le cas où nous avons 3 environnements :
dev
pour le développementstaging
pour la pré-productionprod
pour la production
Dans le premier, nous ne voulons pas la minification ni d'HTTPS mais beaucoup de logs, dans le second, nous voulons la minification et beaucoup de logs et l'HTTPS et dans le dernier, nous voulons la minification, l'HTTPS et peu de logs.
Pour cela, il nous serait possible d'avoir tout un jeu de conditions avec la valeur de la variable d'environnement NODE_ENV
mais c12
nous permet de faire cela de manière plus simple.
Dans notre cas, nous allons éditer la configuration présente dans tnux.config.ts
:
export default {
vite: {
port: 5678,
},
$dev: {
minification: false,
logLevel: 'debug',
vite: {
https: true,
}
},
$staging: {
minification: true,
logLevel: 'debug',
vite: {
https: true,
}
},
$prod: {
minification: true,
logLevel: 'error',
vite: {
https: true,
}
}
}
En utilisant la syntaxe $<envName>
, nous pouvons définir des configurations qui seront appliquées en fonction de la valeur de process.env.NODE_ENV
.
Ainsi, avec la commande NODE_ENV=prod jiti src/index.ts
, nous allons avoir la configuration suivante :
{
app: { name: 'tnux', version: '0.2.0', baseURL: '/tnux' },
vite: { port: 5678, https: true },
tailwind: { themes: { colors: { primary: 'blue', secondary: 'gray' } } },
minification: true,
logLevel: 'error'
}
Il est possible de changer le nom de l'environnement en utilisant la clé envName
dans la configuration de c12
. Par défaut, process.env.NODE_ENV
est utilisé. Ainsi, il suffit de changer la valeur de cette variable pour changer la configuration qui est chargée.
Conclusion
C12
permet de créer facilement une configuration pour nos applications à partir de plusieurs sources. Voici un récapitulatif de l'ordre de chargement, de celle avec le plus de poids à celle avec le moins d'importance :
- Sur-charge de la configuration par les options
- Fichier de configuration dans le dossier courant
- Fichier RC dans le dossier courant
- Fichier RC global
- Configuration du package.json
- Configuration par défaut
- Extension des configurations des différents couches
Maintenant que nous avons vu la base, les utilitaires bas niveaux, nous allons pouvoir commencer à les assembler dans des outils plus haut niveau pour faire des programmes plus complexes avec des fonctionnalités plus intéressantes. Ainsi, nous découvrirons dans la suite de cette série citty
, un outil pour créer des applications en ligne de commande. Puis nous verrons comment créer notre propre application en ligne de commande avec citty
et les autres outils d'UnJS.