MicroFrontends with Vue

Microservices are a well known concept nowadays. Handling encapsulated logic in different applications, can be a nice fit for bigger teams to avoid problems in deployments and development speed. Most of the time though, people are talking about the backend, where the UI part to some extend stays as a black box. One of the solutions to also further break down the UI is the concept of microfrontends which we will further explore here.

There are obviously also other ways to solve issues (I can highly recommend reading Sam Newmans awesome book on this https://samnewman.io/books/building_microservices_2nd_edition/ ), but one way to decompose your UI might be as following.

Each service not only contains the backend logic which then gets called the from one service to render the UI, but rather each service comes with its own UI which then gets loaded into an application which keeps an overview of all the systems to load. This allows us to deploy the full service UI and backend on its own without the need to deploy one big system for each change. This avoids keeping two systems too much in sync in terms of deployments.

Basic structure

In order to do this I used a vue js setup, where we have one container app, which further loads asynchronously applications into the main container (this is heavily inspired by https://blog.bitsrc.io/how-to-develop-microfrontends-using-react-step-by-step-guide-47ebb479cacd

flow of data

To achieve this one component was written:

<template>
  <div :id="this.containerName()" />
</template>

<script>
//utility function to generate a uuid (I didn't wanted to import the uuid
//lib just for a random string
function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
      (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  );
}
export default {
  name: "MicroFrontend",
  props: {
    // name of the microfrontend like `wisdom` or `dicreoll`
    name: {
      type: String,
      required: true
    },
    // host where the script should be loaded from
    host: {
      type: String,
      required: true
    },
    // define a static container name rather than a generic one
    // not needed if you are ok with random divs all the time
    staticContainerName: {
      type: String,
      required: false,
      default: null
    }
  },
  data: function () {
    return {
      uuid: uuidv4()
    }
  },
  methods: {
    // calculates the container id we load the microfrontend app into
    containerName() {
      if(this.staticContainerName != null) {
        return this.staticContainerName;
      }
      return `${ this.name }-${this.uuid}-container`;
    }
  },
  mounted() {//
    // id is generated by frontend - this will avoid loading the same
    // script twice
    const scriptId = `micro-frontend-script-${this.name}`;
    const renderMicroFrontend = () => {
      const fnName = `render${this.name}`;
      const containerName = `#${this.containerName()}`;
      //load the render function per convention and handover the container id
      window[fnName](containerName);
    };
    if (document.getElementById(scriptId)) {
      renderMicroFrontend();
      return;
    }
    //first load the manifest.json this contains the way forward
    fetch(`${this.host}/manifest.json`)
        .then((res) => res.json())
        .then((manifest) => {
          const script = document.createElement("script");
          script.id = scriptId;
          
          script.crossOrigin = "";
          //load out the path to the main.js file
          script.src = `${this.host}/${manifest["src/main.js"]["file"]}`;
          script.onload = () => {
            // call the function defined on top which will resolve then the app
            renderMicroFrontend();
          };
          document.head.appendChild(script);
        });
  }
};
</script>

<style scoped>
</style>

At the mounted stage we will trigger a check and if necessary a load of the corresponding script files.

Each script is ensured to be loaded only once, where the script id is the identifier here. Each microfrontend must follow a convention here and offer in its own application a function called render<name> (e.g. renderDiceRoll ). The manifest json must per convention offer a file with src/main.js which consists of the render functions. The script is than attached to the document header section so that it can be loaded. Afterwards the function is called, which calls the actual render function of the microfrontend app.

Calling one microfrontend is than as simple as including this in the main container.

<vue-micro-frontend name="diceroll" host="http://localhost:8000"/>

The full sample can be found here https://github.com/zelle7/vue3_microfrontend_poc

Not solved here

Things which are left out here:

  • Authentication
  • Communication between services
  • Different technologies (like loading vue3 and vue2 or even things like react)

Links

Font Awesome Unicodes

Nach dem ich heute längere Zeit damit verbracht habe, die passenden Unicodes zu den Font Awesome Icons zu parsen, stelle ich hier nochmal das JSON zur Verfügung. Vielleicht findet ja jemand Verwendung dafür. (Stand 17.02.2015)



{"fa-glass":"\u000","fa-music":"\u001","fa-search":"\u002","fa-envelope-o":"\u003","fa-heart":"\u004","fa-star":"\u005","fa-star-o":"\u006","fa-user":"\u007","fa-film":"\u008","fa-th-large":"\u009","fa-th":"\u00a","fa-th-list":"\u00b","fa-check":"\u00c","fa-remove":"\u00d","fa-close":"\u00d","fa-times":"\u00d","fa-search-plus":"\u00e","fa-search-minus":"\u010","fa-power-off":"\u011","fa-signal":"\u012","fa-gear":"\u013","fa-cog":"\u013","fa-trash-o":"\u014","fa-home":"\u015","fa-file-o":"\u016","fa-clock-o":"\u017","fa-road":"\u018","fa-download":"\u019","fa-arrow-circle-o-down":"\u01a","fa-arrow-circle-o-up":"\u01b","fa-inbox":"\u01c","fa-play-circle-o":"\u01d","fa-rotate-right":"\u01e","fa-repeat":"\u01e","fa-refresh":"\u021","fa-list-alt":"\u022","fa-lock":"\u023","fa-flag":"\u024","fa-headphones":"\u025","fa-volume-off":"\u026","fa-volume-down":"\u027","fa-volume-up":"\u028","fa-qrcode":"\u029","fa-barcode":"\u02a","fa-tag":"\u02b","fa-tags":"\u02c","fa-book":"\u02d","fa-bookmark":"\u02e","fa-print":"\u02f","fa-camera":"\u030","fa-font":"\u031","fa-bold":"\u032","fa-italic":"\u033","fa-text-height":"\u034","fa-text-width":"\u035","fa-align-left":"\u036","fa-align-center":"\u037","fa-align-right":"\u038","fa-align-justify":"\u039","fa-list":"\u03a","fa-dedent":"\u03b","fa-outdent":"\u03b","fa-indent":"\u03c","fa-video-camera":"\u03d","fa-photo":"\u03e","fa-image":"\u03e","fa-picture-o":"\u03e","fa-pencil":"\u040","fa-map-marker":"\u041","fa-adjust":"\u042","fa-tint":"\u043","fa-edit":"\u044","fa-pencil-square-o":"\u044","fa-share-square-o":"\u045","fa-check-square-o":"\u046","fa-arrows":"\u047","fa-step-backward":"\u048","fa-fast-backward":"\u049","fa-backward":"\u04a","fa-play":"\u04b","fa-pause":"\u04c","fa-stop":"\u04d","fa-forward":"\u04e","fa-fast-forward":"\u050","fa-step-forward":"\u051","fa-eject":"\u052","fa-chevron-left":"\u053","fa-chevron-right":"\u054","fa-plus-circle":"\u055","fa-minus-circle":"\u056","fa-times-circle":"\u057","fa-check-circle":"\u058","fa-question-circle":"\u059","fa-info-circle":"\u05a","fa-crosshairs":"\u05b","fa-times-circle-o":"\u05c","fa-check-circle-o":"\u05d","fa-ban":"\u05e","fa-arrow-left":"\u060","fa-arrow-right":"\u061","fa-arrow-up":"\u062","fa-arrow-down":"\u063","fa-mail-forward":"\u064","fa-share":"\u064","fa-expand":"\u065","fa-compress":"\u066","fa-plus":"\u067","fa-minus":"\u068","fa-asterisk":"\u069","fa-exclamation-circle":"\u06a","fa-gift":"\u06b","fa-leaf":"\u06c","fa-fire":"\u06d","fa-eye":"\u06e","fa-eye-slash":"\u070","fa-warning":"\u071","fa-exclamation-triangle":"\u071","fa-plane":"\u072","fa-calendar":"\u073","fa-random":"\u074","fa-comment":"\u075","fa-magnet":"\u076","fa-chevron-up":"\u077","fa-chevron-down":"\u078","fa-retweet":"\u079","fa-shopping-cart":"\u07a","fa-folder":"\u07b","fa-folder-open":"\u07c","fa-arrows-v":"\u07d","fa-arrows-h":"\u07e","fa-bar-chart-o":"\u080","fa-bar-chart":"\u080","fa-twitter-square":"\u081","fa-facebook-square":"\u082","fa-camera-retro":"\u083","fa-key":"\u084","fa-gears":"\u085","fa-cogs":"\u085","fa-comments":"\u086","fa-thumbs-o-up":"\u087","fa-thumbs-o-down":"\u088","fa-star-half":"\u089","fa-heart-o":"\u08a","fa-sign-out":"\u08b","fa-linkedin-square":"\u08c","fa-thumb-tack":"\u08d","fa-external-link":"\u08e","fa-sign-in":"\u090","fa-trophy":"\u091","fa-github-square":"\u092","fa-upload":"\u093","fa-lemon-o":"\u094","fa-phone":"\u095","fa-square-o":"\u096","fa-bookmark-o":"\u097","fa-phone-square":"\u098","fa-twitter":"\u099","fa-facebook-f":"\u09a","fa-facebook":"\u09a","fa-github":"\u09b","fa-unlock":"\u09c","fa-credit-card":"\u09d","fa-rss":"\u09e","fa-hdd-o":"\u0a0","fa-bullhorn":"\u0a1","fa-bell":"\u0f3","fa-certificate":"\u0a3","fa-hand-o-right":"\u0a4","fa-hand-o-left":"\u0a5","fa-hand-o-up":"\u0a6","fa-hand-o-down":"\u0a7","fa-arrow-circle-left":"\u0a8","fa-arrow-circle-right":"\u0a9","fa-arrow-circle-up":"\u0aa","fa-arrow-circle-down":"\u0ab","fa-globe":"\u0ac","fa-wrench":"\u0ad","fa-tasks":"\u0ae","fa-filter":"\u0b0","fa-briefcase":"\u0b1","fa-arrows-alt":"\u0b2","fa-group":"\u0c0","fa-users":"\u0c0","fa-chain":"\u0c1","fa-link":"\u0c1","fa-cloud":"\u0c2","fa-flask":"\u0c3","fa-cut":"\u0c4","fa-scissors":"\u0c4","fa-copy":"\u0c5","fa-files-o":"\u0c5","fa-paperclip":"\u0c6","fa-save":"\u0c7","fa-floppy-o":"\u0c7","fa-square":"\u0c8","fa-navicon":"\u0c9","fa-reorder":"\u0c9","fa-bars":"\u0c9","fa-list-ul":"\u0ca","fa-list-ol":"\u0cb","fa-strikethrough":"\u0cc","fa-underline":"\u0cd","fa-table":"\u0ce","fa-magic":"\u0d0","fa-truck":"\u0d1","fa-pinterest":"\u0d2","fa-pinterest-square":"\u0d3","fa-google-plus-square":"\u0d4","fa-google-plus":"\u0d5","fa-money":"\u0d6","fa-caret-down":"\u0d7","fa-caret-up":"\u0d8","fa-caret-left":"\u0d9","fa-caret-right":"\u0da","fa-columns":"\u0db","fa-unsorted":"\u0dc","fa-sort":"\u0dc","fa-sort-down":"\u0dd","fa-sort-desc":"\u0dd","fa-sort-up":"\u0de","fa-sort-asc":"\u0de","fa-envelope":"\u0e0","fa-linkedin":"\u0e1","fa-rotate-left":"\u0e2","fa-undo":"\u0e2","fa-legal":"\u0e3","fa-gavel":"\u0e3","fa-dashboard":"\u0e4","fa-tachometer":"\u0e4","fa-comment-o":"\u0e5","fa-comments-o":"\u0e6","fa-flash":"\u0e7","fa-bolt":"\u0e7","fa-sitemap":"\u0e8","fa-umbrella":"\u0e9","fa-paste":"\u0ea","fa-clipboard":"\u0ea","fa-lightbulb-o":"\u0eb","fa-exchange":"\u0ec","fa-cloud-download":"\u0ed","fa-cloud-upload":"\u0ee","fa-user-md":"\u0f0","fa-stethoscope":"\u0f1","fa-suitcase":"\u0f2","fa-bell-o":"\u0a2","fa-coffee":"\u0f4","fa-cutlery":"\u0f5","fa-file-text-o":"\u0f6","fa-building-o":"\u0f7","fa-hospital-o":"\u0f8","fa-ambulance":"\u0f9","fa-medkit":"\u0fa","fa-fighter-jet":"\u0fb","fa-beer":"\u0fc","fa-h-square":"\u0fd","fa-plus-square":"\u0fe","fa-angle-double-left":"\u100","fa-angle-double-right":"\u101","fa-angle-double-up":"\u102","fa-angle-double-down":"\u103","fa-angle-left":"\u104","fa-angle-right":"\u105","fa-angle-up":"\u106","fa-angle-down":"\u107","fa-desktop":"\u108","fa-laptop":"\u109","fa-tablet":"\u10a","fa-mobile-phone":"\u10b","fa-mobile":"\u10b","fa-circle-o":"\u10c","fa-quote-left":"\u10d","fa-quote-right":"\u10e","fa-spinner":"\u110","fa-circle":"\u111","fa-mail-reply":"\u112","fa-reply":"\u112","fa-github-alt":"\u113","fa-folder-o":"\u114","fa-folder-open-o":"\u115","fa-smile-o":"\u118","fa-frown-o":"\u119","fa-meh-o":"\u11a","fa-gamepad":"\u11b","fa-keyboard-o":"\u11c","fa-flag-o":"\u11d","fa-flag-checkered":"\u11e","fa-terminal":"\u120","fa-code":"\u121","fa-mail-reply-all":"\u122","fa-reply-all":"\u122","fa-star-half-empty":"\u123","fa-star-half-full":"\u123","fa-star-half-o":"\u123","fa-location-arrow":"\u124","fa-crop":"\u125","fa-code-fork":"\u126","fa-unlink":"\u127","fa-chain-broken":"\u127","fa-question":"\u128","fa-info":"\u129","fa-exclamation":"\u12a","fa-superscript":"\u12b","fa-subscript":"\u12c","fa-eraser":"\u12d","fa-puzzle-piece":"\u12e","fa-microphone":"\u130","fa-microphone-slash":"\u131","fa-shield":"\u132","fa-calendar-o":"\u133","fa-fire-extinguisher":"\u134","fa-rocket":"\u135","fa-maxcdn":"\u136","fa-chevron-circle-left":"\u137","fa-chevron-circle-right":"\u138","fa-chevron-circle-up":"\u139","fa-chevron-circle-down":"\u13a","fa-html5":"\u13b","fa-css3":"\u13c","fa-anchor":"\u13d","fa-unlock-alt":"\u13e","fa-bullseye":"\u140","fa-ellipsis-h":"\u141","fa-ellipsis-v":"\u142","fa-rss-square":"\u143","fa-play-circle":"\u144","fa-ticket":"\u145","fa-minus-square":"\u146","fa-minus-square-o":"\u147","fa-level-up":"\u148","fa-level-down":"\u149","fa-check-square":"\u14a","fa-pencil-square":"\u14b","fa-external-link-square":"\u14c","fa-share-square":"\u14d","fa-compass":"\u14e","fa-toggle-down":"\u150","fa-caret-square-o-down":"\u150","fa-toggle-up":"\u151","fa-caret-square-o-up":"\u151","fa-toggle-right":"\u152","fa-caret-square-o-right":"\u152","fa-euro":"\u153","fa-eur":"\u153","fa-gbp":"\u154","fa-dollar":"\u155","fa-usd":"\u155","fa-rupee":"\u156","fa-inr":"\u156","fa-cny":"\u157","fa-rmb":"\u157","fa-yen":"\u157","fa-jpy":"\u157","fa-ruble":"\u158","fa-rouble":"\u158","fa-rub":"\u158","fa-won":"\u159","fa-krw":"\u159","fa-bitcoin":"\u15a","fa-btc":"\u15a","fa-file":"\u15b","fa-file-text":"\u15c","fa-sort-alpha-asc":"\u15d","fa-sort-alpha-desc":"\u15e","fa-sort-amount-asc":"\u160","fa-sort-amount-desc":"\u161","fa-sort-numeric-asc":"\u162","fa-sort-numeric-desc":"\u163","fa-thumbs-up":"\u164","fa-thumbs-down":"\u165","fa-youtube-square":"\u166","fa-youtube":"\u167","fa-xing":"\u168","fa-xing-square":"\u169","fa-youtube-play":"\u16a","fa-dropbox":"\u16b","fa-stack-overflow":"\u16c","fa-instagram":"\u16d","fa-flickr":"\u16e","fa-adn":"\u170","fa-bitbucket":"\u171","fa-bitbucket-square":"\u172","fa-tumblr":"\u173","fa-tumblr-square":"\u174","fa-long-arrow-down":"\u175","fa-long-arrow-up":"\u176","fa-long-arrow-left":"\u177","fa-long-arrow-right":"\u178","fa-apple":"\u179","fa-windows":"\u17a","fa-android":"\u17b","fa-linux":"\u17c","fa-dribbble":"\u17d","fa-skype":"\u17e","fa-foursquare":"\u180","fa-trello":"\u181","fa-female":"\u182","fa-male":"\u183","fa-gittip":"\u184","fa-gratipay":"\u184","fa-sun-o":"\u185","fa-moon-o":"\u186","fa-archive":"\u187","fa-bug":"\u188","fa-vk":"\u189","fa-weibo":"\u18a","fa-renren":"\u18b","fa-pagelines":"\u18c","fa-stack-exchange":"\u18d","fa-arrow-circle-o-right":"\u18e","fa-arrow-circle-o-left":"\u190","fa-toggle-left":"\u191","fa-caret-square-o-left":"\u191","fa-dot-circle-o":"\u192","fa-wheelchair":"\u193","fa-vimeo-square":"\u194","fa-turkish-lira":"\u195","fa-try":"\u195","fa-plus-square-o":"\u196","fa-space-shuttle":"\u197","fa-slack":"\u198","fa-envelope-square":"\u199","fa-wordpress":"\u19a","fa-openid":"\u19b","fa-institution":"\u19c","fa-bank":"\u19c","fa-university":"\u19c","fa-mortar-board":"\u19d","fa-graduation-cap":"\u19d","fa-yahoo":"\u19e","fa-google":"\u1a0","fa-reddit":"\u1a1","fa-reddit-square":"\u1a2","fa-stumbleupon-circle":"\u1a3","fa-stumbleupon":"\u1a4","fa-delicious":"\u1a5","fa-digg":"\u1a6","fa-pied-piper":"\u1a7","fa-pied-piper-alt":"\u1a8","fa-drupal":"\u1a9","fa-joomla":"\u1aa","fa-language":"\u1ab","fa-fax":"\u1ac","fa-building":"\u1ad","fa-child":"\u1ae","fa-paw":"\u1b0","fa-spoon":"\u1b1","fa-cube":"\u1b2","fa-cubes":"\u1b3","fa-behance":"\u1b4","fa-behance-square":"\u1b5","fa-steam":"\u1b6","fa-steam-square":"\u1b7","fa-recycle":"\u1b8","fa-automobile":"\u1b9","fa-car":"\u1b9","fa-cab":"\u1ba","fa-taxi":"\u1ba","fa-tree":"\u1bb","fa-spotify":"\u1bc","fa-deviantart":"\u1bd","fa-soundcloud":"\u1be","fa-database":"\u1c0","fa-file-pdf-o":"\u1c1","fa-file-word-o":"\u1c2","fa-file-excel-o":"\u1c3","fa-file-powerpoint-o":"\u1c4","fa-file-photo-o":"\u1c5","fa-file-picture-o":"\u1c5","fa-file-image-o":"\u1c5","fa-file-zip-o":"\u1c6","fa-file-archive-o":"\u1c6","fa-file-sound-o":"\u1c7","fa-file-audio-o":"\u1c7","fa-file-movie-o":"\u1c8","fa-file-video-o":"\u1c8","fa-file-code-o":"\u1c9","fa-vine":"\u1ca","fa-codepen":"\u1cb","fa-jsfiddle":"\u1cc","fa-life-bouy":"\u1cd","fa-life-buoy":"\u1cd","fa-life-saver":"\u1cd","fa-support":"\u1cd","fa-life-ring":"\u1cd","fa-circle-o-notch":"\u1ce","fa-ra":"\u1d0","fa-rebel":"\u1d0","fa-ge":"\u1d1","fa-empire":"\u1d1","fa-git-square":"\u1d2","fa-git":"\u1d3","fa-hacker-news":"\u1d4","fa-tencent-weibo":"\u1d5","fa-qq":"\u1d6","fa-wechat":"\u1d7","fa-weixin":"\u1d7","fa-send":"\u1d8","fa-paper-plane":"\u1d8","fa-send-o":"\u1d9","fa-paper-plane-o":"\u1d9","fa-history":"\u1da","fa-genderless":"\u1db","fa-circle-thin":"\u1db","fa-header":"\u1dc","fa-paragraph":"\u1dd","fa-sliders":"\u1de","fa-share-alt":"\u1e0","fa-share-alt-square":"\u1e1","fa-bomb":"\u1e2","fa-soccer-ball-o":"\u1e3","fa-futbol-o":"\u1e3","fa-tty":"\u1e4","fa-binoculars":"\u1e5","fa-plug":"\u1e6","fa-slideshare":"\u1e7","fa-twitch":"\u1e8","fa-yelp":"\u1e9","fa-newspaper-o":"\u1ea","fa-wifi":"\u1eb","fa-calculator":"\u1ec","fa-paypal":"\u1ed","fa-google-wallet":"\u1ee","fa-cc-visa":"\u1f0","fa-cc-mastercard":"\u1f1","fa-cc-discover":"\u1f2","fa-cc-amex":"\u1f3","fa-cc-paypal":"\u1f4","fa-cc-stripe":"\u1f5","fa-bell-slash":"\u1f6","fa-bell-slash-o":"\u1f7","fa-trash":"\u1f8","fa-copyright":"\u1f9","fa-at":"\u1fa","fa-eyedropper":"\u1fb","fa-paint-brush":"\u1fc","fa-birthday-cake":"\u1fd","fa-area-chart":"\u1fe","fa-pie-chart":"\u200","fa-line-chart":"\u201","fa-lastfm":"\u202","fa-lastfm-square":"\u203","fa-toggle-off":"\u204","fa-toggle-on":"\u205","fa-bicycle":"\u206","fa-bus":"\u207","fa-ioxhost":"\u208","fa-angellist":"\u209","fa-cc":"\u20a","fa-shekel":"\u20b","fa-sheqel":"\u20b","fa-ils":"\u20b","fa-meanpath":"\u20c","fa-buysellads":"\u20d","fa-connectdevelop":"\u20e","fa-dashcube":"\u210","fa-forumbee":"\u211","fa-leanpub":"\u212","fa-sellsy":"\u213","fa-shirtsinbulk":"\u214","fa-simplybuilt":"\u215","fa-skyatlas":"\u216","fa-cart-plus":"\u217","fa-cart-arrow-down":"\u218","fa-diamond":"\u219","fa-ship":"\u21a","fa-user-secret":"\u21b","fa-motorcycle":"\u21c","fa-street-view":"\u21d","fa-heartbeat":"\u21e","fa-venus":"\u221","fa-mars":"\u222","fa-mercury":"\u223","fa-transgender":"\u224","fa-transgender-alt":"\u225","fa-venus-double":"\u226","fa-mars-double":"\u227","fa-venus-mars":"\u228","fa-mars-stroke":"\u229","fa-mars-stroke-v":"\u22a","fa-mars-stroke-h":"\u22b","fa-neuter":"\u22c","fa-facebook-official":"\u230","fa-pinterest-p":"\u231","fa-whatsapp":"\u232","fa-server":"\u233","fa-user-plus":"\u234","fa-user-times":"\u235","fa-hotel":"\u236","fa-bed":"\u236","fa-viacoin":"\u237","fa-train":"\u238","fa-subway":"\u239","fa-medium":"\u23a"}

Scroll to top