Introduction

Dans l’article 1 de la série consacrée au Design System + NPM Workspaces, nous avons vu comment créer le monorepo contenant les sources de notre premier composant.
Dans cet article, nous allons voir comment ajouter d’autres composants et comment publier nos composants sur un serveur gestionnaire de packages NPM.

Création d’un second composant

Le second composant de notre Design System sera un Toaster (https://codeseven.github.io/toastr/demo.html). Ce composant sera utilisé pour afficher des messages aux utilisateurs.

Comme pour le premier composant ilb-button, nous utiliserons le template Lit + TypeScript légèrement modifié. Vous pouvez donc dupliquer le premier composant et renommer le dossier ilb-button copy en ilb-toaster.

Modifier le fichier dev/index.html avec le code suivant :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Toaster Component</title>
    <script type="module" src="./index.js"></script>
</head>
<body>
    <button id="btn">Click Me to show toast !!</button>
    <ilb-toaster id="toaster" text="My custom message"></ilb-toaster>
    <script>
        const toaster = document.getElementById('toaster');
        const button = document.getElementById('btn');
        button.addEventListener('click', (e) => {
            toaster.setAttribute('showToast', true);
        });
    </script>
</body>
</html>

Supprimer le dossier node_modules, nous ferons une nouvelle installation des dépendances une fois le composant prêt.

Modifier le fichier src/index.scss avec le code suivant :

@import url(https://fonts.googleapis.com/css?family=Roboto:400,100,900);

//colors
$red: #E1332D;
$white: #fff;
$black: #000;

.toaster {
  position: fixed;
  right: 1rem;
  top: 1rem;
  background: $red;
  padding: 20px;
  border-radius: 8px;
  opacity: 0.8;
  box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.6);
  color: $black;

  &:hover {
    opacity: 1;
  }
}

Ainsi que le fichier src/index.ts, les commentaires vous renseignent sur les propriétés et la méthode de rendu :

import {LitElement, html, PropertyValues} from 'lit';
import {customElement, property} from 'lit/decorators.js';

import styles from './index.scss';

@customElement('ilb-toaster')
export class IlbToaster extends LitElement {

  @property()
  text: String = ''; // Message à afficher dans le toast

  @property()
  timer: number = 3000; // Durée d'affichage du toast

  @property()
  showToast: boolean = false; // Affichage du toast

  static get styles(): any {
    return [styles];
  }

	// Lance un timeout pour afficher le toast sur la durée souhaitée une fois la propriété showToast modifiée
  protected updated(changedProperties: PropertyValues): void {
    if (changedProperties.has('showToast')) {
      this.toasterTimer();
    }
  }

  toasterTimer() {
    setTimeout(() => { this.showToast = false }, this.timer);
  }

  // La méthode de rendu du composant Toaster
  render() {
    return this.showToast ? html`
      <div class="toaster">
        ${this.text}
      </div>
    `: '';
  }
}

Pour des raisons de simplicité, nous passons le message en propriété du composant, mais nous aurions également pu le passer via le slot pour laisser plus de possibilités de personnalisations à l’utilisateur de notre composant.

À la racine du package ilb-toaster, modifier les fichiers suivants :

src/customElement.json

{
  "version": "experimental",
  "tags": [
    {
      "name": "ilb-toaster",
      "path": "./src/index.ts",
      "description": "Toaster Design System",
      "properties": [
        {
          "name": "styles",
          "type": "CSSResult",
          "default": "\"css`\\n    .btn {\\n      background: red\\n    }\\n  `\""
        }
      ],
      "slots": [
        {
          "name": "",
          "description": "This element has a slot"
        }
      ]
    }
  ]
}

package.json

{
    "name": "ilb-toaster",
    "version": "0.0.0",
    "description": "Toaster webComponent",
    "keywords": [
        "toaster",
        "web-components",
        "lit-element",
        "typescript"
    ],
    "license": "ISC",
    "main": "dist/index.js",
    "unpkg": "dist/index.js",
    "type": "module",
    "types": "dist/index.d.ts",
    "files": [
        "dist"
    ],
    "scripts": {
        "build": "rollup -c ../../rollup.config.js",
        "serve": "rollup -w -c ../../rollup.config.dev.js",
        "checksize": "rollup -c ../../rollup.config.js ; cat dist/index.js | gzip -9 | wc -c ; rm dist/index.js",
        "prepublish": "rollup -c ../../rollup.config.js",
        "test": "web-test-runner ./src/**/*.spec.ts --node-resolve",
        "test:watch": "web-test-runner ./src/**/*.spec.ts --node-resolve --watch",
        "lint": "eslint ./src --ext ts"
    },
    "devDependencies": {
        "node-sass": "^7.0.1",
        "rollup": "^2.74.1",
        "sass": "^1.52.1"
    },
    "dependencies": {
        "lit": "^2.0.2"
    }
}

Les fichiers tsconfig.json, web-dev-server.config.js et web-test-runner.config.js restent identiques.

Nous pouvons maintenant exécuter l’installation des dépendances via la commande :

npm i --workspace=ilb-toaster // permet de spécifier le package ou s'éxecute la commande d'installation
// ou
npm i --workspaces // pour éxecuter la commande sur tous les packages

Et enfin lancer le serveur de développement avec la commande :

npm run --workspace=ilb-toaster serve

Le second composant fonctionne :

Notre composant Toast fonctionne

Maintenant que notre Design System comporte deux composants, il faut publier ces derniers sur un gestionnaire de packages afin de pouvoir les consommer dans une application ou dans un autre composant du Design System.

Publication des composants

Dans un premier temps nous allons utiliser une image Docker (https://hub.docker.com/r/verdaccio/verdaccio/) pour installer une registry npm localement.

Pour cela, lancer votre client Docker et télécharger l’image via la commande :

docker pull verdaccio/verdaccio

Une fois le téléchargement terminé, lancer l’image via la commande :

docker run -it --rm --name verdaccio -p 4873:4873 verdaccio/verdaccio

Il faut maintenant créer un utilisateur dans un autre terminal grâce à la commande :

npm adduser --registry http://0.0.0.0:4873/

Il suffit alors de suivre le prompt et d’entrer les informations souhaitées, dans notre cas :

Username: admin
Password: admin

Il faut maintenant mettre à jour la registry sur nos packages du Design System en ajoutant la clé sur chaque fichier package.json pour ajouter l’URL par default de la registry locale

...
	"publishConfig": {
	  "registry": "http://0.0.0.0:4873/"
	},
...

Pour rendre la publication plus facile, nous allons créer un script qui va prendre en paramètres les nom du package que nous souhaitons publier : à la racine du projet, créer un dossier cli, ce dossier nous servira plus tard pour créer une CLI afin de faciliter la création de nouveaux composants.

Dans le dossier cli, créer un nouveau fichier publish.js :

const { execSync } = require('child_process');
const minimist = require('minimist');

const argv = minimist(process.argv.slice(2)); // Utilisation de minimist pour récupérer les arguments passés dans la ligne de commande 

const { version, component } = argv; // Récupération de la version et du composant à publier

console.log('version', version); // Log pour avoir la version dans la console
console.log('component', component); // Log pour avoir le composant dans la console

execSync(`npm version ${version} --workspace=${component}`); // Met a jour la version dans le fichier package.json du package
execSync(`npm publish --workspace=${component}  --registry http://localhost:4873/`); // Publie le package sur la registry locale

Pour ce script, nous avons besoin de la librairie minimist que l’on va installer en dépendance de développement au moyen de la commande :

npm install -D minimist

Une fois le script créé, il faut builder le composant et le publier au moyen des commandes suivantes :

npm run --workspace=ilb-button build

Modifier l’attribut “scripts”, à la racine du projet dans votre fichier package.json :

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "publish:major": "node ./cli/publish --version=major",
    "publish:minor": "node ./cli/publish --version=minor",
    "publish:patch": "node ./cli/publish --version=patch"
  }

Puis :

npm run publish:major -- --component=ilb-button

En sortie de console, nous devons avoir :

Résultats de la console suite à la commande de publish

On vérifie maintenant que le package NPM est bien publié sur la registry :

Interface web de notre registry avec notre composant déployé

Et voilà, vous pouvez faire de même pour notre second package “toaster”.

Conclusion

Nous avons maintenant notre monorepo qui contient deux composants que nous sommes capables de publier sur une registry NPM. Dans la suite de cette série d’articles, nous verrons comment :

  • Comment créer un CLI pour faciliter la vie vos développeurs lors de la création de composants
  • Comment un composant peut avoir un autre composant en dépendance.
  • Comment consommer nos composants dans une application Angular / React / JavaScript.
  • Comment documenter notre Design System.

Série d’articles Design System :