$30 off During Our Annual Pro Sale. View Details »

Micro Frontends with Vue.js

Micro Frontends with Vue.js

AlexanderTserkovniy

November 09, 2019
Tweet

Other Decks in Programming

Transcript

  1. Micro Frontends with Vue.js

    View Slide

  2. PRESENTATION CONTENT
    Intro
    Tech part
    Q&A

    View Slide

  3. PMLAB – это украинская продуктовая компания c
    full-cycle development в сфере Entertainment.
    Мы являемся частью международного Холдинга,
    который представлен в шести странах мира с
    общим числом сотрудников - 1 600 человек.

    View Slide

  4. У нас есть стенд, подходите пообщаемся

    View Slide

  5. In web dev since 2008
    JS Fest program committee member
    Consultant in tech recruiting and Full Stack
    JavaScript architecture
    Author of the top 1 2016-2018 DOU article
    https://dou.ua/lenta/articles/dont-move-abroad/
    Oleksandr Tserkovnyi
    FE Architect Backoffice & C team

    View Slide

  6. View Slide

  7. Front-end Developer Handbook 2019
    http://bit.ly/2GiDP3o
    Image link
    http://bit.ly/2UKlxSH

    View Slide

  8. https://hackernoon.com/understanding-micro-frontends-b1c11585a297
    Micro Frontends

    View Slide

  9. https://www.cygnismedia.com/blog/micro-frontends-development/

    View Slide

  10. https://www.cygnismedia.com/blog/micro-frontends-development/

    View Slide

  11. Independent
    deploy
    When one team wants to
    deploy without triggering
    regression or just another
    team’s part.
    Clear boundaries
    Test coverage, monitoring,
    version control.
    A&B testing ease
    Easier to cover with A&B
    testing individual parts
    instead of hacking on top
    of monolith.
    Use/update any
    contact/library
    Micro Frontend team
    decides what to use in
    their project.

    View Slide

  12. You might NOT need it
    Is your delivery long?
    Do you have many product owners in one project?
    Might you trigger regression of another team?
    Is your code reusable as much as possible?
    You have 2-3 devs in the whole team and it is enough.
    Do you need different setup for various user groups?

    View Slide

  13. It is expensive
    Much time, regression, communication
    Hard to sell to business
    Make it incremental
    Dedicate only a part of resource
    Sell it as multiplier
    Sell it as individual products following common standard (ind. deploy)

    View Slide

  14. No technologies, no tools
    No industrial standard
    Only two runtime options (iframe and lazy load)
    It is not something, which is inevitably will become all frontends
    14

    View Slide

  15. Issues are the same
    1. Size
    2. Scalability
    3. Upgrade version of main framework/library
    4. Long delivery
    5. Long CI&CD

    View Slide

  16. One REST API to glue them all?

    View Slide

  17. View Slide

  18. Front-end specificity
    Different consumers
    Not only code, but markup
    No concept of scalability rules for a “container”
    You cannot enter a “container” by user behalf (2g, device, surr.)
    Others code might break yours
    18

    View Slide

  19. TECH PART
    Intro
    Tech part
    Q&A

    View Slide

  20. View Slide

  21. View Slide

  22. Backoffice
    Internal usage only
    Latest chrome (CSS Grids, no polyfills)
    Operating since 2016
    Universal for the whole company

    View Slide

  23. We want
    independent deploy
    Team 1 wants to delivery
    their small change not
    affecting Team 2.
    We don’t want to
    stop development
    Please continue delivery
    of the features.
    We want standards
    Please be able to pass
    experience to other teams,
    even the outsource without
    loosing the quality.
    We want identical
    look and feel
    Every section should look
    the same.

    View Slide

  24. Business requirements
    1. Business have to continue operating
    2. Time to market
    24

    View Slide

  25. View Slide

  26. Discuss the request and possible solutions
    Share the experience
    POC
    Set requirements (DO NOT create a spaceship, solve only your issues)
    Only Vue.js
    Drawn architecture
    Tickets, planning and finally sprint
    Grooming

    View Slide

  27. Components reusability
    Services reusability (data fetching, user identity etc.)
    Caching of the libraries which are in use by other Micro Frontends
    Routing
    Integration testing
    UX adaptation

    View Slide

  28. TECH DEBT
    Components in the common folder
    Utils in the utils folder or scattered around
    Unit tests coverage is something around ~0%
    Constants in one file

    View Slide

  29. Components reusability

    View Slide

  30. https://storybook.js.org/
    Industrial standard for reusable components
    Separate testing (snapshot, unit, screen
    capturing testing)
    Force to think components concepts
    Versatile (разносторонний)
    to npm and global find&replace
    Vue.use(BoKit) plugin instead of
    import BoButton from './BoButton';
    Vue.component('BoButton', BoButton);

    View Slide

  31. Utilities and constants
    31

    View Slide

  32. Adapter
    npm library
    instead of global find&replace
    imports left untouched but the inside
    content was changed:
    import { httpClient } from ‘bo-utils’;
    export default httpClient;

    View Slide

  33. Unit tests after separation

    View Slide

  34. POC schema
    1. User enters website
    2. Page with core.js and other vital small JavaScript
    code is loaded
    3. Router is downloaded
    4. User goes to some page via router
    5. Encounters a spinner
    6. Micro Frontend files are downloaded to the page
    7. Cached libraries of the same version are used
    8. Connected to the main store and router
    9. Layout service places this Micro Frontend to his
    placeholder
    10. Rendered
    11. User interaction

    View Slide

  35. Don’t code just for code
    1. User enters website
    2. Core.js is basically what’s left inside main project and is
    called SHELL
    3. Router is NOT downloaded, why for?
    4. User goes to some page via router
    5. Encounters a spinner
    6. Micro Frontend files are downloaded to the page
    7. Cashing might be in the future, but for now they need
    different versions
    8. DO NOT Connected to the main store and router, why
    for?
    9. DO NOT Overengineer we have only one layout,
    placeholders are just components
    10. Rendered
    11. User interaction

    View Slide

  36. POC Fate

    View Slide

  37. View Slide

  38. Usually thrown away
    Ideal is not finished!
    The milestones road
    Make it
    release candidate

    View Slide

  39. POC is marinading on PROD
    39
    We polish our solution

    View Slide

  40. render /destroy / initialize

    View Slide

  41. static file server / CDN

    View Slide

  42. export to window

    View Slide

  43. stores separated / EE

    View Slide

  44. own router / root router / base

    View Slide

  45. Architecture entities
    45
    Following single responsibility principle

    View Slide

  46. SHELL – core entity, set up using config
    ROUTER – main routes and global routes orchestration is placed in SHELL as well (passed as a
    prop during each Micro Frontend's rendering phase). Visually it is presented in sidebar. It dictates
    at which URL, which Micro Frontend to run.
    LOADER – this entity could fetch user's Micro Frontends versions and the Micro Frontend files
    themselves.
    RENDERER – component which receives a prop of Micro Frontend name, retrieves it from global
    microservices storage (currently it is window[namespace] (Webpack)) using LOADER's method.
    MANIFEST – a json schema, which contains list of the files created by a single Micro Frontend
    and paths for these files, as they must be downloaded.
    LOCALIZATION – provides an API for localization of texts within any Micro Frontend (passed as
    a prop during each Micro Frontend's rendering phase).
    IDENTITY – provides an API to detect user related data and ACL, for instance email (passed as a
    prop during each Micro Frontend's rendering phase).
    Helper source: https://www.youtube.com/watch?v=2r9KqASOSeM
    Mykhailo Churilov "Front-end Microservices Architecture"

    View Slide

  47. MANIFEST
    47
    Cache breaking

    View Slide

  48. {
    "app.css": "/players-search-app.ad2.css",
    "app.js": "/players-search-app.ad2.js",
    "chunk-vendors.css": "/players-search-chunk-vendors.ad2.css",
    "chunk-vendors.js": "/players-search-chunk-vendors.ad2.js"
    }

    View Slide

  49. RENDERER
    49
    Component which receives a prop of Micro Frontend name,
    retrieves it from global microservices storage (currently it is
    window[namespace] (Webpack)) using LOADER's method.

    View Slide









  50. Unable to load microfrontend '{{ name }}'

    {{error.message}}
    {{error.stack}}




    View Slide

  51. watch: {
    $route() {
    this.currentInstance && this.destroyMicrofrontend(this.currentInstance);
    this.renderMicrofrontend(this.name, this.base);
    }
    },
    mounted() {
    this.renderMicrofrontend(this.name, this.base);
    },
    destroyed() {
    this.destroyMicrofrontend(this.name);
    }

    View Slide

  52. async renderMicrofrontend(name, base) {
    await this.$mf.ensureMicrofrontendReady(this.name);
    this.isReady = false;
    this.error = null;
    try {
    const host = document.createElement('div');
    const wrapper = this.$el.querySelector('.microfrontend-wrapper div');
    wrapper.innerHTML = '';
    wrapper.appendChild(host);
    const microfrontend = await this.$mf.getMicrofrontendByName(name);
    microfrontend.render(host, { base: base });
    this.currentInstance = name;
    } catch (e) {
    this.error = e;
    throw e;
    }

    View Slide

  53. async destroyMicrofrontend(name) {
    try {
    const microfrontend =await this.$mf.getMicrofrontendByName(name);
    microfrontend && microfrontend.destroy();
    } finally {
    this.isReady = false;
    this.currentInstance = null;
    }
    }

    View Slide

  54. LOADER
    54
    This entity could fetch user's Micro Frontends versions and the
    Micro Frontend files themselves.

    View Slide

  55. async function getManifest(uri) {
    return fetch(uri).then(response => {
    return response.json();
    });
    }
    P.S. `try {} catch {}` is above

    View Slide

  56. async function getFileCollection(name, version) {
    const url = `/microfrontends/${name}/${version}`;
    const manifest = await getManifest(`${url}/manifest.json`);
    return Object.values(manifest).reduce((result, value) => {
    if (value.endsWith('.js')) {
    result.js.unshift(`${url}${value}`);
    }
    if (value.endsWith('.css')) {
    result.css.unshift(`${url}${value}`);
    }
    return result;
    },
    {
    css: [],
    js: []
    }
    );
    }

    View Slide

  57. async function getFileCollectionWithLocalOverride(name, version) {
    const port = window.__MF_DEV[name.toUpperCase()];
    if (port) {
    console.warn(`Microfrontend '${name}' was loaded from localhost`);
    return {
    js: [`//localhost:${port}/${name.toLowerCase()}-app.js?v=` + +new Date()]
    };
    }
    return getFileCollection(name, version);
    }

    View Slide

  58. async ensureMicrofrontendReady(name) {
    if (this.state[name]) return true;
    const version = this.props.bomf.versions[name];
    try {
    const config = window.__MF_DEV
    ? await getFileCollectionWithLocalOverride(name, version)
    : await getFileCollection(name, version);
    for (const css of config.css || []) {/**/}
    for (const js of config.js || []) {/**/}
    const microfrontend = await this.getMicrofrontendByName(name);
    const notify = this.props.notify;
    microfrontend && microfrontend.initialize({
    ...this.props,
    notify: {
    error(e, atts = {}) {
    notify.error(e, { ...atts, source: name, version }); } }
    });
    this.state[name] = microfrontend;

    View Slide

  59. getMicrofrontendByName(name) {
    const namespace = '__bo__mf__';
    return window[namespace] && window[namespace][name];
    }

    View Slide

  60. window['__bo__mf__'] =
    window['__bo__mf__'] || {},
    window['__bo__mf__']['players-search'] = // ...

    View Slide

  61. ROUTER
    61
    Main routes and global routes orchestration is placed in SHELL as well (passed as
    a prop during each Micro Frontend's rendering phase). Visually it is presented in
    sidebar. It dictates at which URL, which Micro Frontend to run.

    View Slide

  62. const router = new Router({
    mode: 'history',
    routes: [
    {
    path: '/login',
    name: 'Login',
    component: Login
    },
    {
    path: '/players/search',
    components: {
    default: PlayerSearch,
    leftSideBar: Navigation
    }
    }
    // …
    was

    View Slide

  63. const router = new Router({
    mode: 'history',
    routes: [
    {
    path: '/login',
    name: 'Login',
    component: Login
    },
    {
    path: '/players/search',
    components: {
    leftSideBar: Navigation,
    default: Microfrontend // RENDERER
    },
    props: { default: { name: 'players-search', base: '/' } }
    },
    // …
    became

    View Slide

  64. export const createRouter = base => {
    return new Router({
    mode: 'history',
    routes: [
    {
    path: `${base}players/search`,
    name: 'PlayerSearch',
    component: PlayerSearch
    },
    // ...

    View Slide

  65. {
    path: `${base}players/:playerId/`,
    name: 'header',
    component: Player,
    children: [
    {
    path: `trxhistory`,
    name: 'trxhistory',
    component: TRXHistory
    },
    {
    path: 'auditlog',
    name: 'auditlog',
    components: { default: Microfrontend },
    props: { default: { name: 'auditlog', base: `${base}players` } }
    },
    // ...

    View Slide

  66. Micro Frontend entry
    66

    View Slide

  67. Vue.use(...);
    async function initialize(props) {
    applyIdentityService(props.identity);
    applyLocalizationService(props.localization);
    applyRootRouter(props.rootRouter);
    applyEventEmitter(props.ee);
    Vue.use(UIKit, {});
    Vue.use(BoMicrofrontendKit, props);
    Vue.config.errorHandler = err => props.notify.error(err);
    }
    function render(element, { base }) {}
    function destroy() {}
    export { initialize, render, destroy };

    View Slide

  68. Local development
    68
    CI&CD
    feature versions, deploy

    View Slide

  69. Bootstrapping
    69
    `vue create`
    `-p, --preset Skip prompts and use saved or remote preset`
    `vue create --preset gitlab:git.betlab.com:CoreFront/vue-cli-bo-microfrontend-
    template --clone project-name`

    View Slide

  70. View Slide

  71. commitlint
    71

    View Slide

  72. View Slide

  73. View Slide

  74. semantic-release

    View Slide

  75. Backoffice for Backoffice
    75
    Canary releases

    View Slide

  76. 76

    View Slide

  77. View Slide

  78. View Slide

  79. Newrelic
    79
    As a tool for post-production monitoring

    View Slide

  80. 80

    View Slide

  81. 81

    View Slide

  82. Final architecture flow

    View Slide

  83. ROUTER

    View Slide

  84. Decisions and achievements
    1. 2 months only
    2. Did not stop feature dev
    3. Last week – release
    4. Business operating releases as usual
    5. Move to Micro Frontend 1 day
    6. Create new Micro Frontend ~5 day
    7. Node.js instead of BE devs
    8. Flexibility in terms of versioning

    View Slide

  85. Next steps
    1. Continuous deployment instead of
    continuous delivery
    2. Automated rollbacks based on
    newrelic data
    3. Performance testing of single MFE

    View Slide

  86. Take away list
    86
    1. Sell independent deploy as key to faster time to market
    2. Sell MFEs as less error prone entities due to encapsulation
    3. Think about local development
    4. Use vue-cli presets for fast bootstrapping
    5. Use storybook
    6. Use POC as release candidate
    7. Groom with the team
    8. Don’t build spaceship. Focus on the goal. Ideal is not finished.

    View Slide

  87. THANK YOU

    View Slide

  88. Q&A
    Intro
    Tech part
    Q&A
    WE ARE HIRING
    http://bit.ly/2Cef3zx

    View Slide