the state of front-end at crowdtwist

76
e State of Front-end At CrowdTwist

Upload: mark-fayngersh

Post on 14-Jul-2015

1.532 views

Category:

Technology


1 download

TRANSCRIPT

The State ofFront-endAt CrowdTwist

Different apps with lots of code...☞ FanCenter: 22k (SLOC)

☞ Control Center: 12k

☞ Widgets: 9k

...written in various libraries and frameworks...

☞ FanCenter: Backbone.js, Marionette.js, Geppetto, RequireJS

☞ Control Center & Widgets: AngularJS

...using different tools.☞ FanCenter: Crusher (in-house build tool), Mocha

☞ Control Center & Widgets: npm, Bower, Grunt, Express, Karma

So what?

Growing painsAs a new member of the team, I would like to learn CoffeeScript, Jade, Sass, as well as Backbone, Marionette, Geppetto, Angular, Grunt, and Crusher, so that I can develop new features.

Some of these may seem relatively simple to pick up and start using, but it becomes hard really fast to write scalable and maintainable code across the different apps and frameworks.

Think bloated views and spaghetti events in Backbone and custom Angular directives with crazy $scope magic and deep equality checks.

❝Just because you're using Backbone and Angular doesn't mean your code has to suck.❞—a reasonable developer

To which I respond...

True! But... it makes it harder to grow an idiomatic codebase. Each framework has its own special sauce and set of rules you need to understand and follow if you want to write maintainable code.

Unless, of course, every developer is a rockstar and their thought process is identical to mine!

That's how it works, right?

Right!?

How did we get here?☞ Magical frameworks

☞ False separation of concerns

☞ Nascent tools and rapidly evolving ecosystem

MagicalFrameworks

Frameworks

Frameworks make certain assumptions about how an application will function, providing tools out of the box to greatly simplify the development process as a whole.

Tend to stress thinking less and doing more, faster.

For certain use cases, this is a good solution, eg. basic CRUD interfaces, smaller single-page apps.

Frameworks

When dealing with larger applications and growing pains described earlier, it is valuable to ask the following question:

What are the costs / consequences, if any, of coupling your application to a framework?

Frameworks

Consider a form view leveraging a custom directive to render a dynamic list of inputs:

<div ng-controller="SurveyCtrl"> <form name="surveyForm"> <question-list ng-model="survey.questions" /> </form></div>

Now ask yourself, who owns the view(s)?

SeparationofConcerns

Separation of concerns

Separating concerns, such as making requests, validating forms, and rendering dynamic inputs, into separate components makes sense.

Directives make sense, as do route controllers and form validators.

The problem is how these components communicate, the implicit dependencies between them, and being able to answer the original question of who owns the view?

Separation of concerns

If Robert Plant comes along and wants to add a new feature to the existing functionality, he would first need to:

☞ Understand how nested object properties affect a child's $scope

☞ (Kinda) understand the differences between scope, child scope, isolate scope, transculsion

☞ The evils of ngInit and its partner in crime $scope.$watch

☞ $modelValue → $formatters → $viewValue → $render $modelValue ← $parsers ← $viewValue ← $setViewValue

Separation of concerns

This magic makes AngularJS a simplicity trap.

Angular.js is attractive when coming from Backbone.js where you have to do everything yourself (not an ideal situation either).

The problem is you relinquish a non-trivial amount of ownership and control over your application in hopes of the magic paying off and a false sense of simplicity.

When dealing with large applications that consistently grow in features with an increasing amount of developers, ownership and true simplicity become even more important.

Here, here, Angular...You're not so bad!

You're just growing up so fast...Mommy can't keep up with your needs!

No, seriously, what's up with that?☞ The API is radically different

☞ It's essentially a new framework

☞ No support for IE 8 (no one wants to, but we still need to)

☞ Did I mention it's a completely new framework?

We just started building our Angular apps last year!!

RapidlyEvolvingEcosystem

Change is good

That's how the radical notion of front-end development came to be, as well as amazing tools like jQuery, Backbone and Angular.

Stability is better

Is the tool simple, small and predictable enough to use now and still support next year?

Turns out that was a hard question to answer last year.

And it's still hard to answer today!

...they say

But I'm feeling more optimistic it's not going to be for long.

Let's talk about JavaScript

mmm vanilla

The language is maturing

This means we can simplify our toolchain and remove additional languages like CoffeeScript.

We don't need CoffeeScript, it just makes JavaScript better.

But now, JavaScript is making JavaScript better!

The ecosystem is maturing

The tooling is maturingimport {get} from "common/request.js";

class MyComponent {...};

export default MyComponent;

I know what you're thinking

❝JavaScript is great and all, but I need to write an app and I'm not gonna start rolling my own router, MVC library and templating solution❞—a reasonable developer

Let's talk about React

Skeptical? I was too...

Library? User Interfaces?

What year is this, 2006?

There has been a lot of wizardry recently

React may have just found the sweet spot

Back to basics

Instead of building controllers to manage views that depend on other controllers that manage views that manage templates that manage DOM, just build self-contained Components.

import React from "react";import Question from "views/question";

let QuestionList = React.createClass({ render() { let questions = this.props.questions.map(question => { return <Question data={question} />; });

return ( <div class="questions"> {questions} </div> ); }});

export default QuestionList;

❝Dude, is that HTML in your business logic? WAT?? You know you're not supposed to...❞—a reasonable developer

Not quite.That HTML is actually JSX, a syntax that is essentially XML and allows for the DOM to be composed alongside your logic.

If you think that's breaking all the rules, newsflash. You're doing it already.

unwatch = $scope.$watch('survey', function(survey) { if (survey) { unwatch(); $scope.survey.hasQuestions = survey.questions.length > 0 ? true : false; }});

<div ng-controller="SurveyCtrl"> <form name="surveyForm"> <input type="text" ng-init="survey.title = survey.title || 'Default'" ng-model="survey.title"> <input type="checkbox" ng-model="survey.hasQuestions"> </form></div>

It may not be as obvious as JSX, but the coupling is still there!

With React, you at least get all the dependencies isolated to a single component in a single file.

That is ownership.

If you still oppose the idea of JSX, you can use React without JSX:

React.createElement(Question, {data: question}, "innerText");

CompositionYour component can render other components!

<QuestionList> <Question /> <Question /></QuestionList>

(without the complexity of isolate scopes and transclusion)

Speed

You may be thinking the (re)rendering of these components is slow, but React is smart.

React operates on the DOM in-memory before making the smallest set of changes to the browser's document.

This is facilitated by a straightforward definition of state.

State

A component's data is managed by 2 objects, props and state.

props is the configuration of your component passed in as attributes. It is immutable.

state is private to that component and defines the mutations a component undergoes, typically causing re-render.

State

let QuestionList = React.createClass({ getInitialState: { hasAnswer: false },

processAnswer(answer) { this.setState({ hasAnswer: true }); }});

<QuestionList title="Answer these!" />

☞ title is a prop that is passed into the component

☞ hasAnswer is part of the state of the component

State

With React, you know the exact state a component is in at any given point in time, and the UI will reflect that consistently.

With Angular, it is a lot harder to tell what the state of a particular $scope is, depending on ongoing $digest loops, $watch calls, and other magical constructs.

Used in production

There's more...

☞ Check out http://facebook.github.io/react/

☞ Play around with it

☞ Form your own opinion

How could weuse Reactat CrowdTwist?

Quick look at FanCenter

White-label web app for user engagement with brands

Users complete various activities to earn rewards

From a dev perspective

☞ Heavy data-driven customization of UI

☞ Show this view if this client

☞ Rounded corners for that client

☞ Big views with dynamic subviews

☞ Composition?

We chose Marionette.js

Boilerplate views on top of Backbone to simplify common use cases, eg. ItemView, CollectionView, Layout.

Keep in mind it is late 2012, Backbone.js is the cool kid on the block

It will be at least a year until anyone knows what Angular is

Marionette.js

class HomePage extends Marionette.Layout template: HomePageJadeTemplate

# Marionette.Layout container for subviews regions: dashboardRegion: '#dashboard-region' ...

_initDashboard: => @dashboardView = new DashboardView({...}); @listenTo this, 'show', => @dashboardRegion.show @dashboardView

Backbone / Marionette

☞ Many moving parts

☞ Poor layout/view lifecycle control

☞ Spaghetti events around DOM presence

☞ False separation of concerns

☞ Simple routing of urls to function calls make managing state, nested views, and re-renders a very manual and tedious process.

Custom build tool, a.k.a Crusher

☞ In-house build tool

☞ Developed to facilitate control of custom client implementations on the front-end (white-label)

☞ Each client has its own app.js and app.css files

☞ Built dynamically from a directory structure organized by client id

☞ Leverages Sass $variables to override defaults with custom configuration

Custom build tool, a.k.a Crusher

config.scss

clients/├── 1└── stylesheets ├── config.scss

Pros

☞ Front-end owns the view (kinda)

☞ Fits well into model of Cascading Style Sheets

☞ Sharing code with minimal duplication

Cons

☞ Maintenance costs of custom build tool

☞ Very (very) slow build time

☞ False separation of concerns

☞ Database still requires knowledge of specific client configuration

☞ Back-end already provides view-driven data

What if we move all client configuration to the server and use React to build components that manage their own data, styling, and templates?

let HomePage = React.createClass({ mixins: [TextKeyMixin, StyleMixin],

statics: { fetchData(params) { return get({ page_data: { text: ['title.home'] }, model_data: { activities: ['id', 'title'] }, style_data: { attributes: ['borderRadius', 'fontSize', 'backgroundColor'] } }); } },

render() { return ( <div id="home-page" className={this.styleData}> <h1>{this.getTextKey('title.home')}</h1> <Activities activities={this.getModelData('activities')} /> </div> ); }});

Testing components is simple too..

import SampleComponent from "src/common/sample_component.jsx";let {TestUtils} = React.addons;

describe('Sample Component', function() { it('should render with no props', function() { let sampleComponent = TestUtils.renderIntoDocument( <SampleComponent /> );

let heading = TestUtils.findRenderedDOMComponentWithTag( sampleComponent, 'h1');

expect(heading.getDOMNode().textContent).to.be.empty(); });});

across various platforms and devices

$ npm test

To conclude

☞ The problem of scaling large applications with magical frameworks is real

☞ If you think you separated your concerns, think again

☞ The web is changing, keeping up is nearly impossible

☞ Ownership is important, how would you define simplicity?

☞ JavaScript is slowly solving its own problems

☞ There is no silver bullet

To conclude

☞ React's approach is to keep it small and contained

☞ It's just a library for building UI using JavaScript

Suddenly, it's no longer 2006.

Thanks!

☞ @pheuter

☞ CrowdTwist