Managing large scale front-end application development

Nick Ribal

image/svg+xml

Front-end consultant, freelancer and a family man

    @elektronik

4 people, doing all this:

 

  • 10 languages

  • 50M monthly page views

  • 4 domains

  • 4 layouts

  • 4 breakpoints

  • lots of pages

  • 3rd party integrations:

    • we are embedded and we embed, in web & apps

    • tens of wildly varying integrations

  • ever-changing requirements

  • Rich content editor

  • Several back office systems

  • Embed host

90min.com

Lots more stuff...

Plan of attack

  • Philosophy and power tools

  • Constant improvement

  • Iterative development

  • Directory/file structure

  • Code conventions and standards

  • DX, in dev and production

  • Feature flags

  • Meaningful error messages

  • Readable stack traces

  • Runtime introspection

Do one thing, do it well. Compose those small things.

Use Open Source as much as possible

  • Reuse smarter people's work, for free!

  • Contribute back

  • See how others use the same tools in other ways

  • Avoid monolithic, opinionated or hard to replace things

  • npm packages

  • npm scripts

  • Don't reinvent the wheel

  • Use emerging standards & polyfills

  • No JS/CSS frameworks

  • Webpack the all-capable module bundler

  • Babel transforms future JS to ES5

  • Sass real programming language for CSS, has packages and libraries (e.g. Sassdash)

  • PostCSS auto-prefixing and optimizations

Power tools which are entire ecosystems

Continuous improvement

  • Don't compromise on quality!

    • Use good software development practices

    • ​Have mandatory code reviews

    • Learn from industry experts

  • Try and prototype new small stuff all the time

  • Adopt what works well

  • Remove what doesn't

  • When something sucks - tackle and improve it

Iterate!

  • Continuous Integration and Deployment

    • are a must have!

    • tests and TDD eliminate fear of change

  • Evolution > Revolution

    • avoid breaking changes

    • interop the old with the new

  • Technical debt is the enemy

    • sometimes "done is better than perfect", but

    • pay your debt ASAP!

    • the later you do, the more it costs

Directory === Feature

  • Features are portable and self contained
  • Most imports are local: ./something
  • A given feature's hierarchy and organization suits it's complexity and structure, not vice-versa

Let's avoid holy wars about tabs vs spaces

  • Having conventions reduces the element of surprise, which speeds development

  • Commit to conventions - or don't bother

  • Enforce them via style guides, code reviews and linting everything

Dan Abramov taught us DX

?debug-redux

?debug-externals

[scriptLoader] {name: "facebookPixel", src: "//connect.facebook.net/en_US/fbevents.js"},
               {callbackQueue: Array[2], isLoading: true, isLoaded: false}
[scriptLoader] {name: "facebookPixel", src: "//connect.facebook.net/en_US/fbevents.js"},
               {callbackQueue: Array[1], isLoading: true, isLoaded: true}
[scriptLoader] {name: "facebookPixel", src: "//connect.facebook.net/en_US/fbevents.js"},
               {callbackQueue: Array[1], isLoading: false, isLoaded: false}
[scriptLoader] {name: "comscore", src: "//b.scorecardresearch.com/beacon.js"},
               {callbackQueue: Array[1], isLoading: true, isLoaded: false}
[scriptLoader] {name: "quantcast", src: "//edge.quantserve.com/quant.js"},
               {callbackQueue: Array[1], isLoading: true, isLoaded: false}
[scriptLoader] {name: "twitter", src: "//platform.twitter.com/widgets.js"},
               {callbackQueue: Array[1], isLoading: false, isLoaded: false}
[scriptLoader] {name: "googleAnalytics", src: "//www.google-analytics.com/analytics.js"},
               {callbackQueue: Array[1], isLoading: true, isLoaded: false}
[scriptLoader] {name: "googleAnalytics", src: "//www.google-analytics.com/analytics.js"},
               {callbackQueue: Array[1], isLoading: true, isLoaded: true}

Overrides & feature flags FTW!

Server: ?mock-top-posts

[Top posts] Overriding complex
            logic, showing 5 latest

Client: #country-code=DE

[QS] overriding countryCode with 'DE'

Everywhere: ?force-world-domination

[World domination] Activated! ᕕ(ᐛ)ᕗ

Logging and console doesn't have to be ugly

  • Use varying verbosity: info/log/warn/error
  • Use colors, styling, [formatting] and  grouping

Looks good + easy to use = usable by everyone

Go sell ?ads-debug-cc to your biz people as a feature!

Works for UI too!

  • Environment (top left corner in red)
  • Ad slot data and layout overlays:
    • Populated slots are green
    • Empty slots are red
    • Blue is Taboola
  • Clicking and holding on the page shows the layout in layers, as seen in the next slide

#debug-layout is not some stupid hashtag. When added to the URL, it shows:

  • Layout is seen via semi-transparent layers
  • Env: <environment> is displayed in pre-production
  • v: FooView is the name of a Backbone View with the element outlined
  • p: PresenterName is shown for a View which has a Presenter

Consult this handy XKCD chart!

Take pride in your rendering errors!

You are rendering the UI itself, why not use it to display errors too?

(in pre-prod only ;)

Don't forget log to console too, doh

Works for AJAX too!

See the Error (hover me for details) in the header? This is it!

(hover me for details) and the tooltip are only visible in pre-prod

Note the xhr context in the console: all the info you need to troubleshoot

Meaningful error messages with relevant context make errors useful and actionable

function getApiEndpoint(endpointsConf){
  // do some stuff. when that fails:
  const message = 'FETCH types are undefined.' +
    ' Check pdo.config.api for available endpoints'
  console.error(message, {
    message,
    endpointsConf,
    api: pdo.get('config.api'),
  })
  return false
}

Naming things

explicitAndDescriptive > implObsc

  • Entity rename === file/function/module rename
  • Searching for 'size' vs 'SIZE': hundreds vs 3 files

  • Makes reading, maintaining and searching easy

const COUNTRY_CODES_TO_REFRESH_ADS = [US, AR, ]
const SLIDE_COUNT_BEFORE_ADS_REFRESH = 3
function getConfFromElementDataset(element){...}

function iCanHasName(){} > function(){} || () => {}

  • Makes stack traces readable. Especially important in transpiled code, when source maps fail you

The only (anonymous function)s here are from 3rd parties or dependent packages :)

Stack traces you can actually read are a direct result of good naming

How do I inspect what's known only during runtime?

Runtime introspection

Utils and helpers for DRYness and easy mocking

  • array: isSubsetOf, uniq, diff, range, flatten
  • window: event wrappers, queryString
  • dom: RAF, selectText, isElVisible, insertBefore/After
  • fn: makeSafeFn, memoize
  • object: serialize, map, iterate, removeFalsyValues
  • cookie, data, string, script loader and others

What's next for us @90min?

  • Moving to isomorphic/universal JavaScript
  • Rewriting our Editor in Draft.js & Immutable
  • Moving to TLS (SSL)
  • Service Workers and PWA
  • HTTP/2
  • Facebook Instant Articles and Google AMP
  • React Native for mobile?
  • Microservices in Node, Golang and others
  • Meetups on various topics

Thank you and

stay curious :)

Managing large scale front-end application development

By Nick Ribal

Managing large scale front-end application development

Lessons learned and best practices while developing and maintaining our featureful and complex sites over the last two years: philosophy, tools, frameworks and packages; developer experience across environments - including production; JS runtime introspection, debugging, troubleshooting and reconfiguration techniques; CSS/HTML layout and component configuration troubleshooting; specific advice and best practices you can apply to your app today

  • 2,062