Fermer

septembre 27, 2019

Approche Agnostique Cadre (Partie 2)


À propos de l'auteur

Denys est un développeur frontend et un orateur public. Diplômé d'une école d'art et ingénieur, Denys est un passionné de psychologie, de physique,…
Plus d'informations sur
Denys
Mishunov

Nous avons récemment discuté de ce qu'est la «migration Frankenstein», comparée à celle des types de migrations classiques, et avons mentionné deux blocs de construction principaux: microservices et Web Components . Nous avons également une base théorique sur le fonctionnement de ce type de migration. Si vous n'avez pas lu ou oublié cette discussion, vous voudrez peut-être revenir à la Partie 1 en premier parce que cela aide à comprendre tout ce que nous allons couvrir dans cette deuxième partie de l'article.

article, nous allons mettre toute la théorie à l'épreuve en effectuant la migration pas à pas d'une application, en suivant les recommandations de la partie précédente . Pour simplifier les choses, réduire les incertitudes, les inconnues et les suppositions inutiles, pour l'exemple concret de la migration, j'ai décidé de démontrer la pratique sur une application de tâche simple.

 Il est temps de mettre la théorie à l'épreuve
. Il est temps de mettre la théorie à l'épreuve. ( Grand aperçu )

En général, je suppose que vous comprenez bien le fonctionnement d'une application de tâche générique. Ce type d’application répond très bien à nos besoins: il est prévisible, mais comporte un nombre minimum de composants nécessaires pour démontrer différents aspects de la migration Frankenstein. Toutefois, quelles que soient la taille et la complexité de votre application réelle, l'approche est bien évolutive et doit être adaptée à des projets de toute taille.

 Vue par défaut d'une application TodoMVC
Vue par défaut d'un TodoMVC application ( Image agrandie )

Pour cet article, j'ai choisi comme point de départ une application jQuery tirée de du projet TodoMVC – un exemple qui pourrait déjà être être familier à beaucoup d'entre vous. jQuery est un héritage suffisant, peut refléter une situation réelle avec vos projets et, plus important encore, nécessite une maintenance importante et des bidouilles pour l'alimentation d'une application dynamique moderne. (Cela devrait être suffisant pour envisager la migration vers quelque chose de plus flexible.)

Quelle est cette "plus flexible" vers laquelle nous allons migrer? Pour montrer un cas très pratique utile dans la vie réelle, je devais choisir parmi les deux cadres les plus populaires de nos jours: React et Vue. Toutefois, quel que soit le choix que nous ferions, nous manquerions certains aspects de l’autre direction.

Dans cette partie, nous allons passer en revue les deux éléments suivants:

  • Migration d’une application jQuery vers Réagir et
  • Migration d'une application jQuery vers Vue .
 Nos objectifs: résultats de la migration vers React et Vue
Nos objectifs: résultats de la migration vers React et Vue ( Grand aperçu )

Dépôts de code

Tout le code mentionné ici est disponible publiquement et vous pouvez y accéder à tout moment.

  • Frankenstein TodoMVC
    Ce référentiel contient les applications TodoMVC dans différents frameworks / bibliothèques. Par exemple, vous pouvez trouver des branches telles que vue angularjs réagissent et jquery dans ce référentiel.
  • Frankenstein Démo
    Il contient plusieurs branches, chacune représentant une migration direction particulière entre applications, disponible dans le premier référentiel. Il existe des branches telles que migration / jquery-to-react et migration / jquery-to-vue en particulier, que nous couvrirons plus tard.

Les deux dépôts sont: les travaux en cours et les nouvelles branches avec de nouvelles applications et les directions de migration doivent y être ajoutés régulièrement. ( Vous aussi, vous êtes libre de contribuer! ) L'historique des validations dans les branches de migration est bien structuré et peut servir de documentation supplémentaire avec encore plus de détails que je ne pourrais en couvrir dans cet article.

nos mains sales! Nous avons encore beaucoup de chemin à parcourir, alors ne vous attendez pas à une conduite en douceur. C'est à vous de décider comment vous souhaitez suivre cet article, mais vous pouvez procéder comme suit:

  • Clonez la branche jquery du référentiel Frankenstein TodoMVC et suivez scrupuleusement toutes les Vous pouvez également ouvrir une branche dédiée soit à la migration vers le référentiel React soit à la migration vers Vue à partir du Frankenstein Demo et que vous suivez engage l'histoire.
  • Sinon, vous pouvez vous détendre et continuer à lire car je vais mettre en évidence le code le plus critique ici, et il est beaucoup plus important de comprendre les mécanismes du processus plutôt que le code lui-même.

Je voudrais mentionner une fois de plus que nous suivrons strictement les étapes présentées dans la première partie théorique de l'article .

Plongez dans le droit chemin!

  1. Identify Microservices [19659014] Autoriser hôte-t o-Alien Access
  2. Ecrivez un Alien Microservice / Composant
  3. Ecrivez un composant Wrapper autour du service Alien
  4. Remplacez le service hôte par un composant Web
  5. Rincer et répéter pour tous vos composants
  6. Basculer en mode Alien

1. Identify Microservices

Comme 1ère partie le suggère, nous devons, dans cette étape, structurer notre application de manière à small indépendante dédiée à une emploi . Le lecteur attentif peut remarquer que notre application de tâches à faire est déjà petite et indépendante et peut représenter un seul microservice. C’est ce que je ferais moi-même si cette application vivait dans un contexte plus large. Cependant, rappelez-vous que le processus d'identification des microservices est entièrement subjectif et qu'il n'y a pas de réponse correcte.

Donc, pour voir le processus de migration de Frankenstein plus en détail, nous pouvons passer à l'étape suivante. En outre, cette application de tâches à découper est scindée en deux microservices indépendants:

  1. . Un champ de saisie permettant d’ajouter un nouvel élément.
    Ce service peut également contenir l’en-tête de l’application, basé uniquement sur la proximité de positionnement de ces éléments.
  2. [1965 Liste des éléments déjà ajoutés
    Ce service est plus avancé et contient, avec la liste elle-même, des actions telles que le filtrage, les actions des éléments de la liste, etc.
 L'application TodoMVC est scindée en deux microservices indépendants.
Application TodoMVC divisée en deux microservices indépendants. ( Image agrandie )

Conseil : Pour vérifier si les services sélectionnés sont réellement indépendants, supprimez le balisage HTML représentant chacun de ces services. Assurez-vous que les fonctions restantes fonctionnent toujours. Dans notre cas, il devrait être possible d'ajouter de nouvelles entrées dans localStorage (que cette application utilise comme mémoire de stockage) à partir du champ d'entrée sans la liste, tandis que la liste restitue les entrées de localStorage [19659049] même si le champ de saisie est manquant. Si votre application génère des erreurs lorsque vous supprimez le balisage pour un microservice potentiel, reportez-vous à la section «Refactor si nécessaire» de Partie 1 pour obtenir un exemple de traitement de tels cas.

Bien sûr, nous pourrions continuer et scinder le deuxième service et la liste des éléments encore plus loin en microservices indépendants pour chaque élément particulier. Cependant, il pourrait être trop granulaire pour cet exemple. Donc, pour l'instant, nous concluons que notre application va avoir deux services; ils sont indépendants et chacun travaille à sa propre tâche. Par conséquent, nous avons divisé notre application en microservices .

2. Autoriser l'accès hôte à étranger

Permettez-moi de vous rappeler brièvement ce qu'ils sont.

  • Hôte
    C'est ce que notre application actuelle est appelée. Il est écrit avec le cadre à partir duquel nous allons s’éloigner de. Dans ce cas particulier, notre application jQuery.
  • Alien
    En termes simples, il s’agit d’une réécriture progressive de Host sur le nouveau cadre que nous sommes sur le point de déplacer vers . Encore une fois, dans ce cas particulier, il s’agit d’une application React ou Vue.

La règle empirique lors de la scission de Host et Alien est qu’il est possible de développer et de déployer l’un d’entre eux sans casser l’autre. - à tout moment.

Garder Host et Alien indépendants est crucial pour Frankenstein Migration. Cependant, cela rend la communication entre les deux un peu difficile.

Ajouter Alien en tant que sous-module de votre hôte

Même s'il existe plusieurs façons de réaliser la configuration dont nous avons besoin, la manière la plus simple d'organiser votre projet pour y répondre Le critère est probablement git sous-modules . C’est ce que nous allons utiliser dans cet article. Je vous laisse le soin de lire attentivement le fonctionnement des sous-modules dans git afin de comprendre les limitations et les contraintes (19459011) de cette structure.

Les principes généraux de L'architecture de notre projet avec les sous-modules git devrait ressembler à ceci:

  • Host et Alien sont indépendants et sont conservés dans des référentiels distincts git ;
  • Host référence Alien comme sous-module. À ce stade, l'hôte sélectionne un état particulier (commit) d'Alien et l'ajoute sous la forme d'un sous-dossier dans la structure de dossiers de l'hôte.
 React TodoMVC ajouté en tant que sous-module git dans l'application jQuery TodoMVC
. React TodoMVC ajouté sous-module git dans l’application jQuery TodoMVC. ( Grand aperçu )

Le processus d'ajout d'un sous-module est le même pour toutes les applications. L'enseignement Les sous-modules git sortent du cadre de cet article et ne sont pas directement liés à la migration Frankenstein elle-même. Jetons donc un coup d’œil sur les exemples possibles.

Dans les extraits ci-dessous, nous utilisons l’exemple React. Pour toute autre direction de migration, remplacez réagissez par le nom d'une branche de Frankenstein TodoMVC ou ajustez-le à des valeurs personnalisées si nécessaire.

Si vous suivez en utilisant l'application jQuery TodoMVC d'origine:

 $ git sous-module add -b réagir git@gitlab.com: mishunov / frankenstein-todomvc.git réagir
$ git submodule update --remote
$ cd réagir
$ npm i

Si vous suivez la branche migration / jquery-to-react (ou toute autre direction de migration) du référentiel Frankenstein Demo, l'application Alien devrait déjà s'y trouver en tant que sous-module git et vous devriez voir un dossier respectif. Cependant, le dossier est vide par défaut et vous devez mettre à jour et initialiser les sous-modules enregistrés .

Depuis la racine de votre projet (votre hôte):

 Mise à jour du sous-module $ git --init
$ cd réagir
$ npm i

Notez que dans les deux cas, nous installons des dépendances pour l'application Alien, mais que celles-ci deviennent un bac à sable dans le sous-dossier et ne polluent pas notre hôte.

Après avoir ajouté l'application Alien en tant que sous-module de votre hôte, vous êtes indépendant ( en termes de microservices) Applications Alien et Host. Cependant, Host considère Alien comme un sous-dossier dans ce cas et, bien entendu, cela permet à Host d'accéder à Alien sans problème.

3. Écrire un composant / un composant extraterrestre

À cette étape, nous devons choisir le microservice à migrer en premier et l’écrire / l’utiliser du côté extraterrestre. Suivons le même ordre de services que nous avons identifié à l’étape 1 et commençons par le premier: champ de saisie permettant d’ajouter un nouvel élément. Cependant, avant de commencer, convenons qu'au-delà de ce point, nous allons utiliser un composant plus favorable au lieu de microservice ou service à mesure que nous avançons. vers les prémisses des frameworks frontend et du composant suit les définitions de pratiquement tout framework moderne.

Les branches du référentiel Frankenstein TodoMVC contiennent un composant résultant qui représente le premier service «Champ de saisie pour l'ajout d'un nouveau item ”en tant que composant d'en-tête:

L'écriture de composants dans le cadre de votre choix dépasse le cadre de cet article et ne fait pas partie de Frankenstein Migration. Cependant, il est important de garder à l'esprit quelques points importants lorsque vous écrivez un composant Alien.

Indépendance

Tout d'abord, les composants dans Alien doivent suivre le même principe d'indépendance, précédemment défini du côté de l'hôte: composants

Interopérabilité

Grâce à l’indépendance des services, les composants de votre hôte communiquent très probablement d’une manière bien établie, qu’il s’agisse d’un système de gestion d’état ou d’une communication partagée. stockage ou directement via un système d’événements DOM. «L'interopérabilité» des composants Alien signifie qu'ils doivent pouvoir se connecter à la même source de communication, établie par Host, pour envoyer des informations sur ses modifications d'état et écouter les modifications apportées aux autres composants. En pratique, cela signifie que si les composants de votre hôte communiquent via des événements DOM, la construction de votre composant Alien exclusivement dans un souci de gestion d'état ne fonctionnera malheureusement pas pour ce type de migration.

Par exemple, un aperçu du fichier js / storage.js principal canal de communication de nos composants jQuery:

 ...

chercher: function () {
  return JSON.parse (localStorage.getItem (STORAGE_KEY) || "[]");
},
save: function (todos) {
  localStorage.setItem (STORAGE_KEY, JSON.stringify (todos));
  var event = new CustomEvent ("store-update", {detail: {todos}});
  document.dispatchEvent (event);
},

...

Ici, nous utilisons localStorage (car cet exemple n'est pas critique pour la sécurité ) pour stocker nos tâches à effectuer et, une fois que les modifications apportées à l'enregistrement sont enregistrées, nous expédions un événement DOM personnalisé sur l'élément document pouvant être écouté par n'importe quel composant

Parallèlement, du côté Alien (par exemple, React), nous pouvons configurer une communication de gestion d'état aussi complexe que nous le souhaitons. . Cependant, il est probablement sage de le garder pour le futur: pour réussir l’intégration de notre composant Alien React dans Host, nous devons nous connecter au même canal de communication que celui utilisé par Host. Dans ce cas, il s’agit de stockage local . Pour simplifier les choses, nous avons simplement copié le fichier de stockage de Host dans Alien et nous y avons branché nos composants :

 importez todoStorage de "../storage";

class Header étend le composant {
  constructeur (accessoires) {
    this.state = {
      todos: todoStorage.fetch ()
    };
  }
  composantDidMount () {
    document.addEventListener ("store-update", this.updateTodos);
  }
  composantWillUnmount () {
    document.removeEventListener ("store-update", this.updateTodos);
  }
  composantDidUpdate (prevProps, prevState) {
    if (prevState.todos! == this.state.todos) {
      todoStorage.save (this.state.todos);
    }
  }
  ...
}

Nos composants Alien peuvent maintenant parler le même langage avec les composants Host et inversement.

4. Écrire un composant Web wrapper autour du service Alien

Même si nous en sommes maintenant à la quatrième étape, nous avons accompli beaucoup:

  • Nous avons divisé notre application Host en services indépendants qui sont prêts à être remplacés par Alien.
  • Nous avons configuré Host et Alien pour qu'ils soient complètement indépendants les uns des autres, tout en étant très bien connectés via sous-modules git ;
  • Nous avons écrit notre premier composant Alien en utilisant le nouveau cadre.

Il est maintenant temps de mettre en place un pont entre Host et Alien pour que le nouveau composant Alien puisse fonctionner dans Host.

Rappel de Partie 1 : Assurez-vous que votre hôte dispose d'un bundler de paquets. Dans cet article, nous nous appuyons sur Webpack, mais cela ne signifie pas que la technique ne fonctionnera pas avec Rollup ou avec un autre lieur de votre choix. Cependant, je laisse le mappage de Webpack à vos expériences.

Convention de nommage

Comme mentionné dans l'article précédent, nous allons utiliser Web Components pour intégrer Alien dans Host. Du côté de l’hôte, nous créons un nouveau fichier: js / frankenstein-wrappers / Header-wrapper.js . (Ce sera notre premier wrapper Frankenstein.) N'oubliez pas que c’est une bonne idée de nommer vos wrappers de la même façon que vos composants dans l’application Alien, par exemple. en ajoutant simplement un suffixe « -wrapper ». Vous verrez plus tard pourquoi c'est une bonne idée, mais pour l'instant, convenons que cela signifie que si le composant Alien s'appelle Header.js (in React) ou Header.vue (dans Vue), le wrapper correspondant du côté de l'hôte devrait s'appeler Header-wrapper.js .

Dans notre premier wrapper, nous commençons par le passe-partout fondamental pour l'enregistrement . ] a élément personnalisé :

 la classe FrankensteinWrapper étend HTMLElement {}
customElements.define ("frankenstein-header-wrapper", FrankensteinWrapper);

Ensuite, nous devons initialiser Shadow DOM pour cet élément.

Veuillez vous reporter à la partie 1 pour obtenir un raisonnement sur l'utilisation du DOM Shadow.

 La classe FrankensteinWrapper étend HTMLElement {
  connectedCallback () {
    this.attachShadow ({mode: "open"});
  }
}

Avec cela, nous avons mis en place tous les éléments essentiels du composant Web et il est temps d’ajouter notre composant Alien. Tout d’abord, au début de notre encapsuleur Frankenstein, nous devons importer tous les bits responsables du rendu du composant Alien.

 import React from "../../react/node_modules/react";
importer ReactDOM de "../../react/node_modules/react-dom";
importer HeaderApp depuis "../../react/src/components/Header";
...

Ici, nous devons faire une pause une seconde. Notez que nous n’importons pas les dépendances d’Alien à partir de node_modules de Host. Tout vient de l'Alien lui-même qui se trouve dans le sous-dossier react / . C’est la raison pour laquelle l’étape 2 est si importante et il est essentiel de s’assurer que l’organisateur dispose d’un accès complet aux ressources d’Alien.

Nous pouvons maintenant rendre notre composant Alien dans le DOM Shadow du composant Web:

 ...
connectedCallback () {
  ...
  ReactDOM.render (this.shadowRoot);
}
...

Note : Dans ce cas, React n’a besoin de rien d’autre. Cependant, pour rendre le composant Vue, vous devez ajouter un nœud d'habillage contenant votre composant Vue, comme suit:

 ...
connectedCallback () {
  const mountPoint = document.createElement ("div");
  this.attachShadow ({mode: "open"}). appendChild (mountPoint);
  nouveau Vue ({
    render: h => h (VueHeader)
  }). $ mount (mountPoint);
}
...

La raison en est la différence entre le rendu des composants React et Vue: React ajoute un composant au nœud DOM référencé, tandis que Vue remplace le nœud DOM référencé par le composant. Par conséquent, si nous faisons . $ Mount (this.shadowRoot) pour Vue, il remplace essentiellement le DOM Shadow.

C’est tout ce que nous avons à faire pour le wrapper. Le résultat actuel pour l'encapsuleur Frankenstein dans les deux directions de migration jQuery-à-React et jQuery-à-Vue est disponible ici:

Pour résumer les mécanismes de l'encapsuleur Frankenstein:

  1. Créer un élément personnalisé, [19659014] Initiate Shadow DOM,
  2. Importez tout ce qui est nécessaire au rendu d'un composant Alien,
  3. Rendez le composant Alien dans le DOM Shadow de l'élément personnalisé.

Toutefois, cela ne rend pas notre Alien in Host automatiquement. Nous devons remplacer le balisage d'hôte existant par notre nouvelle enveloppe Frankenstein.

Attachez vos ceintures, ce n'est peut-être pas aussi simple qu'on pourrait le croire!

5. Remplacer le service hôte par un composant Web

Continuons et ajoutons notre nouveau fichier Header-wrapper.js à index.html et remplaçons le balisage d'en-tête existant par le nouveau [[19659126] élément personnalisé.

 ...
<! - 
-> <! -

todos

-> <! - -> <! -
-> ...     

Malheureusement, cela ne fonctionnera pas aussi simplement. Si vous ouvrez un navigateur et vérifiez la console, le message Uncaught SyntaxError vous attend. Selon le navigateur et son support pour les modules ES6 il sera soit lié aux importations ES6, soit à la manière dont le composant Alien est rendu. Quoi qu'il en soit, nous devons faire quelque chose à ce sujet, mais le problème et la solution doivent être familiers et clairs pour la plupart des lecteurs.

5.1. Mise à jour de Webpack et de Babel, le cas échéant

Nous devrions impliquer de la magie Webpack et Babel avant d'intégrer notre wrapper Frankenstein. La discussion de ces outils dépasse le cadre de l'article, mais vous pouvez consulter les validations correspondantes dans le référentiel Frankenstein Demo:

Nous avons essentiellement configuré le traitement des fichiers ainsi qu'un nouveau point d'entrée . frankenstein dans la configuration de Webpack pour contenir tout ce qui concerne les encapsuleurs Frankenstein dans un emplacement.

Une fois que Webpack dans Host sait comment traiter le composant Alien et les composants Web, nous sommes prêts à remplacer le balisage de Host. avec le nouvel emballage Frankenstein

5.2. Remplacement du composant réel

Le remplacement du composant doit être simple maintenant. Dans index.html de votre hôte, procédez comme suit:

  1. Remplacez
    Élément DOM par ;
  2. Ajoutez un nouveau script frankenstein.js . Il s’agit du nouveau point d’entrée de Webpack contenant tout ce qui concerne les wrappers Frankenstein.
 ...
<! - Nous remplaçons 
-> ...

C’est tout! Redémarrez votre serveur si nécessaire et assistez à la magie du composant Alien intégré à Host.

Cependant, il manque quelque chose. Le composant Alien dans le contexte de l'hôte n'a pas la même apparence que dans le contexte de l'application autonome Alien.

 Composant Alien React non stylé après son intégration dans Host
Composant Alien React non stylé après son intégration dans Host ( Grand aperçu )

Pourquoi? Les styles du composant ne doivent-ils pas être automatiquement intégrés au composant Alien dans Host? Je souhaite qu'ils le fassent, mais comme dans trop de situations, cela dépend. Nous arrivons à la partie difficile de Frankenstein Migration.

5.3. Informations générales sur le style de la composante extraterrestre

Tout d'abord, l'ironie est qu'il n'y a pas de bogue dans la façon dont les choses fonctionnent. Tout est conçu pour fonctionner. Pour expliquer cela, mentionnons brièvement différentes manières de styliser les composants.

Styles globaux

Nous les connaissons tous: les styles globaux peuvent être (et sont généralement) distribués sans composant particulier et appliqués à la page entière. Les styles globaux affectent tous les nœuds DOM avec des sélecteurs correspondants

Quelques exemples de styles globaux sont les balises

Here, we import the StyleSheetManager component (from Alien, and not from Host) and wrap our React component with it. At the same time, we send the target property pointing to our shadowRoot. C'est ça. If you restart the server, you have to see something like this in your DevTools:

Styles bundled with React Alien component placed within Frankenstein wrapper with all unique CSS classes preserved.
Styles bundled with React Alien component placed within Frankenstein wrapper with all unique CSS classes preserved. (Large preview)

Now, our component’s styles are in Shadow DOM instead of . This way, the rendering of our app now resembles what we have seen with the Vue app previously.

After moving bundled styles into Frankenstein wrapper, the Alien React component begins to look better. However, we’re not there yet.
After moving bundled styles into Frankenstein wrapper, the Alien React component begins to look better. However, we’re not there yet. (Large preview)

Same story: styled-components are responsible just for the bundled part of the React component’s stylesand the global styles manage the remaining bits. We get back to global styles in a bit after we review one more type of styling components.

CSS Modules

If you take a closer look at the Vue component that we have fixed earlier, you might notice that CSS Modules is precisely the way we style that component. However, even if we style it with Scoped CSS (another recommended way of styling Vue components) the way we fix our unstyled component doesn’t change: it is still up to vue-loader and vue-style-loader to handle it through shadowMode: true option.

When it comes to CSS Modules in React (or any other system using CSS Modules without any dedicated tools), things get a bit more complicated and less flexible, unfortunately.

Let’s take a look at the same React component which we’ve just integrated, but this time styled with CSS Modules instead of styled-components. The main thing to note in this component is a separate import for stylesheet:

import styles from './Header.module.css'

The .module.css extension is a standard way to tell React applications built with the create-react-app utility that the imported stylesheet is a CSS Module. The stylesheet itself is very basic and does precisely the same our styled-components do.

Integrating CSS modules into a Frankenstein wrapper consists of two parts:

  • Enabling CSS Modules in bundler,
  • Pushing resulting stylesheet into Shadow DOM.

I believe the first point is trivial: all you need to do is set { modules: true } for css-loader in your Webpack configuration. Since, in this particular case, we have a dedicated extension for our CSS Modules (.module.css), we can have a dedicated configuration block for it under the general .css configuration:

{
  test: /.css$/,
  oneOf: [
    {
      test: /.module.css$/,
      use: [
        ...
        {
          loader: 'css-loader',
          options: {
            modules: true,
          }
        }
      ]
    }
  ]
}

Note: A modules option for css-loader is all we have to know about CSS Modules no matter whether it’s React or any other system. When it comes to pushing resulting stylesheet into Shadow DOM, however, CSS Modules are no different from any other global stylesheet.

By now, we went through the ways of integrating bundled styles into Shadow DOM for the following conventional scenarios:

  • Vue components, styled with CSS Modules. Dealing with Scoped CSS in Vue components won’t be any different;
  • React components, styled with styled-components;
  • Components styled with raw CSS Modules (without dedicated tools like those in Vue). For these, we have enabled support for CSS modules in Webpack configuration.

However, our components still don’t look as they are supposed to because their styles partially come from global styles. Those global styles do not come to our Frankenstein wrappers automatically. Moreover, you might get into a situation in which your Alien components are styled exclusively with global styles without any bundled styles whatsoever. So let’s finally fix this side of the story.

Global Styles And Shadow DOM

Having your components styled with global styles is neither wrong nor bad per se: every project has its requirements and limitations. However, the best you can do for your components if they rely on some global styles is to pull those styles into the component itself. This way, you have proper easy-to-maintain self-contained components with bundled styles.

Nevertheless, it’s not always possible or reasonable to do so: several components might share some styling, or your whole styling architecture could be built using global stylesheets that are split into the modular structure, and so on.

So having an opportunity to pull in global styles into our Frankenstein wrappers wherever it’s required is essential for the success of this type of migration. Before we get to an example, keep in mind that this part is the same for pretty much any framework of your choice — be it React, Vue or anything else using global stylesheets!

Let’s get back to our Header component from the Vue application. Take a look at this import:

import "todomvc-app-css/index.css";

This import is where we pull in the global stylesheet. In this case, we do it from the component itself. It’s only one way of using global stylesheet to style your component, but it’s not necessarily like this in your application.

Some parent module might add a global stylesheet like in our React application where we import index.css only in index.jsand then our components expect it to be available in the global scope. Your component’s styling might even rely on a stylesheet, added with