Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to Micro Frontends

Introduction to Micro Frontends

Avatar for Ivan Jovanovic

Ivan Jovanovic

February 06, 2018
Tweet

More Decks by Ivan Jovanovic

Other Decks in Technology

Transcript

  1. Microservice principles • Lightweight protocol between services • Small services,

    one job per service • Service independence • Easier to understand, develop and test • Speeds up development • Enables continues delivery and deployment
  2. + +

  3. Why? • It’s cool • No more shared codebases and

    conflicts • Independent deployments • Each team can pick their own stack
  4. Manual code fetching • Code lives on different server •

    Independent deployment • Communication is done through: • Window object • Event bus
  5. const fetchApp = ({ element, baseUrl, files = ['main.js', 'main.css']

    }) => { const fnName = element.toLowerCase() .replace(/^[#.]*/, '') files.forEach((filename) => { const fileUrl = baseUrl + '/' + filename if (/.js$/.test(filename)) { $.ajax({ dataType: 'script', cache: true, url: fileUrl }).done(() => { global[fnName](element) }) } if (/.css$/.test(filename)) { $('<link />', { rel: 'stylesheet', type: 'text/css', href: fileUrl }).appendTo('head') } }) }
  6. files.forEach((filename) => { const fileUrl = baseUrl + '/' +

    filename if (/.js$/.test(filename)) { $.ajax({ dataType: 'script', cache: true, url: fileUrl }).done(() => { global[fnName](element) }) } if (/.css$/.test(filename)) { $('<link />', { rel: 'stylesheet', type: 'text/css', href: fileUrl }).appendTo('head') } })
  7. import React from 'react' import { render } from 'react-dom'

    export default (el) => { render(( <h1>Hello</h1> ), document.querySelector(el)) }
  8. Event bus • Eev - https://github.com/chrisdavies/eev • Less than 500

    bytes minified + zipped • Really fast • Zero dependencies • Simple
  9. import Eev from ‘eev' const e = new Eev() e.emit('event',

    { foo: 'Bar' }) e.on('event', (data) => { console.log('got ' + data.foo); // got bar })
  10. import Eev from 'eev' window.e = new Eev() window.e.emit('event', {

    foo: 'Bar' }) window.e.on('event', (data) => { console.log('got ' + data.foo); // got bar })
  11. IFrames • Code lives on different server • Independent deployment

    • Communication is done through browser Event bus
  12. <html> <head> <title>IFrame services</title> <script type='text/javascript'> const app = window.getElementById('app-iframe').contentWindow

    app.postMessage('This will not work', 'https://google.com') // url doesn’t match app.postMessage('This will work, hi there!', 'http://example.com') function receiveMessage (event) { if (event.origin !== 'http://example.com') // security check for the origin return console.log(event.source) // iframe console.log(event.data) // 'hi from an iframe' } window.addEventListener('message', receiveMessage) </script> </head> <body> <div id='app'> <iframe src="https://example.com/app.html" id='app-iframe'></iframe> </div> </body> </html>
  13. const app = window.getElementById('app-iframe').contentWindow app.postMessage('This will not work', 'https://google.com') //

    url doesn’t match app.postMessage('This will work, hi there!', 'http://example.com') function receiveMessage (event) { if (event.origin !== 'http://example.com') // security check for the origin return console.log(event.source) // iframe console.log(event.data) // 'hi from an iframe' } window.addEventListener('message', receiveMessage)
  14. function receiveMessage (event) { if (event.origin !== "http://example.com") return console.log(event.source)

    // window.opener console.log(event.data) // 'This will work, hi there!' event.source.postMessage('hi from an iframe', event.origin) } window.addEventListener('message', receiveMessage)
  15. Single-spa npm module • https://github.com/CanopyTax/single-spa • Use multiple frameworks on

    the same page without refreshing the page • Write code using a new framework, without rewriting your existing app • Lazy load code for improved initial load time
  16. Single-spa npm module • Code lives on the same server

    • Everything is bundled and deployed in the same time • Communication is done through: • Window object • Event bus • Shared state (Redux etc.) • Whatever works for you
  17. import * as singleSpa from 'single-spa' const appName = 'app1'

    const loadingFunction = () => import('./app1/app1.js') const activityFunction = location => location.hash.startsWith('#/app1') singleSpa.declareChildApplication(appName, loadingFunction, activityFunction) singleSpa.start()
  18. let domEl // Make a div for your app export

    function bootstrap(props) { return Promise .resolve() .then(() => { domEl = document.createElement('div') domEl.id = 'app1' document.body.appendChild(domEl) }) }
  19. // Mount the framework code export function mount(props) { return

    Promise .resolve() .then(() => { domEl.textContent = 'App 1 is mounted!' }) }
  20. // Unmount the spa framework from dom export function unmount(props)

    { return Promise .resolve() .then(() => { domEl.textContent = '' }) }
  21. import React from 'react' import ReactDOM from 'react-dom' import rootComponent

    from './path-to-root-component' import singleSpaReact from 'single-spa-react' const reactLifecycles = singleSpaReact({ React, ReactDOM, rootComponent, domElementGetter: () => document.getElementById('main-content') }) export const bootstrap = [ reactLifecycles.bootstrap ] export const mount = [ reactLifecycles.mount ] export const unmount = [ reactLifecycles.unmount ]
  22. import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' import singleSpaAngular2 from 'single-spa-angular2'

    import mainModule from './main-module.ts' import { Router } from '@angular/router' const ng2Lifecycles = singleSpaAngular2({ domElementGetter: () => document.getElementById('angular2'), mainModule, angularPlatform: platformBrowserDynamic(), template: `<component-to-render />`, Router }) export const bootstrap = [ ng2Lifecycles.bootstrap ] export const mount = [ ng2Lifecycles.mount ] export const unmount = [ ng2Lifecycles.unmount ]
  23. import Vue from 'vue' import singleSpaVue from 'single-spa-vue' const vueLifecycles

    = singleSpaVue({ Vue, appOptions: { el: '#mount-location', template: '<div>some template</div>' } }) export const bootstrap = [ vueLifecycles.bootstrap ] export const mount = [ vueLifecycles.mount ] export const unmount = [ vueLifecycles.unmount ]
  24. Conclusion • Don’t use this if you have simple app

    • Use micro frontends to make things easier, not complicated • Micro frontends doesn’t mean to use every framework in the world • Don’t forget to make standards across micro apps