NRL Front-End

Matt Stow

Lead UX Engineer

Web

B.C.

A.D.

The News page uses 4 unique Vue apps

Multiple apps? Huh?!

  • We‘re not an SPA, so we don‘t need to manage the entire application‘s state from one single source
     
  • Unlike React, Vue has a clear distinction between what‘s an app and what‘s a component
     
  • Each app is restricted in its scope. It only has the exact template and data that it needs
     
  • Each page of the website can be completely different
     
  • Our resilience to failure is much greater with everything independent
broncos: (
  primary: #6c1d45,
  tint: #521633,
  tint-rm: #3d0d21,
  secondary: #9e2b64,
  highlight: #f8cc0d,
),
bulldogs: (
  primary: #00468b,
  tint: #00306f,
  tint-rm: #001843,
  secondary: #0072bc,
  highlight: #0090f0,
),
cowboys: (
  primary: #0c2340,
  tint: #0c192f,
  tint-rm: #060d1e,
  secondary: #22447a,
  highlight: #ffd100,
),
dragons: (
  primary: #cd232c,
  tint: #a6121c,
  tint-rm: #1a0303,
  secondary: #737373,
  highlight: #dedede,
),
@mixin generate-u-themes($selector-prefix) {
  @each $theme, $properties in c(themes) {
    @each $property, $value in $properties {
      $u-theme-selector: unquote(".#{$selector-prefix}-#{$theme}") !global;
      $u-theme-variant: $property !global;

      @include generate-u-theme-rules(color) {
        @include u-color-properties($value, false);
      }

      @include generate-u-theme-rules(bg-color) {
        background-color: $value;
      }

      @if ($property == highlight) {
        @include generate-u-theme-rules(bg-color, --legible-color) {
          @include u-color-properties(get-legible-color($value), false);
        }
      }

      @include generate-u-theme-rules(border-color) {
        border-color: $value;
      }
    }
  }
}
.t-broncos .u-t-color-primary {
  color: #6c1d45;
}

.t-broncos .u-t-bg-color-primary {
  background-color: #6c1d45;
}

.t-broncos .u-t-border-color-primary {
  border-color: #6c1d45;
}

.t-broncos .u-t-color-tint {
  color: #521633;
}

.t-broncos .u-t-bg-color-tint {
  background-color: #521633;
}

.t-broncos .u-t-border-color-tint {
  border-color: #521633;
}

.t-broncos .u-t-color-tint-rm {
  color: #3d0d21;
}

.t-broncos .u-t-bg-color-tint-rm {
  background-color: #3d0d21;
}
.t-broncos .u-t-border-color-tint-rm {
  border-color: #3d0d21;
}

.t-broncos .u-t-border-color-tint-rm {
  border-color: #3d0d21;
}

.t-broncos .u-t-color-secondary {
  color: #9e2b64;
}

.t-broncos .u-t-bg-color-secondary {
  background-color: #9e2b64;
}

.t-broncos .u-t-border-color-secondary {
  border-color: #9e2b64;
}

.t-broncos .u-t-color-highlight {
  color: #f8cc0d;
}

.t-broncos .u-t-bg-color-highlight {
  background-color: #f8cc0d;
}

.t-broncos .u-t-border-color-highlight {
  border-color: #f8cc0d;
}

Match Centre

~80% smaller 💪

{
  "44|2018-03-11T05:21:47Z": {
    "gameSeconds": 339,
    "awayTeam": {
      "score": 4,
      "scoring": {
        "$op": "replace",
        "tries": {
          "made": 1,
          "summaries": ["Josh Hoffman 5'"]
        }
      }
    },
    "timeline": [
      [7, {
        "title": "Try",
        "type": "Try",
        "gameSeconds": 339,
        "playerId": 500256,
        "teamId": 500031,
        "awayScore": 4
      }]
    ]
  }
}
setupLiveStream() {
  this.lastSequence = -1;
  this.eventSource = new EventSource(this.liveUrl);

  this.eventSource.addEventListener('message', (e) => {
    if (e.lastEventId === 'close') {
      this.eventSource.close();
    }
    else {
      try {
        const patchData = JSON.parse(e.data);

        const eventId = e.lastEventId.split('|');
        const sequence = Number(eventId[0]);
        const timestamp = eventId[1];

        if (timestamp > this.lastUpdated) {
          if (this.lastSequence === -1 || (sequence - this.lastSequence === 1)) {
            this.match = this.applyJsonPatch(patchData);
            this.lastSequence = sequence;
            this.match.updated = timestamp;

            this.clockTimer().init();
          }
          else {
            this.disconnectLiveStreamAndReFetch();
          }
        }
      }
      catch (error) {
        this.disconnectLiveStreamAndReFetch();
      }
    }
  });
},

Apps

import { StyleSheet } from 'react-native';

import c from '@helpers/color';
import s from '@helpers/spacing';
import t from '@helpers/theme';

export default StyleSheet.create({
  'button': {
    alignItems: 'center',
    backgroundColor: t('secondary'),
    borderColor: t('secondary'),
    borderWidth: 2,
    flexDirection: 'row',
    borderRadius: 5,
    justifyContent: 'center',
    height: 50,
    paddingHorizontal: s('small'),
    width: '100%',
  },
  'button--type-hollow': {
    backgroundColor: 'transparent',
  },
  'button.is-pressed': {
    backgroundColor: t('highlight'),
    borderColor: t('highlight'),
  },
  'button__text': {
    color: c('white'),
    fontSize: 14,
    letterSpacing: 2,
    textAlign: 'center',
    width: '100%',
  },
  'button--on-light button__text': {
    color: c('gray 222222'),
  },
});
<View style={b('match-header')}>
  <View
    style={[
      b('match-header__bg'),
      u('t-bg-color-primary'),
      u(`t-${props.theme.key}-bg-color-primary`)
    ]}
  >
    <Logo
      style={b('match-header__bg-silhouette')}
      height={300}
      theme={props.theme}
      variants={SILHOUETTE_VARIANTS}
      width={300}
    />
  </View>
</View>

Thanks

NRL Front-End Architecture

By Matt Stow

NRL Front-End Architecture

An overview of our front-end architecture across web and apps

  • 1,847