{"id":71122,"date":"2019-09-27T04:15:46","date_gmt":"2019-09-27T11:15:46","guid":{"rendered":"http:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/"},"modified":"2019-09-27T04:15:46","modified_gmt":"2019-09-27T11:15:46","slug":"frankenstein-migration-framework-agnostic-approach-part-2","status":"publish","type":"post","link":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/","title":{"rendered":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)"},"content":{"rendered":"<link rel=\"canonical\" href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/\"><title>Frankenstein Migration: Framework-Agnostic Approach (Part 2)<\/title><\/p>\n<article>\n<header>\n<h1>Frankenstein Migration: Framework-Agnostic Approach (Part 2)<\/h1>\n<address>Denys Mishunov<\/address>\n<p>                  <time datetime=\"2019-09-27T12:00:59+02:00\">2019-09-27T12:00:59+02:00<\/time><time datetime=\"2019-09-27T11:07:38+00:00\">2019-09-27T11:07:38+00:00<\/time><\/header>\n<p>In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from <a href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-1\/\">the previous part<\/a>. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration, I decided to demonstrate the practice on a simple to-do application.<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3ce00bfb-3ab4-4bec-ab15-188fe0604aca\/1-frankenstein-approach-framework-agnostic-migration-part2.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/1-frankenstein-approach-framework-agnostic-migration-part2.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3ce00bfb-3ab4-4bec-ab15-188fe0604aca\/1-frankenstein-approach-framework-agnostic-migration-part2.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3ce00bfb-3ab4-4bec-ab15-188fe0604aca\/1-frankenstein-approach-framework-agnostic-migration-part2.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3ce00bfb-3ab4-4bec-ab15-188fe0604aca\/1-frankenstein-approach-framework-agnostic-migration-part2.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3ce00bfb-3ab4-4bec-ab15-188fe0604aca\/1-frankenstein-approach-framework-agnostic-migration-part2.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/1-frankenstein-approach-framework-agnostic-migration-part2.png?w=900\" sizes=\"100vw\" alt=\"It\u2019s time to put the theory to the test\"><\/a><figcaption>\n      It\u2019s time to put the theory to the test. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3ce00bfb-3ab4-4bec-ab15-188fe0604aca\/1-frankenstein-approach-framework-agnostic-migration-part2.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>In general, I assume that you have a good understanding of how a generic to-do application works. This type of application suits our needs very well: it\u2019s predictable, yet has a minimum viable number of required components to demonstrate different aspects of Frankenstein Migration. However, no matter the size and complexity of your real application, the approach is well-scalable and is supposed to be suitable for projects of any size.<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/75d9c1b3-2c20-4a2f-a22e-a104badc10dd\/todomvc-scr.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/todomvc-scr.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/75d9c1b3-2c20-4a2f-a22e-a104badc10dd\/todomvc-scr.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/75d9c1b3-2c20-4a2f-a22e-a104badc10dd\/todomvc-scr.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/75d9c1b3-2c20-4a2f-a22e-a104badc10dd\/todomvc-scr.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/75d9c1b3-2c20-4a2f-a22e-a104badc10dd\/todomvc-scr.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/todomvc-scr.png?w=900\" sizes=\"100vw\" alt=\"A default view of a TodoMVC application\"><\/a><figcaption>\n      A default view of a TodoMVC application (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/75d9c1b3-2c20-4a2f-a22e-a104badc10dd\/todomvc-scr.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>For this article, as a starting point, I picked <a href=\"http:\/\/todomvc.com\/examples\/jquery\/#\/all\">a jQuery application<\/a> from <a href=\"http:\/\/todomvc.com\/\">the TodoMVC project<\/a> \u2014 an example that may already be familiar to a lot of you. jQuery is legacy enough, might reflect a real situation with your projects, and most importantly, requires significant maintenance and hacks for powering a modern dynamic application. (This should be enough to consider migration to something more flexible.)<\/p>\n<p>What is this \u201cmore flexible\u201d that we are going to migrate to then? To show a highly-practical case useful in real life, I had to choose among <a href=\"https:\/\/2018.stateofjs.com\/front-end-frameworks\/overview\/\">the two most popular frameworks<\/a> these days: React and Vue. However, whichever I would pick, we would miss some aspects of the other direction.<\/p>\n<div data-component=\"FeaturePanel\" data-audience=\"non-subscriber\" data-remove=\"true\"><\/div>\n<p>So in this part, we\u2019ll be running through both of the following:<\/p>\n<ul>\n<li>A migration of a jQuery application to <strong>React<\/strong>, and<\/li>\n<li>A migration of a jQuery application to <strong>Vue<\/strong>.<\/li>\n<\/ul>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/617ba9e0-16f0-4848-9a59-a5085e3fdc12\/react-vue-final.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/react-vue-final.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/617ba9e0-16f0-4848-9a59-a5085e3fdc12\/react-vue-final.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/617ba9e0-16f0-4848-9a59-a5085e3fdc12\/react-vue-final.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/617ba9e0-16f0-4848-9a59-a5085e3fdc12\/react-vue-final.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/617ba9e0-16f0-4848-9a59-a5085e3fdc12\/react-vue-final.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/react-vue-final.png?w=900\" sizes=\"100vw\" alt=\"Our goals: results of the migration to React and Vue\"><\/a><figcaption>\n      Our goals: results of the migration to React and Vue. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/617ba9e0-16f0-4848-9a59-a5085e3fdc12\/react-vue-final.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<h4>Code Repositories<\/h4>\n<p>All the code mentioned here is publicly available, and you can get to it whenever you want. There are two repositories available for you to play with:<\/p>\n<ul>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\">Frankenstein TodoMVC<\/a><br \/>\nThis repository contains TodoMVC <em>applications<\/em> in different frameworks\/libraries. For example, you can find branches like <code>vue<\/code>, <code>angularjs<\/code>, <code>react<\/code> and <code>jquery<\/code> in this repository.<\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\">Frankenstein Demo<\/a><br \/>\nIt contains several branches, each of which represents a particular migration <em>direction<\/em> between applications, available in the first repository. There are branches like <code>migration\/jquery-to-react<\/code>  and <code>migration\/jquery-to-vue<\/code>, in particular, that we\u2019ll be covering later on.<\/li>\n<\/ul>\n<p>Both repositories are work-in-progress and new branches with new applications and migration directions should be added to them regularly. (<em>You\u2019re free to contribute as well!<\/em>) Commits history in migration branches is well structured and might serve as additional documentation with even more details than I could cover in this article.<\/p>\n<p>Now, let\u2019s get our hands dirty! We have a long way ahead, so don\u2019t expect it to be a smooth ride. It\u2019s up to you to decide how you want to follow along with this article, but you could do the following:<\/p>\n<ul>\n<li>Clone the <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/tree\/jquery\"><code>jquery<\/code> branch from the Frankenstein TodoMVC<\/a> repository and strictly follow all of the instructions below.<\/li>\n<li>Alternatively, you can open a branch dedicated to either <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/tree\/migration\/jquery-to-react\">migration to React<\/a> or <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/commits\/migration\/jquery-to-vue\">migration to Vue<\/a> from the <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\">Frankenstein Demo<\/a> repository and follow along with commits history.<\/li>\n<li>Alternatively, you can relax and keep reading because I am going to highlight the most critical code right here, and it\u2019s much more important to understand the mechanics of the process rather than the actual code.<\/li>\n<\/ul>\n<p>I\u2019d like to mention one more time that we\u2019ll strictly be following the steps presented in <a href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-1\/\">the theoretical first part<\/a> of the article.<\/p>\n<p>Let\u2019s dive right in!<\/p>\n<ol>\n<li><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#identify-microservices\">Identify Microservices<\/a><\/li>\n<li><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#allow-host-to-alien-access\">Allow Host-to-Alien Access<\/a><\/li>\n<li><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#write-an-alien-microservice-component\">Write An Alien Microservice\/Component<\/a><\/li>\n<li><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#write-web-component-wrapper-around-alien-service\">Write Web Component Wrapper Around Alien Service<\/a><\/li>\n<li><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#replace-host-service-with-web-component\">Replace Host Service With Web Component<\/a><\/li>\n<li><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#rinse-and-repeat-for-all-of-your-components\">Rinse &#038; Repeat For All Of Your Components<\/a><\/li>\n<li><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#switch-to-alien\">Switch To Alien<\/a><\/li>\n<\/ol>\n<h3>1. Identify Microservices<\/h3>\n<p>As <a href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-1\/\">Part 1<\/a> suggests, in this step, we have to structure our application into <em>small<\/em>, <em>independent<\/em> services dedicated to <em>one particular job<\/em>. The attentive reader might notice that our to-do application is already small and independent and can represent one single microservice on its own. This is how I would treat it myself if this application would live in some broader context. Remember, however, that the process of identifying microservices is entirely subjective and there is no <em>one<\/em> correct answer.<\/p>\n<p>So, in order to see the process of Frankenstein Migration in more detail, we can go a step further and split this to-do application into two independent microservices:<\/p>\n<ol>\n<li><strong>An input field for adding a new item.<\/strong><br \/>\nThis service can also contain the application\u2019s header, based purely on positioning proximity of these elements.<\/li>\n<li><strong>A list of already added items.<\/strong><br \/>\nThis service is more advanced, and together with the list itself, it also contains actions like filtering, list item\u2019s actions, and so on.<\/li>\n<\/ol>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7a1e377b-7783-4900-a823-e33f9624a6fd\/todomvc-application-split-into-two-independent-microservices.jpg?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/todomvc-application-split-into-two-independent-microservices.jpg 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7a1e377b-7783-4900-a823-e33f9624a6fd\/todomvc-application-split-into-two-independent-microservices.jpg 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7a1e377b-7783-4900-a823-e33f9624a6fd\/todomvc-application-split-into-two-independent-microservices.jpg 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7a1e377b-7783-4900-a823-e33f9624a6fd\/todomvc-application-split-into-two-independent-microservices.jpg 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7a1e377b-7783-4900-a823-e33f9624a6fd\/todomvc-application-split-into-two-independent-microservices.jpg 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/todomvc-application-split-into-two-independent-microservices.jpg?w=900\" sizes=\"100vw\" alt=\"TodoMVC application split into two independent microservices\"><\/a><figcaption>\n      TodoMVC application split into two independent microservices. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7a1e377b-7783-4900-a823-e33f9624a6fd\/todomvc-application-split-into-two-independent-microservices.jpg\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p><strong>Tip<\/strong>: <em>To check whether the picked services are genuinely independent, remove HTML markup, representing each of these services. Make sure that the remaining functions still work. In our case, it should be possible to add new entries into<\/em> <code>localStorage<\/code> <em>(that this application is using as storage) from the input field without the list, while the list still renders the entries from<\/em> <code>localStorage<\/code> <em>even if the input field is missing. If your application throws errors when you remove markup for potential microservice, take a look at the \u201cRefactor If Needed\u201d section in <a href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-1\/\">Part 1<\/a> for an example of how to deal with such cases.<\/em><\/p>\n<p>Of course, we could go on and split the second service and the listing of the items even further into independent microservices for each particular item. However, it might be <a href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-1\/\">too granular<\/a> for this example. So, for now, we conclude that our application is going to have two services; they are independent, and each of them works towards its own particular task. Hence, we have split our application into <em>microservices<\/em>.<\/p>\n<h3>2. Allow Host-to-Alien Access<\/h3>\n<p>Let me briefly remind you of what these are.<\/p>\n<ul>\n<li><strong>Host<\/strong><br \/>\nThis is what our current application is called. It is written with the framework from which we\u2019re about to <em>move away<\/em> from. In this particular case, our jQuery application.<\/li>\n<li><strong>Alien<\/strong><br \/>\nSimply put, this one\u2019s a gradual re-write of Host on the new framework that we are about to <em>move to<\/em>. Again, in this particular case, it\u2019s a React or Vue application.<\/li>\n<\/ul>\n<p><strong>The rule of thumb<\/strong> when splitting Host and Alien is that you should be able to develop and deploy any of them without breaking the other one \u2014 at any point in time.<\/p>\n<p>Keeping Host and Alien independent from each other is crucial for Frankenstein Migration. However, this makes arranging communication between the two a bit challenging. How do we allow Host access Alien without smashing the two together?<\/p>\n<h4>Adding Alien As A Submodule Of Your Host<\/h4>\n<p>Even though there are several ways to achieve the setup we need, the simplest form of organizing your project to meet this criterion is probably <a href=\"https:\/\/git-scm.com\/book\/en\/v2\/Git-Tools-Submodules\">git submodules<\/a>. This is what we\u2019re going to use in this article. I\u2019ll leave it up to you to read carefully about <a href=\"https:\/\/git-scm.com\/book\/en\/v2\/Git-Tools-Submodules\">how submodules in git work<\/a> in order to understand <a href=\"https:\/\/github.blog\/2016-02-01-working-with-submodules\/\">limitations and gotchas<\/a> of this structure.<\/p>\n<p>The <strong>general principles of our project\u2019s architecture with git submodules<\/strong> should look like this:<\/p>\n<ul>\n<li>Both Host and Alien are independent and are kept in separate <code>git<\/code> repositories;<\/li>\n<li>Host references Alien as a submodule. At this stage, Host picks a particular state (commit) of Alien and adds it as, what looks like, a subfolder in Host\u2019s folder structure.<\/li>\n<\/ul>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/cf31ddca-5d04-4c9d-9fbe-204d215eafec\/git-submodule.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/git-submodule.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/cf31ddca-5d04-4c9d-9fbe-204d215eafec\/git-submodule.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/cf31ddca-5d04-4c9d-9fbe-204d215eafec\/git-submodule.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/cf31ddca-5d04-4c9d-9fbe-204d215eafec\/git-submodule.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/cf31ddca-5d04-4c9d-9fbe-204d215eafec\/git-submodule.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/git-submodule.png?w=900\" sizes=\"100vw\" alt=\"React TodoMVC added as a git submodule into jQuery TodoMVC application\"><\/a><figcaption>\n      React TodoMVC added as a git submodule into jQuery TodoMVC application. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/cf31ddca-5d04-4c9d-9fbe-204d215eafec\/git-submodule.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>The process of adding a submodule is the same for any application. Teaching <code>git submodules<\/code> is beyond the scope of this article and is not directly related to Frankenstein Migration itself. So let\u2019s just take a brief look at the possible examples.<\/p>\n<p>In the snippets below, we use the React direction as an example. For any other migration direction, replace <code>react<\/code> with the name of a branch from Frankenstein TodoMVC or adjust to custom values where needed.<\/p>\n<p>If you follow along using the original jQuery TodoMVC application:<\/p>\n<div>\n<pre><code>$ git submodule add -b react git@gitlab.com:mishunov\/frankenstein-todomvc.git react\n$ git submodule update --remote\n$ cd react\n$ npm i\n<\/code><\/pre>\n<\/div>\n<p>If you follow along with <code>migration\/jquery-to-react<\/code> (or any other migration direction) branch from the Frankenstein Demo repository, the Alien application should already be in there as a <code>git submodule<\/code>, and you should see a respective folder. However, the folder is empty by default, and you need to <a href=\"https:\/\/git-scm.com\/docs\/git-submodule#Documentation\/git-submodule.txt-update--init--remote-N--no-fetch--no-recommend-shallow-f--force--checkout--rebase--merge--referenceltrepositorygt--depthltdepthgt--recursive--jobsltngt--ltpathgt82308203\">update and initialize the registered submodules<\/a>.<\/p>\n<p>From the root of your project (your Host):<\/p>\n<pre><code>$ git submodule update --init\n$ cd react\n$ npm i\n<\/code><\/pre>\n<p>Note that in both cases we install dependencies for the Alien application, but those become sandboxed to the subfolder and won\u2019t pollute our Host.<\/p>\n<p>After adding the Alien application as a submodule of your Host, you get independent (in terms of microservices) Alien and Host applications. However, Host considers Alien a subfolder in this case, and obviously, that allows Host to access Alien without a problem.<\/p>\n<h3>3. Write An Alien Microservice\/Component<\/h3>\n<p>At this step, we have to decide what microservice to migrate first and write\/use it on the Alien\u2019s side. Let\u2019s follow the same order of services we identified in Step 1 and start with the first one: input field for adding a new item. However, before we begin, let\u2019s agree that beyond this point, we are going to use a more favorable term <strong>component<\/strong> instead of <em>microservice<\/em> or <em>service<\/em> as we are moving towards the premises of frontend frameworks and the term <strong>component<\/strong> follows the definitions of pretty much any modern framework.<\/p>\n<p>Branches of Frankenstein TodoMVC repository contain a resulting component that represents the first service \u201cInput field for adding a new item\u201d as a Header component:<\/p>\n<ul>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/components\/Header.js\">Header component in React<\/a><\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/vue\/src\/Header.vue\">Header component in Vue<\/a><\/li>\n<\/ul>\n<p>Writing components in the framework of your choice is beyond the scope of this article and is not part of Frankenstein Migration. However, there are a couple of things to keep in mind while writing an Alien component.<\/p>\n<h5>Independence<\/h5>\n<p>First of all, the components in Alien should follow the same principle of independence, previously set up on the Host\u2019s side: components should not depend on other components in any way.<\/p>\n<h5>Interoperability<\/h5>\n<p>Thanks to the independence of the services, most probably, components in your Host communicate in some well-established way be it a state management system, communication through some shared storage or, directly via a system of DOM events. \u201cInteroperability\u201d of Alien components means that they should be able to connect to the same source of communication, established by Host, to dispatch information about its state changes and listen to changes in other components. In practice, this means that if components in your Host communicate via DOM events, building your Alien component exclusively with state management in mind won\u2019t work flawlessly for this type of migration, unfortunately.<\/p>\n<p>As an example, <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/jquery\/js\/storage.js\">take a look at the <code>js\/storage.js<\/code> file<\/a> that is the primary communication channel for our jQuery components:<\/p>\n<div>\n<pre><code>...\n\nfetch: function() {\n  return JSON.parse(localStorage.getItem(STORAGE_KEY) || \"[]\");\n},\nsave: function(todos) {\n  localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));\n  var event = new CustomEvent(\"store-update\", { detail: { todos } });\n  document.dispatchEvent(event);\n},\n\n...\n<\/code><\/pre>\n<\/div>\n<p>Here, we use <code>localStorage<\/code> (as this example <a href=\"https:\/\/stackoverflow.com\/a\/3719435\/191426\">is not security-critical<\/a>) to store our to-do items, and once the changes to the storage get recorded, we dispatch a custom DOM event on the <code>document<\/code> element that any component can listen to.<\/p>\n<p>At the same time, on the Alien\u2019s side (let\u2019s say React) we can set up as complex state management communication as we want. However, it\u2019s probably smart to keep it for the future: to successfully integrate our Alien React component into Host, we have to connect to the same communication channel used by Host. In this case, it\u2019s <code>localStorage<\/code>. To make things simple, we just copied over Host\u2019s storage file into Alien and <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/components\/Header.js#L4\">hooked up our components to it<\/a>:<\/p>\n<pre><code>import todoStorage from \"..\/storage\";\n\nclass Header extends Component {\n  constructor(props) {\n    this.state = {\n      todos: todoStorage.fetch()\n    };\n  }\n  componentDidMount() {\n    document.addEventListener(\"store-update\", this.updateTodos);\n  }\n  componentWillUnmount() {\n    document.removeEventListener(\"store-update\", this.updateTodos);\n  }\n  componentDidUpdate(prevProps, prevState) {\n    if (prevState.todos !== this.state.todos) {\n      todoStorage.save(this.state.todos);\n    }\n  }\n  ...\n}\n<\/code><\/pre>\n<p>Now, our Alien components can talk the same language with Host components and vice versa.<\/p>\n<h3>4. Write Web Component Wrapper Around Alien Service<\/h3>\n<p>Even though we\u2019re now only on the fourth step, we have achieved quite a lot:<\/p>\n<ul>\n<li>We\u2019ve split our Host application into independent services which are ready to be replaced by Alien services;<\/li>\n<li>We\u2019ve set up Host and Alien to be completely independent of each other, yet very well connected via <code>git submodules<\/code>;<\/li>\n<li>We\u2019ve written our first Alien component using the new framework.<\/li>\n<\/ul>\n<p>Now it\u2019s time to set up a bridge between Host and Alien so that the new Alien component could function in the Host.<\/p>\n<p><strong>Reminder from <a href=\"https:\/\/www.smashingmagazine.com\/articles\/link\">Part 1<\/a><\/strong>: <em>Make sure that your Host has a package bundler available. In this article, we rely on Webpack, but it doesn\u2019t mean that the technique won\u2019t work with Rollup or any other bundler of your choice. However, I leave the mapping from Webpack to your experiments.<\/em><\/p>\n<h4>Naming Convention<\/h4>\n<p>As mentioned in the previous article, we are going to use <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Web_Components\">Web Components<\/a> to integrate Alien into Host. On the Host\u2019s side, we create a new file: <code>js\/frankenstein-wrappers\/Header-wrapper.js<\/code>. (It\u2019s going to be our first Frankenstein wrapper.) Keep in mind that it\u2019s a good idea to name your wrappers the same as your components in Alien application, e.g. just by adding a \u201c<code>-wrapper<\/code>\u201d suffix. You\u201dll see later on why this is a good idea, but for now, let\u2019s agree that this means that if the Alien component is called <code>Header.js<\/code> (in React) or <code>Header.vue<\/code> (in Vue), the corresponding wrapper on the Host\u2019s side should be called <code>Header-wrapper.js<\/code>.<\/p>\n<p>In our first wrapper, we begin with the fundamental <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Web_Components\/Using_custom_elements\">boilerplate for registering<\/a> a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Web_Components\/Using_custom_elements\">custom element<\/a>:<\/p>\n<div>\n<pre><code>class FrankensteinWrapper extends HTMLElement {}\ncustomElements.define(\"frankenstein-header-wrapper\", FrankensteinWrapper);\n<\/code><\/pre>\n<\/div>\n<p>Next, we have to initialize <strong>Shadow DOM<\/strong> for this element. <\/p>\n<p><a href=\"https:\/\/www.smashingmagazine.com\/articles\/#\">Please refer to Part 1<\/a> to get reasoning on why we use Shadow DOM.<\/p>\n<pre><code>class FrankensteinWrapper extends HTMLElement {\n  connectedCallback() {\n    this.attachShadow({ mode: \"open\" });\n  }\n}\n<\/code><\/pre>\n<p>With this, we have all the essential bits of the Web Component set up, and it\u2019s time to add our Alien component into the mix. First of all, at the beginning of our Frankenstein wrapper, we should import all the bits responsible for the Alien component\u2019s rendering.<\/p>\n<pre><code>import React from \"..\/..\/react\/node_modules\/react\";\nimport ReactDOM from \"..\/..\/react\/node_modules\/react-dom\";\nimport HeaderApp from \"..\/..\/react\/src\/components\/Header\";\n...\n<\/code><\/pre>\n<p>Here we have to pause for a second. Note that we do not import Alien\u2019s dependencies from Host\u2019s <code>node_modules<\/code>. Everything comes from the Alien itself that sits in <code>react\/<\/code> subfolder. That is why Step 2 is so important, and it is crucial to make sure the Host has full access to assets of Alien.<\/p>\n<p>Now, we can render our Alien component within Web Component\u2019s Shadow DOM:<\/p>\n<pre><code>...\nconnectedCallback() {\n  ...\n  ReactDOM.render(<HeaderApp \/>, this.shadowRoot);\n}\n...\n<\/code><\/pre>\n<p><strong>Note<\/strong>: <em>In this case, React doesn\u2019t need anything else. However, to render the Vue component, you need to add a wrapping node to contain your Vue component like the following:<\/em><\/p>\n<pre><code>...\nconnectedCallback() {\n  const mountPoint = document.createElement(\"div\");\n  this.attachShadow({ mode: \"open\" }).appendChild(mountPoint);\n  new Vue({\n    render: h => h(VueHeader)\n  }).$mount(mountPoint);\n}\n...\n<\/code><\/pre>\n<p><em>The reason for this is the difference in how React and Vue render components: React appends component to referenced DOM node, while Vue replaces referenced DOM node with the component. Hence, if we do<\/em> <code>.$mount(this.shadowRoot)<\/code> <em>for Vue, it essentially replaces the Shadow DOM.<\/em><\/p>\n<p>That\u2019s all we have to do to our wrapper for now. The current result for Frankenstein wrapper in both jQuery-to-React and jQuery-to-Vue migration directions can be found over here:<\/p>\n<ul>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/2f2456f5f11da1f0393fb94c41d3fa3708647061\/js\/frankenstein-wrappers\/Header-wrapper.js\">Frankenstein Wrapper for React component<\/a><\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/daec73379e3793c1f46d6df378dd95138ddf3f67\/frankenstein-wrappers\/header-wrapper.js\">Frankenstein Wrapper for Vue component<\/a><\/li>\n<\/ul>\n<p>To sum up the mechanics of the Frankenstein wrapper:<\/p>\n<ol>\n<li>Create a custom element,<\/li>\n<li>Initiate Shadow DOM,<\/li>\n<li>Import everything needed for rendering an Alien component,<\/li>\n<li>Render the Alien component within the custom element\u2019s Shadow DOM.<\/li>\n<\/ol>\n<p>However, this doesn\u2019t render our Alien in Host automatically. We have to replace the existing Host markup with our new Frankenstein wrapper.<\/p>\n<p>Fasten your seatbelts, it may not be as straightforward as one would expect!<\/p>\n<div><\/div>\n<h3>5. Replace Host Service With Web Component<\/h3>\n<p>Let\u2019s go on and add our new <code>Header-wrapper.js<\/code> file to <code>index.html<\/code> and replace the existing header markup with the newly-created <code><frankenstein-header-wrapper><\/code> custom element.<\/p>\n<div>\n<pre><code>...\n<!-- <header class=\"header\">-->\n<!--   <h1>todos<\/h1>-->\n<!--   <input class=\"new-todo\" placeholder=\"What needs to be done?\" autofocus>-->\n<!-- <\/header>-->\n\n<frankenstein-header-wrapper><\/frankenstein-header-wrapper>\n...\n<script type=\"module\"\n        src=\"js\/frankenstein-wrappers\/Header-wrapper.js\"><\/script>\n    <\/code><\/pre>\n<\/div>\n<p>Unfortunately, this won\u2019t work as simple as that. If you open a browser and check the console, there is the <code>Uncaught SyntaxError<\/code> waiting for you. Depending on the <a href=\"https:\/\/caniuse.com\/#search=modules\">browser and its support for ES6 modules<\/a>, it will either be related to ES6 imports or to the way the Alien component gets rendered. Either way, we have to do something about it, but the problem and solution should be familiar and clear to most of the readers.<\/p>\n<h4>5.1. Update Webpack and Babel where needed<\/h4>\n<p>We should involve some Webpack and Babel magic before integrating our Frankenstein wrapper. Wrangling these tools is beyond the scope of the article, but you can take a look at the corresponding commits in the Frankenstein Demo repository:<\/p>\n<ul>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/commit\/8e7b75d6e73380e03974d8e5d8adb4b7e240473a\">Configuration for migration to React<\/a><\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/commit\/8f1bb81cae6d057388c7dbf627d64c1789c92a4a\">Configuration for migration to Vue<\/a><\/li>\n<\/ul>\n<p>Essentially, we set up the processing of the files as well as <strong>a new entry point<\/strong> <code>frankenstein<\/code> <strong>in Webpack\u2019s configuration<\/strong> to contain everything related to Frankenstein wrappers in one place.<\/p>\n<p>Once Webpack in Host knows how to process the Alien component and Web Components, we\u2019re ready to replace Host\u2019s markup with the new Frankenstein wrapper.<\/p>\n<h4>5.2. Actual Component\u2019s Replacement<\/h4>\n<p>The component\u2019s replacement should be straightforward now. In <code>index.html<\/code> of your Host, do the following:<\/p>\n<ol>\n<li>Replace <code><br \/>\n<header class=\"header\"><\/code> DOM element with <code><frankenstein-header-wrapper><\/code>;<\/li>\n<li>Add a new script <code>frankenstein.js<\/code>. This is the new entry point in Webpack that contains everything related to Frankenstein wrappers.<\/li>\n<\/ol>\n<pre><code>...\n<!-- We replace <header class=\"header\"> -->\n<frankenstein-header-wrapper><\/frankenstein-header-wrapper>\n...\n<script src=\".\/frankenstein.js\"><\/script>\n<\/code><\/pre>\n<p>That\u2019s it! Restart your server if needed and witness the magic of the Alien component integrated into Host.<\/p>\n<p>However, something still seemd to be is missing. The Alien component in the Host context doesn\u2019t look the same way as it does in the context of the standalone Alien application. It\u2019s simply unstyled.<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/0fd03322-0ff8-46ca-8941-2506eb339974\/react-unstyled.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/react-unstyled.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/0fd03322-0ff8-46ca-8941-2506eb339974\/react-unstyled.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/0fd03322-0ff8-46ca-8941-2506eb339974\/react-unstyled.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/0fd03322-0ff8-46ca-8941-2506eb339974\/react-unstyled.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/0fd03322-0ff8-46ca-8941-2506eb339974\/react-unstyled.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/react-unstyled.png?w=900\" sizes=\"100vw\" alt=\"Unstyled Alien React component after being integrated into Host\"><\/a><figcaption>\n      Unstyled Alien React component after being integrated into Host (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/0fd03322-0ff8-46ca-8941-2506eb339974\/react-unstyled.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Why is it so? Shouldn\u2019t the component\u2019s styles be integrated with the Alien component into Host automatically? I wish they would, but as in too many situations, it depends. We\u2019re getting to the challenging part of Frankenstein Migration.<\/p>\n<h4>5.3. General Information On The Styling Of The Alien Component<\/h4>\n<p>First of all, the irony is that there is no bug in the way things work. Everything is as it\u2019s designed to work. To explain this, let\u2019s briefly mention different ways of styling components.<\/p>\n<h5>Global Styles<\/h5>\n<p>We all are familiar with these: global styles can be (and usually are) distributed without any particular component and get applied to the whole page. Global styles affect all DOM nodes with matching selectors.<\/p>\n<p>A few examples of global styles are <code><\/p>\n<style><\/code> and <code><link rel=\"stylesheet\"><\/code> tags found into your <code>index.html<\/code>. Alternatively, a global stylesheet can be imported into some root JS module so that all components could get access to it as well.<\/p>\n<p>The problem of styling applications in this way is obvious: maintaining monolithic stylesheets for large applications becomes very hard. Also, as we saw in the previous article, <a href=\"https:\/\/codesandbox.io\/embed\/vue-component-with-css-modules-sfxf0?fontsize=14&#038;module=%2Findex.html\">global styles can easily break components<\/a> that are rendered straight in the main DOM tree like it is in React or Vue.<\/p>\n<h5>Bundled Styles<\/h5>\n<p>These styles usually are tightly coupled with a component itself and are rarely distributed without the component. The styles typically reside in the same file with the component. Good examples of this type of styling are <a href=\"https:\/\/www.styled-components.com\/\">styled-components<\/a> in React or <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/css-modules.html\">CSS Modules<\/a> and <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/scoped-css.html\">Scoped CSS<\/a> in <a href=\"https:\/\/vuejs.org\/v2\/guide\/single-file-components.html\">single file components<\/a> in Vue. However, no matter the variety of tools for writing bundled styles, the underlying principle in most of them is the same: the tools provide a scoping mechanism to lock down styles defined in a component so that the styles don\u2019t break other components or global styles.<\/p>\n<h5>Why Could Scoped Styles Be Fragile?<\/h5>\n<p>In Part 1, when justifying the use of Shadow DOM in Frankenstein Migration, we briefly covered the topic of <a href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-1\/\">scoping vs. encapsulation<\/a>)  and how encapsulation of Shadow DOM is different from scoping styling tools. However, we did not explain why scoping tools provide such fragile styling for our components, and now, when we faced the unstyled Alien component, it becomes essential for understanding.<\/p>\n<p>All scoping tools for modern frameworks work similarly:<\/p>\n<ul>\n<li>You write styles for your component in some way without thinking much about scope or encapsulation;<\/li>\n<li>You run your components with imported\/embedded stylesheets through some bundling system, like <a href=\"https:\/\/webpack.js.org\/\">Webpack<\/a> or <a href=\"https:\/\/rollupjs.org\/\">Rollup<\/a>;<\/li>\n<li>The bundler generates unique CSS classes or other attributes, creating and injecting individual selectors for both your HTML and corresponding stylesheets;<\/li>\n<li>The bundler makes a <code><br \/>\n<style><\/code> entry in the <code><head><\/code> of your document and puts your components\u2019 styles with unique mingled selectors in there.<\/li>\n<\/ul>\n<p>That\u2019s pretty much it. It does work and works fine in many cases. Except for when it does not: when styles for all components live in the global styling scope, it becomes easy to break those, for example, using <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Specificity\">higher specificity<\/a>. This explains the potential fragility of scoping tools, but why is our Alien component completely unstyled?<\/p>\n<p>Let\u2019s take a look at the current Host using DevTools. When inspecting the newly-added Frankenstein wrapper with the Alien React component, for example, we can see something like this:<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d746c522-b1df-4edc-9548-4f52ce263903\/component-mingled-classes.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-mingled-classes.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d746c522-b1df-4edc-9548-4f52ce263903\/component-mingled-classes.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d746c522-b1df-4edc-9548-4f52ce263903\/component-mingled-classes.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d746c522-b1df-4edc-9548-4f52ce263903\/component-mingled-classes.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d746c522-b1df-4edc-9548-4f52ce263903\/component-mingled-classes.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-mingled-classes.png\" sizes=\"100vw\" alt=\"Frankenstein wrapper with Alien component inside. Note unique CSS classes on the Alien\u2019s nodes.\"><\/a><figcaption>\n      Frankenstein wrapper with Alien component inside. Note unique CSS classes on the Alien\u2019s nodes. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/d746c522-b1df-4edc-9548-4f52ce263903\/component-mingled-classes.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>So, Webpack does generate unique CSS classes for our component. Great! Where are the styles then? Well, the styles are precisely where they are designed to be \u2014 in the document\u2019s <code><head><\/code>.<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/906238b4-b9c1-4d6e-969c-7246aa48429e\/component-stylesheet.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-stylesheet.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/906238b4-b9c1-4d6e-969c-7246aa48429e\/component-stylesheet.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/906238b4-b9c1-4d6e-969c-7246aa48429e\/component-stylesheet.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/906238b4-b9c1-4d6e-969c-7246aa48429e\/component-stylesheet.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/906238b4-b9c1-4d6e-969c-7246aa48429e\/component-stylesheet.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-stylesheet.png\" sizes=\"100vw\" alt=\"While Alien component is within Frankenstein wrapper, its styles are in document\u2019s head.\"><\/a><figcaption>\n      While Alien component is within Frankenstein wrapper, its styles are in document\u2019s <code><head><\/code>. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/906238b4-b9c1-4d6e-969c-7246aa48429e\/component-stylesheet.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>So everything works as it should, and this is the main problem. Since our Alien component resides in Shadow DOM, and as <a href=\"https:\/\/www.smashingmagazine.com\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-1\/\">explained in Part #1<\/a>, Shadow DOM provides full encapsulation of components from the rest of the page and global styles, including those newly-generated stylesheets for the component that cannot cross the shadow border and get to the Alien component. Hence, the Alien component is left unstyled. However, now, the tactics of solving the problem should be clear: we should somehow place the component\u2019s styles in the same Shadow DOM where our component resides (instead of the document\u2019s <code><head><\/code>).<\/p>\n<h4>5.4. Fixing Styles For The Alien Component<\/h4>\n<p>Up until now, the process of migrating to any framework was the same. However, things start diverging here: every framework has its recommendations on how to style components, and hence, the ways of tackling the problem differ. Here, we discuss most common cases but, if the framework you work with uses some unique way of styling components, you need to keep in mind the basic tactics such as putting the component\u2019s styles into Shadow DOM instead of <code><head><\/code>.<\/p>\n<p>In this chapter, we are covering fixes for:<\/p>\n<ul>\n<li>Bundled styles with <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/css-modules.html\">CSS Modules in Vue<\/a> (tactics for <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/scoped-css.html\">Scoped CSS<\/a> are the same);<\/li>\n<li>Bundled styles with <a href=\"https:\/\/www.styled-components.com\/\">styled-components<\/a> in React;<\/li>\n<li><a href=\"https:\/\/github.com\/css-modules\/css-modules\">Generic CSS Modules<\/a> and global styles. I combine these because CSS Modules, in general, are very similar to the global stylesheets and can be imported by any component making the styles disconnected from any particular component.<\/li>\n<\/ul>\n<p>Constraints first: <strong>anything we do to fix styling should not break the Alien component itself<\/strong>. Otherwise, we lose the independence of our Alien and Host systems. So, to address the styling issue, we are going to rely on either bundler\u2019s configuration or the Frankenstein wrapper.<\/p>\n<h5>Bundled Styles In Vue And Shadow DOM<\/h5>\n<p>If you\u2019re writing a Vue application, then you\u2019re most probably using <a href=\"https:\/\/vuejs.org\/v2\/guide\/single-file-components.html\">single file components<\/a>. If you\u2019re also using Webpack, you should be familiar with two loaders <code>vue-loader<\/code> and <code>vue-style-loader<\/code>. The former allows you to write those single file components while the latter dynamically injects the component\u2019s CSS into a document as a <code><\/p>\n<style><\/code> tag. By default, <code>vue-style-loader<\/code> injects the component\u2019s styles into the document\u2019s <code><head><\/code>. However, both packages accept the <code>shadowMode<\/code> option in configuration which allows us to easily change the default behavior and inject styles (as the option\u2019s name implies) into Shadow DOM. Let\u2019s see it in action.<\/p>\n<p><strong>Webpack Configuration<\/strong><\/p>\n<p>At a bare minimum, <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-vue\/config\/webpack.dev.js\">the Webpack configuration file<\/a> should contain the following:<\/p>\n<pre><code>const VueLoaderPlugin = require('vue-loader\/lib\/plugin');\n...\nmodule: {\n  rules: [\n    {\n      test: \/.vue$\/,\n      loader: 'vue-loader',\n      options: {\n        shadowMode: true\n      }\n    },\n    {\n      test: \/.css$\/,\n      include: path.resolve(__dirname, '..\/vue'),\n      use: [\n        {\n          loader:'vue-style-loader',\n          options: {\n            shadowMode: true\n          }\n        },\n        'css-loader'\n      ]\n    }\n  ],\n  plugins: [\n    new VueLoaderPlugin()\n  ]\n}\n<\/code><\/pre>\n<p>In a real application, your <code>test: \/.css$\/<\/code> block will be more sophisticated (probably involving the <a href=\"https:\/\/webpack.js.org\/configuration\/module\/#ruleoneof\"><code>oneOf<\/code> rule<\/a>) to account for both Host and Alien configurations. However, in this case, our jQuery is styled with simple <code><link rel=\"stylesheet\"><\/code> in <code>index.html<\/code>, so we don\u2019t build styles for Host via Webpack, and it\u2019s safe to cater for Alien only.<\/p>\n<p><strong>Wrapper Configuration<\/strong><\/p>\n<p>In addition to Webpack configuration, we also need to update our Frankenstein wrapper, pointing Vue to the correct Shadow DOM. In our <code>Header-wrapper.js<\/code>, rendering of the Vue component should include the <code>shadowRoot<\/code> property leading to <code>shadowRoot<\/code> of our Frankenstein wrapper:<\/p>\n<pre><code>...\nnew Vue({\n  shadowRoot: this.shadowRoot,\n  render: h => h(VueHeader)\n}).$mount(mountPoint);\n...\n<\/code><\/pre>\n<p>After you update the files and restart your server, you should be getting something like this in your DevTools:<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/f51da161-b591-4662-8d1a-f789653987b5\/component-styles-within-shadow.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-styles-within-shadow.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/f51da161-b591-4662-8d1a-f789653987b5\/component-styles-within-shadow.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/f51da161-b591-4662-8d1a-f789653987b5\/component-styles-within-shadow.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/f51da161-b591-4662-8d1a-f789653987b5\/component-styles-within-shadow.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/f51da161-b591-4662-8d1a-f789653987b5\/component-styles-within-shadow.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-styles-within-shadow.png\" sizes=\"100vw\" alt=\"Styles bundled with Alien Vue component placed within Frankenstein wrapper with all unique CSS classes preserved.\"><\/a><figcaption>\n      Styles bundled with Alien Vue component placed within Frankenstein wrapper with all unique CSS classes preserved. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/f51da161-b591-4662-8d1a-f789653987b5\/component-styles-within-shadow.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Finally, styles for the Vue component are within our Shadow DOM. At the same time, your application should look like this:<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/656596c1-5e84-48e5-b6cc-347b2bc340e8\/app-vue-halfway.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/app-vue-halfway.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/656596c1-5e84-48e5-b6cc-347b2bc340e8\/app-vue-halfway.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/656596c1-5e84-48e5-b6cc-347b2bc340e8\/app-vue-halfway.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/656596c1-5e84-48e5-b6cc-347b2bc340e8\/app-vue-halfway.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/656596c1-5e84-48e5-b6cc-347b2bc340e8\/app-vue-halfway.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/app-vue-halfway.png\" sizes=\"100vw\" alt=\"Header component starts to look more like it should. However, something is still missing.\"><\/a><figcaption>\n      Header component starts to look more like it should. However, something is still missing. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/656596c1-5e84-48e5-b6cc-347b2bc340e8\/app-vue-halfway.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>We start getting something resembling our Vue application: styles bundled with the component, are injected into the wrapper\u2019s Shadow DOM, but the component still looks not as it is supposed to. The reason is that in the original Vue application, the component is styled not only with the bundled styles but also partially with global styles. However, before fixing the global styles, we have to get our React integration to the same state as the Vue one.<\/p>\n<h5>Bundled Styles In React And Shadow DOM<\/h5>\n<p>Because there are many ways one can style a React component, the particular solution to fix an Alien component in Frankenstein Migration depends on the way we style the component in the first place. Let\u2019s briefly cover the most commonly used alternatives.<\/p>\n<p><strong>styled-components<\/strong><\/p>\n<p><a href=\"https:\/\/www.styled-components.com\/\">styled-components<\/a> is one of the most popular ways of styling React components. For the <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/components\/Header.js\">Header React component<\/a>, styled-components is precisely the way we style it. Since this is a classic CSS-in-JS approach, there is no file with a dedicated extension that we could hook our bundler onto as we do for <code>.css<\/code> or <code>.js<\/code> files, for example. Luckily, styled-components allow the injection of component\u2019s styles into a custom node (Shadow DOM in our case) instead of the document\u2019s <code>head<\/code>with the help of the <a href=\"https:\/\/www.styled-components.com\/docs\/api#stylesheetmanager\"><code>StyleSheetManager<\/code> helping component<\/a>. It is a pre-defined component, installed with the <code>styled-components<\/code> package that accepts <code>target<\/code> property, defining \u201can alternate DOM node to inject styles info\u201d. Exactly what we need! Moreover, we do not even need to change our Webpack configuration: everything is up to our Frankenstein wrapper.<\/p>\n<p>We should update our <code>Header-wrapper.js<\/code> that contains the React Alien component with the following lines:<\/p>\n<div>\n<pre><code>...\nimport { StyleSheetManager } from \"..\/..\/react\/node_modules\/styled-components\";\n...\nconst target = this.shadowRoot;\nReactDOM.render(\n  <StyleSheetManager target={target}>\n    <HeaderApp \/>\n  <\/StyleSheetManager>,\n  appWrapper\n);\n...\n<\/code><\/pre>\n<\/div>\n<p>Here, we import the <code>StyleSheetManager<\/code> component (from Alien, and not from Host) and wrap our React component with it. At the same time, we send the <code>target<\/code> property pointing to our <code>shadowRoot<\/code>. That\u2019s it. If you restart the server, you have to see something like this in your DevTools:<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/9ab04018-3cf4-4d8c-9bd1-ce854bef5adf\/style-sheet-manager.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/style-sheet-manager.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/9ab04018-3cf4-4d8c-9bd1-ce854bef5adf\/style-sheet-manager.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/9ab04018-3cf4-4d8c-9bd1-ce854bef5adf\/style-sheet-manager.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/9ab04018-3cf4-4d8c-9bd1-ce854bef5adf\/style-sheet-manager.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/9ab04018-3cf4-4d8c-9bd1-ce854bef5adf\/style-sheet-manager.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/style-sheet-manager.png\" sizes=\"100vw\" alt=\"Styles bundled with React Alien component placed within Frankenstein wrapper with all unique CSS classes preserved.\"><\/a><figcaption>\n      Styles bundled with React Alien component placed within Frankenstein wrapper with all unique CSS classes preserved. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/9ab04018-3cf4-4d8c-9bd1-ce854bef5adf\/style-sheet-manager.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Now, our component\u2019s styles are in Shadow DOM instead of <code><head><\/code>. This way, the rendering of our app now resembles what we have seen with the Vue app previously.<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b1435d79-272f-4b04-825c-d7df79bd4de2\/component-react-halfway.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-react-halfway.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b1435d79-272f-4b04-825c-d7df79bd4de2\/component-react-halfway.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b1435d79-272f-4b04-825c-d7df79bd4de2\/component-react-halfway.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b1435d79-272f-4b04-825c-d7df79bd4de2\/component-react-halfway.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b1435d79-272f-4b04-825c-d7df79bd4de2\/component-react-halfway.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/component-react-halfway.png\" sizes=\"100vw\" alt=\"After moving bundled styles into Frankenstein wrapper, the Alien React component begins to look better. However, we\u2019re not there yet.\"><\/a><figcaption>\n      After moving bundled styles into Frankenstein wrapper, the Alien React component begins to look better. However, we\u2019re not there yet. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/b1435d79-272f-4b04-825c-d7df79bd4de2\/component-react-halfway.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Same story: <strong>styled-components are responsible just for the bundled part of the React component\u2019s styles<\/strong>, and 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.<\/p>\n<p><strong>CSS Modules<\/strong><\/p>\n<p>If you take a closer look at the <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/vue\/src\/Header.vue\">Vue component<\/a> that we have fixed earlier, you might notice that <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/css-modules.html\">CSS Modules<\/a> is precisely the way we style that component. However, even if we style it with <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/scoped-css.html\">Scoped CSS<\/a> (another recommended way of styling Vue components) the way we fix our unstyled component doesn\u2019t change: it is still up to <code>vue-loader<\/code> and <code>vue-style-loader<\/code> to handle it through <code>shadowMode: true<\/code> option.<\/p>\n<p>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.<\/p>\n<p>Let\u2019s take a look at the same React component which we\u2019ve just integrated, but this time <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react-css-modules\/src\/components\/Header.js\">styled with CSS Modules instead of styled-components<\/a>. The main thing to note in this component is a separate import for stylesheet:<\/p>\n<pre><code>import styles from '.\/Header.module.css'\n<\/code><\/pre>\n<p>The <code>.module.css<\/code> extension is a standard way to tell React applications built with <a href=\"https:\/\/github.com\/facebook\/create-react-app\">the <code>create-react-app<\/code> utility<\/a> that the imported stylesheet is a CSS Module. <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react-css-modules\/src\/components\/Header.module.css\">The stylesheet itself<\/a> is very basic and does precisely the same our styled-components do.<\/p>\n<p>Integrating CSS modules into a Frankenstein wrapper consists of two parts:<\/p>\n<ul>\n<li>Enabling CSS Modules in bundler,<\/li>\n<li>Pushing resulting stylesheet into Shadow DOM.<\/li>\n<\/ul>\n<p>I believe the first point is trivial: all you need to do is <a href=\"https:\/\/webpack.js.org\/loaders\/css-loader\/#modules\">set <code>{ modules: true }<\/code> for <code>css-loader<\/code><\/a> in your Webpack configuration. Since, in this particular case, we have a dedicated extension for our CSS Modules (<code>.module.css<\/code>), we can have a dedicated configuration block for it under the general <code>.css<\/code> configuration:<\/p>\n<pre><code>{\n  test: \/.css$\/,\n  oneOf: [\n    {\n      test: \/.module.css$\/,\n      use: [\n        ...\n        {\n          loader: 'css-loader',\n          options: {\n            modules: true,\n          }\n        }\n      ]\n    }\n  ]\n}\n<\/code><\/pre>\n<p><strong>Note<\/strong>: <em>A<\/em> <code>modules<\/code> <em>option for<\/em> <code>css-loader<\/code> <em>is all we have to know about CSS Modules no matter whether it\u2019s 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.<\/em><\/p>\n<p>By now, we went through the ways of integrating <em>bundled styles<\/em> into Shadow DOM for the following conventional scenarios:<\/p>\n<ul>\n<li>Vue components, styled with CSS Modules. Dealing with <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/scoped-css.html\">Scoped CSS<\/a> in Vue components won\u2019t be any different;<\/li>\n<li>React components, styled with styled-components;<\/li>\n<li>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.<\/li>\n<\/ul>\n<p>However, our components still don\u2019t look as they are supposed to because their styles partially come from <em>global styles<\/em>. 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\u2019s finally fix this side of the story.<\/p>\n<p><strong>Global Styles And Shadow DOM<\/strong><\/p>\n<p>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.<\/p>\n<p>Nevertheless, it\u2019s 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.<\/p>\n<p>So having an opportunity to pull in global styles into our Frankenstein wrappers wherever it\u2019s 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 \u2014 be it React, Vue or anything else using global stylesheets!<\/p>\n<p>Let\u2019s get back to our <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/vue\/src\/Header.vue\">Header component from the Vue application<\/a>. Take a look at <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/vue\/src\/Header.vue#L17\">this import<\/a>:<\/p>\n<pre><code>import \"todomvc-app-css\/index.css\";\n<\/code><\/pre>\n<p>This import is where we pull in the global stylesheet. In this case, we do it from the component itself. It\u2019s only one way of using global stylesheet to style your component, but it\u2019s not necessarily like this in your application.<\/p>\n<p>Some parent module might add a global stylesheet like in our React application where we <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/index.js#L4\">import <code>index.css<\/code> only in <code>index.js<\/code><\/a>, and then our components expect it to be available in the global scope. Your component\u2019s styling might even rely on a stylesheet, added with <code><\/p>\n<style><\/code> or <code><link><\/code> to your <code>index.html<\/code>. It doesn\u2019t matter. What matters, however, is that you should expect to either import global stylesheets in your Alien component (if it doesn\u2019t harm the Alien application) or explicitly in the Frankenstein wrapper. Otherwise, the wrapper would not know that the Alien component needs any stylesheet other than the ones already bundled with it.<\/p>\n<blockquote>\n<p><strong>Caution<\/strong>. If there are many global stylesheets to be shared between Alien components and you have a lot of such components, this might harm the performance of your Host application under the migration period.<\/p>\n<\/blockquote>\n<p>Here is how import of a global stylesheet, required for the Header component, is done in <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-react\/js\/frankenstein-wrappers\/Header-wrapper.js#L10\">Frankenstein wrapper for React component<\/a>:<\/p>\n<pre><code>\/\/ we import directly from react\/, not from Host\nimport '..\/..\/react\/node_modules\/todomvc-app-css\/index.css'\n<\/code><\/pre>\n<p>Nevertheless, by importing a stylesheet this way, we still bring the styles to the global scope of our Host, while what we need is to pull in the styles into our Shadow DOM. How do we do this?<\/p>\n<p><strong>Webpack configuration for global stylesheets & Shadow DOM<\/strong><\/p>\n<p>First of all, you might want to add an explicit test to make sure that we process only the stylesheets coming from our Alien. In case of our React migration, it will look similar to this:<\/p>\n<pre><code>test: \/.css$\/,\noneOf: [\n  \/\/ this matches stylesheets coming from \/react\/ subfolder\n  {\n    test: \/\/react\/\/,\n    use: []\n  },\n  ...\n]\n<\/code><\/pre>\n<p>In case of Vue application, obviously, you change <code>test: \/\/react\/\/<\/code> with something like <code>test: \/\/vue\/\/<\/code>. Apart from that, the configuration will be the same for any framework. Next, let\u2019s specify the required loaders for this block.<\/p>\n<pre><code>...\nuse: [\n  {\n    loader: 'style-loader',\n    options: {\n      ...\n    }\n  },\n  'css-loader'\n]\n<\/code><\/pre>\n<p>Two things to note. First, you have to specify <code>modules: true<\/code> in <code>css-loader<\/code>\u2019s configuration if you\u2019re processing CSS Modules of your Alien application.<\/p>\n<p>Second, we should convert styles into <code><\/p>\n<style><\/code> tag before injecting those into Shadow DOM. In the case of Webpack, for that, we use <a href=\"https:\/\/webpack.js.org\/loaders\/style-loader\/\"><code>style-loader<\/code><\/a>. The default behavior for this loader is to insert styles into the document\u2019s head. Typically. And this is precisely what we don\u2019t want: our goal is to get stylesheets into Shadow DOM. However, in the same way we used <code>target<\/code> property for styled-components in React or <code>shadowMode<\/code> option for Vue components that allowed us to specify custom insertion point for our <code><\/p>\n<style><\/code> tags, regular <code>style-loader<\/code> provides us with nearly same functionality for any stylesheet: <a href=\"https:\/\/webpack.js.org\/loaders\/style-loader\/#insert\">the <code>insert<\/code> configuration option<\/a> is exactly what helps us achieve our primary goal. Great news! Let\u2019s add it to our configuration.<\/p>\n<pre><code>...\n{\n  loader: 'style-loader',\n  options: {\n    insert: 'frankenstein-header-wrapper'\n  }\n}\n<\/code><\/pre>\n<p>However, not everything is so smooth here with a couple of things to keep in mind.<\/p>\n<p><strong>Global stylesheets and <code>insert<\/code> option of <code>style-loader<\/code><\/strong><\/p>\n<p>If you check <a href=\"https:\/\/webpack.js.org\/loaders\/style-loader\/#insert\">documentation for this option<\/a>, you notice, that this option takes one selector per configuration. This means that if you have several Alien components requiring global styles pulled into a Frankenstein wrapper, you have to specify <code>style-loader<\/code> for each of the Frankenstein wrappers. In practice, this means that you, probably, have to rely on <a href=\"https:\/\/webpack.js.org\/configuration\/module\/#ruleoneof\"><code>oneOf<\/code> rule<\/a> in your configuration block to serve to all wrappers.<\/p>\n<pre><code>{\n  test: \/\/react\/\/,\n  oneOf: [\n    {\n      test: \/1-TEST-FOR-ALIEN-FILE-PATH$\/,\n      use: [\n        {\n          loader: 'style-loader',\n          options: {\n            insert: '1-frankenstein-wrapper'\n          }\n        },\n        `css-loader`\n      ]\n    },\n    {\n      test: \/2-TEST-FOR-ALIEN-FILE-PATH$\/,\n      use: [\n        {\n          loader: 'style-loader',\n          options: {\n            insert: '2-frankenstein-wrapper'\n          }\n        },\n        `css-loader`\n      ]\n    },\n    \/\/ etc.\n  ],\n}\n<\/code><\/pre>\n<p>Not very flexible, I agree. Nevertheless, it\u2019s not a big deal as long as you don\u2019t have hundreds of components to migrate. Otherwise, it might make your Webpack configuration hard to maintain. The real problem, however, is that we can not write a CSS selector for Shadow DOM.<\/p>\n<p>Trying to solve this, we might note that the <code>insert<\/code> option can also take a function instead of a plain selector to specify <a href=\"https:\/\/webpack.js.org\/loaders\/style-loader\/#insert-styles-at-top\">more advanced logic for insertion<\/a>. With this, we can use this option to insert stylesheets straight into Shadow DOM! In simplified form it might look similar to this:<\/p>\n<div>\n<pre><code>insert: function(element) {\n  var parent = document.querySelector('frankenstein-header-wrapper').shadowRoot;\n  parent.insertBefore(element, parent.firstChild);\n}\n<\/code><\/pre>\n<\/div>\n<p>Tempting, isn\u2019t it? However, this won\u2019t work for our scenario or will work far from optimal. Our <code><frankenstein-header-wrapper><\/code> is indeed available from <code>index.html<\/code> (because we added it in Step 5.2). But when Webpack processes all dependencies (incl. the stylesheets) for either an Alien component or a Frankenstein wrapper, Shadow DOM is not yet initialized in the Frankenstein wrapper: imports are processed before that. Hence, pointing <code>insert<\/code> straight to shadowRoot will result in an error.<\/p>\n<p>There is only one case when we can guarantee that Shadow DOM is initialized before Webpack processes our stylesheet dependency. If Alien component does not import a stylesheet itself and it becomes up to Frankenstein wrapper to import it, we might employ <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/import#Dynamic_Import\">dynamic import<\/a> and import the required stylesheet after we set up Shadow DOM:<\/p>\n<pre><code>this.attachShadow({ mode: \"open\" });\nimport('..\/vue\/node_modules\/todomvc-app-css\/index.css');\n<\/code><\/pre>\n<p>This will work: such import, combined with the <code>insert<\/code> configuration above, will indeed find correct Shadow DOM and insert <code><\/p>\n<style><\/code> tag into it. Nevertheless, getting and processing stylesheet will take time, which means your users on a slow connection or slow devices might face a moment of the unstyled component before your stylesheet gets on its place within wrapper\u2019s Shadow DOM.<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3de31daf-fae0-42ce-bfd5-a99f821ea438\/2-frankenstein-approach-framework-agnostic-migration-part2-large.gif\"><img decoding=\"async\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/2-frankenstein-approach-framework-agnostic-migration-part2.gif\" width=\"800\" height=\"\" alt=\"Unstyled Alien component gets rendered before the global stylesheet is imported and added to the Shadow DOM.\"><\/a><figcaption>Unstyled Alien component gets rendered before the global stylesheet is imported and added to the Shadow DOM. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3de31daf-fae0-42ce-bfd5-a99f821ea438\/2-frankenstein-approach-framework-agnostic-migration-part2-large.gif\">Large preview<\/a>)<\/figcaption><\/figure>\n<p>So all in all, even though <code>insert<\/code> accepts function, unfortunately, it\u2019s not enough for us, and we have to fall back to plain CSS selectors like <code>frankenstein-header-wrapper<\/code>. This doesn\u2019t place stylesheets into Shadow DOM automatically, however, and the stylesheets reside in <code><frankenstein-header-wrapper><\/code> outside of Shadow DOM.<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/1fc849fd-29a3-4686-97e2-b45e3a028f86\/style-outside-shadow.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/style-outside-shadow.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/1fc849fd-29a3-4686-97e2-b45e3a028f86\/style-outside-shadow.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/1fc849fd-29a3-4686-97e2-b45e3a028f86\/style-outside-shadow.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/1fc849fd-29a3-4686-97e2-b45e3a028f86\/style-outside-shadow.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/1fc849fd-29a3-4686-97e2-b45e3a028f86\/style-outside-shadow.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/style-outside-shadow.png\" sizes=\"100vw\" alt=\"style-loader puts imported stylesheet into the Frankenstein wrapper, but outside of Shadow DOM.\"><\/a><figcaption><code>style-loader<\/code> puts imported stylesheet into the Frankenstein wrapper, but outside of Shadow DOM. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/1fc849fd-29a3-4686-97e2-b45e3a028f86\/style-outside-shadow.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>We need one more piece of the puzzle.<\/p>\n<p><strong>Wrapper configuration for global stylesheets & Shadow DOM<\/strong><\/p>\n<p>Luckily, the fix is quite straightforward on the wrapper\u2019s side: when Shadow DOM gets initialized, we need to check for any pending stylesheets in the current wrapper and pull them into Shadow DOM.<\/p>\n<p>The current state of the global stylesheet\u2019s import is as follows:<\/p>\n<ul>\n<li>We import a stylesheet that has to be added into Shadow DOM. The stylesheet can be imported in either the Alien component itself or, explicitly in the Frankenstein wrapper. In the case of migration to React, for example, <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-react\/js\/frankenstein-wrappers\/Header-wrapper.js#L10\">the import is initialized from the wrapper<\/a>. However, in migration to Vue, the similar <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/vue\/src\/Header.vue#L17\">component itself imports the required stylesheet<\/a>, and we don\u2019t have to import anything in the wrapper.<\/li>\n<li>As pointed out above, when Webpack processes <code>.css<\/code> imports for the Alien component, thanks to the <code>insert<\/code> option of <code>style-loader<\/code>, the stylesheets get injected into a Frankenstein wrapper, but outside of Shadow DOM.<\/li>\n<\/ul>\n<p>Simplified initialization of Shadow DOM in Frankenstein wrapper, should currently (before we pull in any stylesheets) look similar to this:<\/p>\n<pre><code>this.attachShadow({ mode: \"open\" });\nReactDOM.render(); \/\/ or `new Vue()`\n<\/code><\/pre>\n<p>Now, to avoid flickering of the unstyled component, what we need to do now is pull in all the required stylesheets <em>after<\/em> initialization of the Shadow DOM, but <em>before<\/em> the Alien component\u2019s rendering.<\/p>\n<pre><code>this.attachShadow({ mode: \"open\" });\n\nArray.prototype.slice\n  .call(this.querySelectorAll(\"style\"))\n  .forEach(style => {\n    this.shadowRoot.prepend(style);\n  });\n\nReactDOM.render(); \/\/ or new Vue({})\n<\/code><\/pre>\n<p>It was a long explanation with a lot of details, but mainly, all it takes to pull in global stylesheets into Shadow DOM:<\/p>\n<ul>\n<li>In Webpack configuration add <code>style-loader<\/code> with <code>insert<\/code> option pointing to required Frankenstein wrapper.<\/li>\n<li>In the wrapper itself, pull in \u201cpending\u201d stylesheets after initialization of Shadow DOM, but before the Alien component\u2019s rendering.<\/li>\n<\/ul>\n<p>After implementing these changes, your component should have everything it needs. The only thing you might want (this is not a requirement) to add is some custom CSS to fine-tune an Alien component in Host\u2019s environment. You might even style your Alien component completely different when used in Host. It goes beyond the main point of the article, but you look at the final code for the wrapper, where you can find examples of how to override simple styles on the wrapper level.<\/p>\n<ul>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-react\/js\/frankenstein-wrappers\/Header-wrapper.js#L37\">Frankenstein wrapper for React component<\/a><\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-vue\/frankenstein-wrappers\/Header-wrapper.js#L36\">Frankenstein wrapper for Vue component<\/a><\/li>\n<\/ul>\n<p>You can also take a look at the Webpack configuration at this step of migration:<\/p>\n<ul>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/10375db728c7d707cd15a799761ec0d2a254930c\/config\/webpack.conf.js\">Migration to React with<\/a> styled-components<\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/c642993405dd4ace6c6c30b4ee84f07c57af24ee\/config\/webpack.conf.js\">Migration to React with CSS Modules<\/a><\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/f591f1aaa59992034d4e6ce808a6ced2aff4f47f\/config\/webpack.dev.js\">Migration to Vue<\/a><\/li>\n<\/ul>\n<p>And finally, our components look exactly as we intended them to look like.<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/8d21c2d2-9bf7-42b9-8436-eb7677e28c60\/header-complete.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/header-complete.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/8d21c2d2-9bf7-42b9-8436-eb7677e28c60\/header-complete.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/8d21c2d2-9bf7-42b9-8436-eb7677e28c60\/header-complete.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/8d21c2d2-9bf7-42b9-8436-eb7677e28c60\/header-complete.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/8d21c2d2-9bf7-42b9-8436-eb7677e28c60\/header-complete.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/header-complete.png\" sizes=\"100vw\" alt=\"Result of migrating Header component written with Vue and React. The listing of the to-do items is still jQuery application.\"><\/a><figcaption>\n      Result of migrating Header component written with Vue and React. The listing of the to-do items is still jQuery application. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/8d21c2d2-9bf7-42b9-8436-eb7677e28c60\/header-complete.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<h4>5.5. Summary of fixing styles for the Alien component<\/h4>\n<p>This is a great moment to sum up what we have learned in this chapter so far. It might look like we had to do enormous work to fix styling of the Alien component; however, it all boils down to:<\/p>\n<ul>\n<li>Fixing bundled styles implemented with <a href=\"https:\/\/www.styled-components.com\/\">styled-components<\/a> in React or <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/css-modules.html\">CSS modules<\/a> and <a href=\"https:\/\/vue-loader.vuejs.org\/guide\/scoped-css.html\">Scoped CSS<\/a> in Vue is as simple as a couple of lines in Frankenstein wrapper or Webpack configuration.<\/li>\n<li>Fixing styles, implemented with <a href=\"https:\/\/github.com\/css-modules\/css-modules\">CSS Modules<\/a>, starts with just one line in <code>css-loader<\/code> configuration. After that, CSS Modules are treated as a global stylesheet.<\/li>\n<li>Fixing global stylesheets requires configuring <code>style-loader<\/code> package with <code>insert<\/code> option in Webpack, and updating Frankenstein wrapper to pull in the stylesheets into Shadow DOM at the right moment of the wrapper\u2019s lifecycle.<\/li>\n<\/ul>\n<p>After all, we have got properly styled Alien component migrated into the Host. There is just one thing that might or might not bother you depending on what framework you migrate to, however.<\/p>\n<p>Good news first: <strong>If you\u2019re migrating to Vue<\/strong>, the demo should be working just fine, and you should be able to add new to-do items from migrated Vue component. However, <strong>if you\u2019re migrating to React<\/strong>, and try to add a new to-do item, you won\u2019t succeed. Adding new items simply doesn\u2019t work, and no entries are added to the list. But why? What\u2019s the problem? No prejudice, but React has its own opinions on some things.<\/p>\n<h4>5.6. React And JS Events In Shadow DOM<\/h4>\n<p>No matter what <a href=\"https:\/\/reactjs.org\/docs\/web-components.html\">React documentation tells you<\/a>, React is not very friendly to Web Components. The simplicity of the example in the documentation doesn\u2019t stand any criticism, and anything more complicated than rendering a link in Web Component requires some research and investigation.<\/p>\n<p>As you have seen while fixing the styling for our Alien component, contrary to Vue where things fit Web Components nearly out of the box, React is not that Web Components-ready. For now, we have an understanding of how to make React components at least look good within Web Components, but there is also functionality and JavaScript events to fix.<\/p>\n<p>Long story short: Shadow DOM encapsulates events and retargets them, while <strong>React does not support this behavior of Shadow DOM natively<\/strong> and hence does not catch events coming from within Shadow DOM. There are deeper reasons for this behavior, and there is even <a href=\"https:\/\/github.com\/facebook\/react\/issues\/9242\">an open issue in React\u2019s bug tracker<\/a> if you want to dive into more details and discussions.<\/p>\n<p>Luckily, smart people prepared a solution for us. <a href=\"https:\/\/stackoverflow.com\/users\/3278894\/josephnvu\">@josephnvu<\/a> provided <a href=\"https:\/\/stackoverflow.com\/a\/37891448\/191426\">the basis for the solution<\/a>, and <a href=\"https:\/\/www.npmjs.com\/~lukasbombach\">Lukas Bombach<\/a> converted it into <a href=\"https:\/\/www.npmjs.com\/package\/react-shadow-dom-retarget-events\"><code>react-shadow-dom-retarget-events<\/code> npm module<\/a>. So you can install the package, follow instructions on the packages\u2019 page, update your wrapper\u2019s code and your Alien component will magically start working:<\/p>\n<pre><code>import retargetEvents from 'react-shadow-dom-retarget-events';\n...\nReactDOM.render(\n  ...\n);\nretargetEvents(this.shadowRoot);\n<\/code><\/pre>\n<p>If you want to have it more performant, you can make a local copy of the package (MIT license allows that) and limit the number of events to listen to as it is done <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-react\/js\/frankenstein-wrappers\/react-event-retargeting.js\">in Frankenstein Demo repository<\/a>. For this example, I know what events I need to retarget and <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-react\/js\/frankenstein-wrappers\/react-event-retargeting.js#L5\">specify only those<\/a>.<\/p>\n<p>With this, we are finally (I know it was a long process) done with proper migration of the first styled and fully-functional Alien component. Get yourself a good drink. You deserve it!<\/p>\n<div><\/div>\n<h3>6. Rinse & Repeat For All Of Your Components<\/h3>\n<p>After we migrated the first component, we should repeat the process for all of our components. In the case of Frankenstein Demo, there is only one left, however: the one, responsible for rendering the listing of to-do items.<\/p>\n<h4>New Wrappers For New Components<\/h4>\n<p>Let\u2019s start with adding a new wrapper. Following the naming convention, discussed above (since our React component is called <code>MainSection.js<\/code>), the corresponding wrapper in migration to React should be called <code>MainSection-wrapper.js<\/code>. At the same time, a similar component in Vue is called <code>Listing.vue<\/code>, hence the corresponding wrapper in the migration to Vue should be called <code>Listing-wrapper.js<\/code>. However, no matter the naming convention, the wrapper itself is going to be nearly identical to the one we already have:<\/p>\n<ul>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-react\/js\/frankenstein-wrappers\/MainSection-wrapper.js\">Wrapper for React listing<\/a><\/li>\n<li><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-vue\/frankenstein-wrappers\/Listing-wrapper.js\">Wrapper for Vue listing<\/a><\/li>\n<\/ul>\n<p>There is just one interesting thing we introduce in this <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/components\/MainSection.js\">second component in React application<\/a>. Sometimes, for that or another reason, you might want to use some jQuery plugin in your components. In case of our React component, we introduced two things:<\/p>\n<ul>\n<li><a href=\"https:\/\/getbootstrap.com\/docs\/4.0\/components\/tooltips\/\">Tooltip plugin from Bootstrap<\/a> that uses jQuery,<\/li>\n<li>A toggle for CSS classes like <code><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/components\/MainSection.js#L41\">.addClass()<\/a><\/code> and <code><a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/components\/MainSection.js#L43\">.removeClass()<\/a><\/code>.\n<p><strong>Note<\/strong>: <em>This use of jQuery for adding\/removing classes is purely illustrative. Please don\u2019t use jQuery for this scenario in real projects \u2014 rely on plain JavaScript instead.<\/em><\/li>\n<\/ul>\n<p>Of course, it might look weird to introduce jQuery in an Alien component when we migrate away from jQuery, but your Host might be different from the Host in this example \u2014 you might migrate away from AngularJS or anything else. Also, jQuery functionality in a component and global jQuery are not necessarily the same thing.<\/p>\n<p>However, the problem is that even if you confirm that component works just fine in the context of your Alien application, when you put it into Shadow DOM, your jQuery plugins and other code that rely on jQuery just won\u2019t work.<\/p>\n<h4>jQuery In Shadow DOM<\/h4>\n<p>Let\u2019s take a look at a general initialization of a random jQuery plugin:<\/p>\n<pre><code>$('.my-selector').fancyPlugin();\n<\/code><\/pre>\n<p>This way, all elements with <code>.my-selector<\/code> are going to be processed by <code>fancyPlugin<\/code>. This form of initialization assumes that <code>.my-selector<\/code> is present in global DOM. However, once such an element is put into Shadow DOM, just like with styles, shadow boundaries prevent jQuery from sneaking into it. As a result, jQuery can not find elements within Shadow DOM.<\/p>\n<p>The solution is to provide an optional second parameter to the selector that defines the root element for jQuery to search from. And this is, where we can supply our <code>shadowRoot<\/code>.<\/p>\n<pre><code>$('.my-selector', this.shadowRoot).fancyPlugin();\n<\/code><\/pre>\n<p>This way, jQuery selectors and, as a result, the plugins will work just fine.<\/p>\n<p>Keep in mind though that the Alien components are intended to be used both: in Alien without shadow DOM, and in Host within Shadow DOM. Hence we need a more unified solution that would not assume the presence of Shadow DOM by default.<\/p>\n<p>Analyzing <code>MainSection<\/code> component in our React application, we find that <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-todomvc\/blob\/react\/src\/components\/MainSection.js#L21\">it sets <code>documentRoot<\/code> property<\/a>.<\/p>\n<pre><code>...\nthis.documentRoot = this.props.root? this.props.root: document;\n...\n<\/code><\/pre>\n<p>So, we check for passed <code>root<\/code> property, and if it exists, this is what we use as <code>documentRoot<\/code>. Otherwise, we fall back to <code>document<\/code>.<\/p>\n<p>Here is the initialize of the tooltip plugin that uses this property:<\/p>\n<pre><code>$('[data-toggle=\"tooltip\"]', this.documentRoot).tooltip({\n  container: this.props.root || 'body'\n});\n<\/code><\/pre>\n<p>As a bonus, we use the same <code>root<\/code> property to define a container for injecting the tooltip in this case.<\/p>\n<p>Now, when the Alien component is ready to accept the <code>root<\/code> property, we update rendering of the component in corresponding Frankenstein wrapper:<\/p>\n<pre><code>\/\/ `appWrapper` is the root element within wrapper\u2019s Shadow DOM.\nReactDOM.render(<MainApp root={ appWrapper } \/>, appWrapper);\n<\/code><\/pre>\n<p>And that\u2019s it! The component works as fine in Shadow DOM as it does in the global DOM.<\/p>\n<h4>Webpack configuration for multi-wrappers scenario<\/h4>\n<p>The exciting part is happening in Webpack\u2019s configuration when using several wrappers. Nothing changes for the bundled styles like those CSS Modules in Vue components, or styled-components in React. However, global styles should get a little twist now.<\/p>\n<p>Remember, we said that <code>style-loader<\/code> (responsible for injecting global stylesheets into correct Shadow DOM) is inflexible as it takes just one selector at a time for its <code>insert<\/code> option. This means that we should split the <code>.css<\/code> rule in Webpack to have one sub-rule per wrapper using <a href=\"https:\/\/webpack.js.org\/configuration\/module\/#ruleoneof\"><code>oneOf<\/code> rule<\/a> or similar, if you\u2019re on a bundler other than Webpack.<\/p>\n<p>It\u2019s always easier to explain by using an example, so let\u2019s talk about the one from <a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-vue\/config\/webpack.dev.js#L84\">migration to Vue<\/a> this time (<a href=\"https:\/\/gitlab.com\/mishunov\/frankenstein-demo\/blob\/migration\/jquery-to-react\/config\/webpack.conf.js#L52\">the one in migration to React<\/a>, however, is nearly identical):<\/p>\n<pre><code>...\noneOf: [\n  {\n    issuer: \/Header\/,\n    use: [\n      {\n        loader: 'style-loader',\n        options: {\n          insert: 'frankenstein-header-wrapper'\n        }\n      },\n      ...\n    ]\n  },\n  {\n    issuer: \/Listing\/,\n    use: [\n      {\n        loader: 'style-loader',\n        options: {\n          insert: 'frankenstein-listing-wrapper'\n        }\n      },\n      ...\n    ]\n  },\n]\n...\n<\/code><\/pre>\n<p>I have excluded <code>css-loader<\/code> as its configuration is the same in all cases. Let\u2019s talk about <code>style-loader<\/code> instead. In this configuration, we insert <code><\/p>\n<style><\/code> tag into either <code>*-header-*<\/code> or <code>*-listing-*<\/code>, depending on the name of the file requesting that stylesheet (<a href=\"https:\/\/webpack.js.org\/configuration\/module\/#ruleissuer\"><code>issuer<\/code> rule in Webpack<\/a>). But we have to remember that the global stylesheet required for rendering an Alien component might be imported in two places:<\/p>\n<ul>\n<li>The Alien component itself,<\/li>\n<li>A Frankenstein wrapper.<\/li>\n<\/ul>\n<p>And here, we should appreciate the naming convention for wrappers, described above, when the name of an Alien component and a corresponding wrapper match. If, for example, we have a stylesheet, imported in a Vue component called <code>Header.vue<\/code>, it gets to correct <code>*-header-*<\/code> wrapper. At the same time, if we, instead, import the stylesheet in the wrapper, such stylesheet follows precisely the same rule if the wrapper is called <code>Header-wrapper.js<\/code> without any changes in the configuration. Same thing for the <code>Listing.vue<\/code> component and its corresponding wrapper <code>Listing-wrapper.js<\/code>. Using this naming convention, we reduce the configuration in our bundler.<\/p>\n<p>After all of your components migrated, it\u2019s time for the final step of the migration.<\/p>\n<h3>7. Switch To Alien<\/h3>\n<p>At some point, you find out that the components you identified at the very first step of the migration, are all replaced with Frankenstein wrappers. No jQuery application is left really and what you have is, essentially, the Alien application that is glued together using the means of Host.<\/p>\n<p>For example, the content part of <code>index.html<\/code> in the jQuery application \u2014 after migration of both microservices \u2014 looks something like this now:<\/p>\n<pre><code><section class=\"todoapp\">\n  <frankenstein-header-wrapper><\/frankenstein-header-wrapper>\n  <frankenstein-listing-wrapper><\/frankenstein-listing-wrapper>\n<\/section>\n<\/code><\/pre>\n<p>At this moment, there is no point in keeping our jQuery application around: instead, <strong>we should switch to Vue application<\/strong> and forget about all of our wrappers, Shadow DOM and fancy Webpack configurations. To do this, we have an elegant solution.<\/p>\n<p>Let\u2019s talk about HTTP requests. I will mention Apache configuration here, but this is just an implementation detail: doing the switch in Nginx or anything else should be as trivial as in Apache.<\/p>\n<p>Imagine that you have your site served from the <code>\/var\/www\/html<\/code> folder on your server. In this case, your <code>httpd.conf<\/code> or <code>httpd-vhost.conf<\/code> should have an entry that points to that folder like:<\/p>\n<pre><code>DocumentRoot \"\/var\/www\/html\"\n<\/code><\/pre>\n<p>To switch your application after the Frankenstein migration from jQuery to React, all you need to do is update the <code>DocumentRoot<\/code> entry to something like:<\/p>\n<pre><code>DocumentRoot \"\/var\/www\/html\/react\/build\"\n<\/code><\/pre>\n<p>Build your Alien application, restart your server, and your application is served directly from the Alien\u2019s folder: the React application served from the <code>react\/<\/code> folder. However, the same is true for Vue, of course, or any other framework you have migrated too. This is why it is so vital to keep Host and Alien completely independent and functional at any point in time because your Alien becomes your Host at this step.<\/p>\n<p>Now you can safely remove everything around your Alien\u2019s folder, including all the Shadow DOM, Frankenstein wrappers and any other migration-related artifact. It was a rough path at moments, but you have migrated your site. Congratulations!<\/p>\n<h3>Conclusion<\/h3>\n<p>We definitely went through somewhat rough terrain in this article. However, after we started with a jQuery application, we have managed to migrate it to both Vue and React. We have discovered some unexpected and not-so-trivial issues along the way: we had to fix styling, we had to fix JavaScript functionality, introduce some bundler configurations, and so much more. However, it gave us a better overview of what to expect in real projects. In the end, we have got a contemporary application without any remaining bits from the jQuery application even though we had all the rights to be skeptical about the end result while the migration was in progress.<\/p>\n<figure><a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3b5918a2-4923-43ed-948b-1f7d6c4724ef\/3-frankenstein-approach-framework-agnostic-migration-part2.png\"><\/p>\n<p>    <img decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/3-frankenstein-approach-framework-agnostic-migration-part2.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3b5918a2-4923-43ed-948b-1f7d6c4724ef\/3-frankenstein-approach-framework-agnostic-migration-part2.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3b5918a2-4923-43ed-948b-1f7d6c4724ef\/3-frankenstein-approach-framework-agnostic-migration-part2.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3b5918a2-4923-43ed-948b-1f7d6c4724ef\/3-frankenstein-approach-framework-agnostic-migration-part2.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3b5918a2-4923-43ed-948b-1f7d6c4724ef\/3-frankenstein-approach-framework-agnostic-migration-part2.png 2000w\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/3-frankenstein-approach-framework-agnostic-migration-part2.png\" sizes=\"100vw\" alt=\"After the switch to Alien, Frankenstein can be retired.\"><\/a><figcaption>\n      After the switch to Alien, Frankenstein can be retired. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/3b5918a2-4923-43ed-948b-1f7d6c4724ef\/3-frankenstein-approach-framework-agnostic-migration-part2.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Frankenstein Migration is neither a silver bullet nor should it be a scary process. It\u2019s just the defined algorithm, applicable to a lot of projects, that helps to transform projects into something new and robust in a predictable manner.<\/p>\n<div>\n  <img decoding=\"async\" src=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/logo-red-18.png\" alt=\"Smashing Editorial\"><span>(dm, yk, il)<\/span>\n<\/div>\n<\/article>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Frankenstein Migration: Framework-Agnostic Approach (Part 2) Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00 In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example&#8230;<a class=\"moretag\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/\"> Read the full article&#8230;<\/a><\/p>\n","protected":false},"author":4,"featured_media":71123,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[371],"tags":[],"class_list":["post-71122","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-user-experience"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.0.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Guest Contribution\"\/>\n\t<meta name=\"keywords\" content=\"user experience\" \/>\n\t<link rel=\"canonical\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.0.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies\" \/>\n\t\t<meta property=\"og:description\" content=\"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-09-27T11:15:46+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-09-27T11:15:46+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies\" \/>\n\t\t<meta name=\"twitter:description\" content=\"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#article\",\"name\":\"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies\",\"headline\":\"Frankenstein Migration: Framework-Agnostic Approach (Part 2)\",\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/www.taterboy.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/1-frankenstein-approach-framework-agnostic-migration-part2.png?fit=400%2C227&ssl=1\",\"width\":400,\"height\":227},\"datePublished\":\"2019-09-27T04:15:46-07:00\",\"dateModified\":\"2019-09-27T04:15:46-07:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#webpage\"},\"articleSection\":\"User Experience\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"position\":2,\"name\":\"User Experience\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#listItem\",\"name\":\"Frankenstein Migration: Framework-Agnostic Approach (Part 2)\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#listItem\",\"position\":3,\"name\":\"Frankenstein Migration: Framework-Agnostic Approach (Part 2)\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/\",\"name\":\"Guest Contribution\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Guest Contribution\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#webpage\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/\",\"name\":\"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies\",\"description\":\"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/www.taterboy.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/09\\\/1-frankenstein-approach-framework-agnostic-migration-part2.png?fit=400%2C227&ssl=1\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#mainImage\",\"width\":400,\"height\":227},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/09\\\/frankenstein-migration-framework-agnostic-approach-part-2\\\/#mainImage\"},\"datePublished\":\"2019-09-27T04:15:46-07:00\",\"dateModified\":\"2019-09-27T04:15:46-07:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies","description":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,","canonical_url":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/","robots":"max-image-preview:large","keywords":"user experience","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#article","name":"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies","headline":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)","author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/1-frankenstein-approach-framework-agnostic-migration-part2.png?fit=400%2C227&ssl=1","width":400,"height":227},"datePublished":"2019-09-27T04:15:46-07:00","dateModified":"2019-09-27T04:15:46-07:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#webpage"},"isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#webpage"},"articleSection":"User Experience"},{"@type":"BreadcrumbList","@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","position":1,"name":"Home","item":"https:\/\/www.taterboy.com\/blog","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","position":2,"name":"User Experience","item":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#listItem","name":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)"},"previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#listItem","position":3,"name":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)","previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}}]},{"@type":"Organization","@id":"https:\/\/www.taterboy.com\/blog\/#organization","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","url":"https:\/\/www.taterboy.com\/blog\/"},{"@type":"Person","@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author","url":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/","name":"Guest Contribution","image":{"@type":"ImageObject","@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g","width":96,"height":96,"caption":"Guest Contribution"}},{"@type":"WebPage","@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#webpage","url":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/","name":"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies","description":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/#website"},"breadcrumb":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#breadcrumblist"},"author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"creator":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/1-frankenstein-approach-framework-agnostic-migration-part2.png?fit=400%2C227&ssl=1","@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#mainImage","width":400,"height":227},"primaryImageOfPage":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/#mainImage"},"datePublished":"2019-09-27T04:15:46-07:00","dateModified":"2019-09-27T04:15:46-07:00"},{"@type":"WebSite","@id":"https:\/\/www.taterboy.com\/blog\/#website","url":"https:\/\/www.taterboy.com\/blog\/","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","inLanguage":"en-US","publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"}}]},"og:locale":"en_US","og:site_name":"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","og:type":"article","og:title":"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies","og:description":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,","og:url":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/","article:published_time":"2019-09-27T11:15:46+00:00","article:modified_time":"2019-09-27T11:15:46+00:00","twitter:card":"summary","twitter:title":"Frankenstein Migration: Framework-Agnostic Approach (Part 2) | Design for Immersive Technologies","twitter:description":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)Frankenstein Migration: Framework-Agnostic Approach (Part 2) Denys Mishunov 2019-09-27T12:00:59+02:002019-09-27T11:07:38+00:00In this article, we\u2019ll be putting all the theory to the test by performing step-by-step migration of an application, following the recommendations from the previous part. To make things straightforward, reduce uncertainties, unknowns, and unnecessary guessing, for the practical example of migration,"},"aioseo_meta_data":{"post_id":"71122","title":null,"description":null,"keywords":null,"keyphrases":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_custom_url":null,"og_image_custom_fields":null,"og_custom_image_width":null,"og_custom_image_height":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"location":null,"local_seo":null,"created":"2021-02-07 17:35:05","updated":"2026-09-02 04:26:17","focus_keyword":null,"additional_keywords":null,"truseo_locale":null,"primary_term":null,"og_image_url":null,"og_image_width":null,"og_image_height":null,"twitter_image_url":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"Article","isEnabled":true},"graphs":[]},"limit_modified_date":false,"ai":null,"breadcrumb_settings":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/\" title=\"User Experience\">User Experience<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tFrankenstein Migration: Framework-Agnostic Approach (Part 2)\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/www.taterboy.com\/blog"},{"label":"User Experience","link":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/"},{"label":"Frankenstein Migration: Framework-Agnostic Approach (Part 2)","link":"https:\/\/www.taterboy.com\/blog\/2019\/09\/frankenstein-migration-framework-agnostic-approach-part-2\/"}],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p8wzr5-iv8","jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/09\/1-frankenstein-approach-framework-agnostic-migration-part2.png?fit=400%2C227&ssl=1","_links":{"self":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/71122","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/comments?post=71122"}],"version-history":[{"count":0,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/71122\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/media\/71123"}],"wp:attachment":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/media?parent=71122"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/categories?post=71122"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/tags?post=71122"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}