Moving Microsoft Teams to React

Software development at Scale!

Abhik Mitra

@complancoder

Engineering Manager @

  1. Currently Building Microsoft Teams
  2. I helped build Outlook for Android
  3. Seven years of Work Ex

Microsoft Teams

Microsoft Teams

1000+ Engineers

3 Continents 

325,000 Organizations

8M+ Lines of Code 

Microsoft Teams

2015

2016

TimeLine

2017

2018

TimeLine

North Star

  • Move to React for all UI code

  • Non UI services moves to framework agnostic code

  • Each Micro frontends doing it own release

  • Developer productivity

    • Faster Builds

Stop - Rewrite - Start

Approach 1

Approach 2

1. Speak to Designers

2. Carve out a Rectangle in Angular

3.Move Rectangle to React

3.Move Rectangle to React

  • How do we put React in Angular ?

  • How do we move non UI/services code to North star progressively ?

  • How do we move features progressively to React ?
  • How do we not impact the bundle size ?

Awesome React!

Angular Directive

Awesome React!

Empty Directive

Awesome React!

Awesome React!

Angular App

Data

Events

React-Angular Bridge

Demo

Design Goals

  1. React Code base in a separate MonoRepo
  2. React Code base will have no reference to Angular

React-Angular Bridge

var directive = reactBridgeDirective
    .forLazyLoadedComponent(() => 
    import("@msteams/component")
        .then(_ => _.AwesomeReactComponent)
    )


    .withInjected("loggerService").ofType<ILoggerService>()
    .withInjected("someAngularService").ofType<someAngularService>()

    .setUpComponentProps((services) => {
        return {
            onButtonClick:() => { /* more code */},
            randomProp: "Hello"
        }
    })

    .onComponentDidMount((component, injectedServices) => {
        /* Call functions on the component */
    })

    .create();

angular.module("app").directive('componentWrapper', directive);

1

2

3

<div>
    <component-wrapper ng-if="ctrl.isReactEnabled"></component-wrapper>
</div>

4

React-Angular Bridge

reactBridgeDirective
    .forLazyLoadedComponent(() => 
    import("@msteams/component")
        .then(_ => _.AwesomeReactComponent)
    )
    .withInjected("someAngularService").ofType<someAngularService>()
    .setUpComponentProps((services) => {
        return {
            onButtonClick:() => { /* more code */},
            randomProp: "Hello"
        }
    })
    .onComponentDidMount((component, injectedServices) => {
        component.printSomething(
            injectedServices.someAngularService.getString()
        );
    })
    .create();
class Wrapper extends React.Component {
    /* other component related code*/
    printSomething(str) {
        console.log(str);
    } 

    render() {
        return (
            <>
                {{this.props.randomProp}}
                <button onClick={this.props.onButtonClick}></button>
            </>
        );
    }
}

Type safety

Fluid API

reactBridgeDirective
    .forLazyLoadedComponent(/* use Dynamic import*/ )
    .withInjected("loggerService").ofType<ILoggerService>()
    .withManualRendering((render) => {
        return new Promise((resolve, reject) => {
            requestAnimationFrame(() => {
                /*Render React component whenever you want to*/
            });
        });
      })
    .setUpTelemetry((services) => {
      /* return scenarios for telemetry*/
      const scenarios = services.constants.getConstant("scenarios");
      return [ scenarios.scenarioOne, scenarios.scenarioTwo ];
    })
    .withErrorHandling((error, element, injectedServices) => {
        /* log the error or display a custom error component*/
    })
    .setUpComponentProps(() => (
        /* Send props to React for 2 way communication*/
    ))
    .onComponentDidMount((component, injectedServices) => {
         /* Log or do something else*/
    })
    .onComponentWillUnmount((component, injectedServices) => {
        /* Log or do something else*/
    })
    .create();

• Simple, Readable & Easy
• Less boilerplate and scaffolding
• React Code base unaware of Angular

React-Angular Bridge

Angular

Router

/calendar

/files

/calling

React Component

React Component

Angular Component

Multiple React Roots

  • React Keeps memory of last initialized Root

  • React still keeps references of the DOM nodes even if unmountComponentAtNode is called.

  • Leaked memory pops up as an Array of Fiber Nodes

  • The library fixes this issue by rendering a Fragment on unmount.

Multiple React Roots

 

  • There is ton of non Component code like AuthenticationService, LoggingService, UploadService, etc
  • Even non UI code in Angular is tied to the framework
  • Not possible to migrate all horizontal services together
  • React code should not have references to angular

 

Non UI Services

import * as React from "react";
import { SharepointFilesContainer } from "./containers";
import { LoggingService } from "@msteams/services-logging";

class SharePointFilesLayout extends React.Component<{}> {
  constructor(props: any) {
    super(props);
  }

  public render() {
    LoggingService.logInfo("Rendering Sharepoint Files View");
    return <SharepointFilesContainer />;
  }
}

export { SharePointFilesLayout };

?

Typical React Code

resolve : {
    alias : {
        "@msteams/services-logging": "shims/loggingServiceShim.ts"
    }
}

export const LoggingService: ILoggingService = 

  angular.element(document.body).injector().get('bridgeLoggingService');

loggingServiceShim.ts

Webpack to the rescue

import { map } from "lodash"
import { http } from "./http"

Lodash

resolve : {
    alias : {
        "lodash": "customLodash/index.ts"
    }
}

Custom Lodash

Awesome Component

Webpack to the rescue

import { map } from "lodash"
import { http } from "./http"

Lodash

resolve : {
    alias : {
        "lodash": "customLodash/index.ts"
    }
}

Custom Lodash

Awesome Component

Webpack to the rescue

Custom Lodash

  • New git Mono Repo 
  • Publish to Private NPM Repo
  • New Repo enforces interfaces Separation                                        
  • Identify UI rectangles that need refresh 
  • Move Rectangles to new code base .
  • New Repo enforces interfaces Separation                                        
  • Consume NPM Modules published from new repo
  • Wrap React components from these modules in Bridge
  • Use Webpack aliases to provide implementation of old services from angular                                       

Putting it all together

Thank You !

- Abhik Mitra

@complancoder

linkedin.com/in/iamabhik

Made with Slides.com