Slide 1

Slide 1 text

From Angular to React by @Jack_Franklin

Slide 2

Slide 2 text

From X to Y My experience migrating complex software (Regardless of the tech choices).

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

The Store

Slide 9

Slide 9 text

— Started 2 years ago, built on Angular 1 — Primarily used within iframes — 2 main developers no longer with the company — Heavy on features — Tickets being sold constantly — Used by our flagship clients

Slide 10

Slide 10 text

The store works well.

Slide 11

Slide 11 text

But changing it is very tricky.

Slide 12

Slide 12 text

— Unclear which features are actually needed — Many different use cases — Regular general admission tickets — Reserved seating tickets — Queued tickets for big sales — VIP tickets ("meet and greet") — Merchandise addons (Ticket + album = 10% discount) — We have a lot of stores in the wild

Slide 13

Slide 13 text

We want to be in a position to iterate quickly on this product, but with the current codebase we can't.

Slide 14

Slide 14 text

What do we do?

Slide 15

Slide 15 text

1. Start completely from scratch 2. Stick with Angular 1, and rewrite complex parts. 3. Migrate from Angular 1 to 2/3/4 4. Migrate bit by bit to something else

Slide 16

Slide 16 text

Start completely from scratch ! — We can't afford to not work on bugs, improvements on the existing product — The Angular app began as a big rewrite - business wouldn't like it to happen again

Slide 17

Slide 17 text

Stick with Angular 1 ! — Angular 1 is reaching its end - focus is now on Angular 2 and beyond. — Not much developer expertise in Angular across Songkick

Slide 18

Slide 18 text

Migrate from Angular 1 to 2 and beyond ! — Not much developer expertise in Angular across Songkick — Not many Angular evangelists at Songkick

Slide 19

Slide 19 text

Migrate bit by bit to something else ! — Allows us to work on bugs in existing code whilst migrating others — Allows us to build new features whilst migrating

Slide 20

Slide 20 text

Migrate bit by bit to something else ! — We can experiment by moving piece by piece and try different approaches — Current stores continue to function correctly

Slide 21

Slide 21 text

Migrate bit by bit to something else ! — We can move quickly and release small parts at once - avoiding risky big bang releases — Small downside: migration specific code - plumbing

Slide 22

Slide 22 text

What to migrate to?

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

Why React? ! I and my team are all familiar with React ! Another, newer, product at Songkick was built with React ! React leaves you with less framework specific code

Slide 25

Slide 25 text

Note: I am not saying that Angular is a bad choice, or that you shouldn't use it. Just for us, it wasn't the best fit.

Slide 26

Slide 26 text

So, we've decided to migrate, and what to migrate to.

Slide 27

Slide 27 text

Now for the hard part — 40,000 lines of JavaScript — Most of those lines are tied directly to Angular — Many dependencies outdated due to lack of maintanance over past 6 months — A store that cannot be broken at any point (we sell tickets pretty much continuously)

Slide 28

Slide 28 text

The Theory Components (or directives in Angular)

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

The Plan

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

The theory is easy!

Slide 37

Slide 37 text

But more complicated in practice...

Slide 38

Slide 38 text

Angular and dependency injection

Slide 39

Slide 39 text

Angular 1 was built in a time where we had no module system in JavaScript, so it provided one.

Slide 40

Slide 40 text

// define your Foo object const Foo = {...} angular.service('Foo', Foo) // in another Angular thing function SomeController(Foo) { Foo.bar() ... } Angular will inject the Foo dependency into SomeController at runtime.

Slide 41

Slide 41 text

In ES2015 we have a module system (which Babel transpiles for us) const Foo = {...} export default Foo // in another file import Foo from './foo'

Slide 42

Slide 42 text

ES2015 modules are brilliant And have loads of advantages over dependency injection that this migration gives you for free.

Slide 43

Slide 43 text

The step by step process of migrating an Angular directive to a React component

Slide 44

Slide 44 text

1. Find all dependencies that the directive (or a dependency of the directive) injects.

Slide 45

Slide 45 text

2. Migrate all of those one by one to React

Slide 46

Slide 46 text

But wait! You need to migrate localStorage-service.js away from Angular. angular.service('LocalStorage', function() { ... }) But this service is used in ~15 places in the app. And we want to release the migration in tiny chunks.

Slide 47

Slide 47 text

So, we can first pull the logic into a standalone thing: const localStorage = { ... }

Slide 48

Slide 48 text

And then export it so any non Angular code can just import it: const localStorage = { ... } export default localStorage

Slide 49

Slide 49 text

And then leave the small Angular wrapper: const localStorage = { ... } export default localStorage angular.service('LocalStorage', function() { return localStorage })

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

! Existing Angular code can still use the local storage code without being aware it's changed ! New React components can import the module without any of the Angular boilerplate " We had to write a small amount of migration specific code

Slide 52

Slide 52 text

Migration specific code Code that you know will be deleted when the migration is finished. A neccessary part of the migration but you should keep it to a minimum. You should also group it so it's easily found and removed later.

Slide 53

Slide 53 text

So, we've migrated all dependencies away from Angular

Slide 54

Slide 54 text

Now we can migrate listingsButton- directive.js to React

Slide 55

Slide 55 text

import React, { Component } from 'react' class ListingsButton extends Component { // code left out to save space :) render() { return ( Buy now! ) } }

Slide 56

Slide 56 text

But how do we embed this within Angular? Angular directives are used as if they were custom HTML elements:
  • Rihanna at The O2

    Buy now
  • Slide 57

    Slide 57 text

    No content

    Slide 58

    Slide 58 text

    ngReact lets you embed React components within Angular applications It is the piece of code that enables our entire migration strategy to work. Remember: you can render many React apps on a page in any place you want.

    Slide 59

    Slide 59 text

    Aside: React is really malleable

    Slide 60

    Slide 60 text

    With ngReact
  • Rihanna at The O2

  • Slide 61

    Slide 61 text

    We can continue this strategy, migrating the other components to React:
  • Slide 62

    Slide 62 text

    No content

    Slide 63

    Slide 63 text

    And now we can migrate this into one big React component! import React, { Component } from 'react' import ListingsTitle from './listings-title' import ListingsButton from './listings-button' class ListingsItem extends Component { render() { return (
  • ) } }

    Slide 64

    Slide 64 text

    And update our template

    Slide 65

    Slide 65 text

    No content

    Slide 66

    Slide 66 text

    https://www.sitepoint.com/organize-large-react- application/

    Slide 67

    Slide 67 text


    Slide 68

    Slide 68 text

    Thanks for coming back!

    Slide 69

    Slide 69 text

    Maintaining functionality — At no point can the store be broken. — Newly migrated code must have exactly the same functionality — Put simply: users must not notice any difference

    Slide 70

    Slide 70 text

    Unit Tests — Existing Angular tests can be migrated as the services move away from Angular — Used as an opportunity to add test coverage

    Slide 71

    Slide 71 text

    React components are very testable import React from 'react' import ListingsDate from './listings-date' import { mount } from 'enzyme' describe('ListingsDateComponent', () => { describe('an event with just a start date', function() { const event = { event_date: { date: '2016-01-01' } } const wrapper = mount(React.createElement(ListingsDate, { event })) it('formats the month correctly', function() { expect(wrapper.find('.date-block__seg--month').text()).toEqual('Jan') }) it('formats the day correctly', function() { expect(wrapper.find('.date-block__seg--day').text()).toEqual('1') }) it('formats the year correctly', function() { expect(wrapper.find('.date-block__seg--year').text()).toEqual('2016') }) }) })

    Slide 72

    Slide 72 text

    https://www.sitepoint.com/test-react-components-jest

    Slide 73

    Slide 73 text

    Unit tests are good because they provide quick feedback and can test at a very granular level.

    Slide 74

    Slide 74 text

    Unit tests (in our case) are bad because when you migrate code, you migrate tests Therefore, they are not technically proof that everything works as before.

    Slide 75

    Slide 75 text

    Acceptance tests

    Slide 76

    Slide 76 text

    No content

    Slide 77

    Slide 77 text

    Automated tests that are run using Protractor & Selenium — ! They test the entire system, with no faked API calls or fake data — ! This means they are entirely independent of the migration — " They can flake sometimes and are slow to run

    Slide 78

    Slide 78 text

    Because they are slow to run, we keep acceptance tests to our core user journeys

    Slide 79

    Slide 79 text

    They run on every single build, and they have to pass to enable us to deploy

    Slide 80

    Slide 80 text

    The pipeline

    Slide 81

    Slide 81 text

    No content

    Slide 82

    Slide 82 text

    Making sure deploys have succeeded

    Slide 83

    Slide 83 text

    Track user activity to ensure deploys are successful

    Slide 84

    Slide 84 text

    No content

    Slide 85

    Slide 85 text

    !

    Slide 86

    Slide 86 text

    Unfortunately, fires will happen during this process.

    Slide 87

    Slide 87 text

    To date we've had 5 fires caused by migration: — Two were down to bad data in our test / dev environment not matching "real life"

    Slide 88

    Slide 88 text

    To date we've had 5 fires caused by migration: — Two were particular journeys not being covered by any unit or acceptance tests

    Slide 89

    Slide 89 text

    To date we've had 5 fires caused by migration: — Another was down to timezones ! — 3 people in Japan bought tickets for an event on the wrong day because of me...

    Slide 90

    Slide 90 text

    You have to communicate how and why a fire happened, but how you'll prevent it next time Because you can never ever release bug free so!ware.

    Slide 91

    Slide 91 text

    Momentum & Prioritisation

    Slide 92

    Slide 92 text

    One of the core goals of this migration was to make the code easier to work on, add features to and fix bugs with more confidence.

    Slide 93

    Slide 93 text

    Pick work based on bugs and churn rate, not code quality Churn rates: the amount of times a file is changed

    Slide 94

    Slide 94 text

    Code quality Churn rate Bug rate Priority Bad Low Low Low Good Low Low Lowest Bad Medium High High Good Low High High Bad High High Highest

    Slide 95

    Slide 95 text

    A migrated codebase is not a perfect code base

    Slide 96

    Slide 96 text

    1. Migrate from X to Y 2. Refactor or redesign Y based on new learnings

    Slide 97

    Slide 97 text

    The next time you touch some code, you'll know more about it than before. So why make it perfect now?

    Slide 98

    Slide 98 text

    Keeping people happy and productive We knew this migration was going to be at least 6 months, more likely closer to a year.

    Slide 99

    Slide 99 text

    And in any large software migration...

    Slide 100

    Slide 100 text

    There will be good times...

    Slide 101

    Slide 101 text

    And bad ones...

    Slide 102

    Slide 102 text

    But, remember, it can always be worse...

    Slide 103

    Slide 103 text

    But, remember, it can always be worse...

    Slide 104

    Slide 104 text

    No content

    Slide 105

    Slide 105 text

    Anyway; where was I?!

    Slide 106

    Slide 106 text

    Momentum & Prioritisation

    Slide 107

    Slide 107 text

    Mix larger, multi-week work with short, 1-2 day work, to keep momentum.

    Slide 108

    Slide 108 text

    <1 day's worth of work

    Slide 109

    Slide 109 text

    1 week's worth of work

    Slide 110

    Slide 110 text

    Break down large work into small PRs

    Slide 111

    Slide 111 text

    Release early, release often

    Slide 112

    Slide 112 text

    Release early, release often ! Encourages small pull requests and units of work

    Slide 113

    Slide 113 text

    Release early, release often ! Keeps momentum up in the team

    Slide 114

    Slide 114 text

    Release early, release often ! Easier to react 1 to a bug if release causes it 1 pun very much intended

    Slide 115

    Slide 115 text

    The scout's rule Always leave things better than when you found them.

    Slide 116

    Slide 116 text

    Mix visual work + "under the hood work"

    Slide 117

    Slide 117 text

    Have some form of tracking or metrics, however rough it is

    Slide 118

    Slide 118 text

    No content

    Slide 119

    Slide 119 text

    No content

    Slide 120

    Slide 120 text

    No content

    Slide 121

    Slide 121 text

    Tooling Because you can't do a JavaScript talk without talking about tools. #fatigue

    Slide 122

    Slide 122 text

    Migrations are a good excuse to reconsider tooling And also provide a break from working on the actual app codebase.

    Slide 123

    Slide 123 text

    From Karma to Jest Our tests were running on Karma, but we wanted to try Jest from Facebook. We had (at the time) ~600 tests across ~130 files. So we started migrating them!

    Slide 124

    Slide 124 text

    No content

    Slide 125

    Slide 125 text

    From Browserify to Webpack The store was built using a combination of Makefiles, misc command line tools and Browserify. This was hard for us to follow and was very brittle. We moved to Webpack which lead to reduce build times, smaller bundles and got us more inline with the rest of the community.

    Slide 126

    Slide 126 text

    By providing a mix of migration tasks (big, small, visual, "under the hood", tooling), we're able to keep work fun and interesting

    Slide 127

    Slide 127 text

    Communicate to the business Specifically: Why?

    Slide 128

    Slide 128 text

    Take 1 "Well, Angular 1 is reaching end of life and React offers a much better component model that fits our ideas of how to build so#ware" "React's lifecycle methods and small API is easier for developers to learn" "React's state model is less magical; its unidirectional data flow really simplifies code and makes it easier to reason about"

    Slide 129

    Slide 129 text

    Take 2 "Right now when you ask us for a new feature, or bug fix, it's hard and takes a long time to fix and have confidence that we've not inadvertently broken anything else" "This migration will enable us to have a leaner, stable codebase which we will have more confidence in and be able to build features and fix bugs more quickly"

    Slide 130

    Slide 130 text

    "So nothing will change in the next year on the store?"

    Slide 131

    Slide 131 text

    Long term user advantages

    Slide 132

    Slide 132 text

    xf buddies

    Slide 133

    Slide 133 text

    How's it going so far? We estimate to be ~51% done. Most of the core visual journey is done. All forms that users interact with are now in React. We've had very few bug reports on any of our new React code, compared to the old Angular code.

    Slide 134

    Slide 134 text

    We've done some stuff well, and I expect to learn we've done some stuff badly.

    Slide 135

    Slide 135 text

    Will you ever be done? Our natural "finish" point is when we've removed Angular from our codebase. But when that happens we'll keep refactoring & revisiting old code as we fix bugs and introduce new features.

    Slide 136

    Slide 136 text

    Takeaways

    Slide 137

    Slide 137 text

    1. Don't migrate for the sake of it "Framework X is now out of date" is not a valid reason!

    Slide 138

    Slide 138 text

    2. Plan, plan and plan again

    Slide 139

    Slide 139 text

    3. Cross business communication is more important than you realise at first

    Slide 140

    Slide 140 text

    4. Prioritise based on pain points in your current application

    Slide 141

    Slide 141 text

    5. Mix up tasks based on difficulty + visual/tooling/etc to keep it interesting

    Slide 142

    Slide 142 text

    6. Have some rough metrics that you can use to track progress internally

    Slide 143

    Slide 143 text

    7. Don't be surprised if you end up rewriting some migrated code

    Slide 144

    Slide 144 text

    8. Don't appoint David Moyes.

    Slide 145

    Slide 145 text

    Thank you! — javascriptplayground.com — Questions / want a job?! [email protected]