Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Beyond the Asset Pipeline

Beyond the Asset Pipeline

My talk from RailsCamp Perth 2014

Ivan Vanderbyl

November 16, 2014
Tweet

More Decks by Ivan Vanderbyl

Other Decks in Programming

Transcript

  1. Ivan Vanderbyl @ivanderbyl github.com/ivanvanderbyl I’m Ivan Vanderbyl, I’m one of

    the front-end engineers at DigitalOcean, and this talk is largely based on how we moved away from the Asset Pipeline.
  2. This is a slide from DHH’s talk at RubyConf 2011

    when Rails 3.1 was announced. This was DHH’s vision for the future of Javascript in Rails.
  3. DHH's future of JavaScript Asset Pipeline Node.js Today 2011 2012

    2013 2014 2015 It was definitely a move forward from the previous practice of vending everything in /public. But it came with a whole new set of problems, and unfortunately the future didn’t work out this way.
  4. TODAY Two things happened. We ended up with a lot

    more javascript in the /app directory, and Node.js came along and taught us that javascript could be composed of small single purpose modules.
  5. //= require_tree . //= require_self More javascript. This was largely

    because require_tree seemed like a good idea at the time
  6. // products.js $(function() { ... }); // users.js $(function() {

    ... }); // account.js $(function() { ... }); // templates.js $(function() { ... }); // controllers.js $(function() { ... }); But all it does is produce a bunch of concatenated javascript named application.js, which typically includes a new DOM ready callback for each function, just to be sure it is safe to run and manipulate the DOM.
  7. This doesn’t just load on every page, it executes on

    every page. This is a mess. This doesn’t just load on every page, it executes on every page. This in turn has page load performance implications.
  8. We don’t get paid to write lines of beautiful Ruby

    code We get paid to build products which customers love you don’t get paid for the aesthetics of your code. You get paid to delivery great products, and part of any great product is a great user experience. And the expectation is quickly becoming that any great user experience will involve javascript. So let’s treat it as a first class citizen in our applications.
  9. Great products require great user experiences you don’t get paid

    for the aesthetics of your code. You get paid to delivery great products, and part of any great product is a great user experience. And the expectation is quickly becoming that any great user experience will involve javascript. So let’s treat it as a first class citizen in our applications.
  10. Great user experiences are built on friendly interactions you don’t

    get paid for the aesthetics of your code. You get paid to delivery great products, and part of any great product is a great user experience. And the expectation is quickly becoming that any great user experience will involve javascript. So let’s treat it as a first class citizen in our applications.
  11. Javascript often plays front-stage role building interactions you don’t get

    paid for the aesthetics of your code. You get paid to delivery great products, and part of any great product is a great user experience. And the expectation is quickly becoming that any great user experience will involve javascript. So let’s treat it as a first class citizen in our applications.
  12. Let’s take JS more seriously you don’t get paid for

    the aesthetics of your code. You get paid to delivery great products, and part of any great product is a great user experience. And the expectation is quickly becoming that any great user experience will involve javascript. So let’s treat it as a first class citizen in our applications.
  13. The what and how of modular Javascript I’m going to

    talk about how to use modular JS with Rails, and then show you an example React component App.
  14. // component_a.js // ES6 Module Definition import MyDependency from './my_dependency';

    var ComponentA = MyDependency.extend({ ... }); export default ComponentA; Let’s look at the future. This is an ES6 module. ES6 is the next generation of JS which is still largely in a proposal state, but some parts can be transpiled to ES5 today. Like this module.
  15. Isolated Code The first thing modular JS gives us is

    code isolation. Everything inside a module will only ever be declared once, and re- used everywhere you require it.
  16. Asset compilers can remove dead code paths Compilers can remove

    code which never gets called, keeping your compiled assets small. Only use a portion of a framework, instead of loading everything.
  17. Test everything in isolation Testing can be done client side,

    inside a headless browser, or on the server, because it’s all isolated. This makes testing more consistent with other languages. You don’t need to spin up the whole stack to run your JS tests
  18. webpack Lets talk about Webpack. It replicates some of the

    features of the AssetPipeline, but it adds support for everything I’ve just mentioned, and I’m going to show you how to use it with Rails.
  19. app/assets/javascripts ├── components │ └── ... ├── _application.js ├── _vendor.js

    ├── application.js └── vendor.js So we might have a structure like this, notice we have some underscored prefixes on application and vendor. These are our webpack entry point files. Everything in here will be compiled to a bundle file inside vendor/assets/javascripts
  20. // _application.js /** * Application Entry Point * * @type

    {React Component} */ window.App = require('./app').default; Inside _application.js we might have some requires like this. This is all valid javascript, using the commonJS require conventions, which will be picked up by Webpack
  21. // _vendor.js /** * Globally require anything which * we

    want to include in vendor.bundle and * webpack will package it here instead * of the application.bundle. */ require('react'); require('react/addons'); One of the really useful features of webpack is chunking. You can create a vendor chunk which just loads the libraries, so you can have a large vendor bundle which changes rarely and a small app bundle which changes often, which is ideal for CDN caching.
  22. // app/assets/javascripts/application.js //= require application.bundle // app/assets/javascripts/vendor.js //= require jquery

    //= require jquery_ujs //= require vendor.bundle Inside the actual vendor.js we just tell the Asset Pipeline to load our compiled static bundle, and the same in application.js
  23. // webpack.config.js var webpack = require('webpack'); module.exports = { context:

    __dirname + '/app/assets/javascripts', entry: { application: './_application.js', vendor: './_vendor.js', }, output: { filename: '[name].bundle.js', path: __dirname + '/vendor/assets/javascripts', }, }; The Webpack config to make all this work is actually quite simple, we have an entry point which loads our modular JS requires, and an output, which dumps the static assets.
  24. $ webpack --compile --watch Hash: 23e1444af49a5c660291 Version: webpack 1.4.10 Time:

    5780ms Asset Size Chunks Chunk Names vendor.bundle.js 648825 0 [emitted] vendor application.bundle.js 301145 1 [emitted] application + 201 hidden modules We can then run the webpack command line tool and output our JS bundles ready for rails to load. There’s also a handy — watch flag to have webpack recompile when things change.
  25. $ npm install moment —save-dev var moment = require(‘moment’); We

    can now install external modules and use them with Webpack.
  26. http://webpack.github.io/docs/testing.html Testing For time I won’t go in to testing

    here, it’s a whole topic in itself. But you can either test webpack modules in the browser, or on the server with Node.js, and here’s the Webpack testing guide.
  27. React.js This talk was actually going to be all about

    React and Flux, but ultimately it made sense to cover Webpack, because without it using React with Rails is not fun
  28. React.js The V in MVC… sort of React is not

    a framework, it’s just the View layer, and the rest is left up to you to implement.
  29. React powers the view layer of Facebook It was created

    at Facebook to power large parts of the Facebook front-end, and all of the Instagram web experience
  30. React is a… Highly performant component centric View layer. React

    is a highly performant view layer, it is not a framework, and it doesn't handle your business logic.
  31. Components have internal state setState({ ... }); Each component has

    its own internal state and properties, nothing more. When the state changes, React renders a virtual DOM representation of the component based on that state.
  32. Virtual DOM ey? Each component has it’s own render method

    which returns virtual DOM nodes, but you never need to call this. It’s used internally by React in response to setState.
  33. Virtual DOM Diffing http://facebook.github.io/react/docs/reconciliation.html The real power of React is

    the DOM diffing, or reconciliation. Once we have a virtual DOM output, React diffs this against the real DOM, then merges the changes with the real DOM, very efficiently. You can read about the crazy heuristics which make this work
  34. setState(); Virtual DOM Reconciliation() DOM Render(); This is the flow.

    You can setState somewhere in your component, which in turn ensures that your DOM is consistent with state you expect.
  35. Your component’s state is always represented in the DOM Your

    component is always represented in the DOM, no separation of templates and view logic, because they should be tightly coupled, because that’s how they usually get used.
  36. Re-render the whole application on every change React is designed

    so you can re-render your whole application on every change
  37. Turns out Javascript is kind of fast Javascript is actually

    quite fast, and this is rarely the bottleneck.
  38. module React from 'react'; var MyComponent = React.createClass({ render: function()

    { return <div>Hello {this.props.name}</div>; } }); export default MyComponent; Here’s an example component. Notice the weird inline HTML stuff, isn’t that crazy?
  39. module React from 'react'; var MyComponent = React.createClass({ render: function()

    { return React.createElement("div", null, "Hello ", this.props.name); } }); export default MyComponent; That’s JSX, which you can compile on the server or the client, or not use at all, because it only gets converted to JS.
  40. render: function(){ <MyComponent name="Ivan" /> }, You can render other

    components just like HTML tags. Very consistent API.
  41. ParentComponent MyComponent Unidirectional Data Flow state = { … }

    You can treat the state of your top level component as the definitive source of truth and just re-render the whole UI every time something changes. This enforces an easy to reason about single direction of data flow.
  42. <MyApp /> <ButtonComponent /> <ListItemComponent name=“Item 1” /> <ListItemComponent name=“Item

    2” /> <ListItemComponent name=“Item 3” /> So an example UI might be composed like this. Your life with React will be significantly easier when each component is kept as simple as possible.
  43. It’s best if components only do one thing It’s best

    if components only do one thing. This makes them easier to understand, and easier to reuse in other components.
  44. var MyApp = React.createClass({ getInitialState: function(){ return { items: [

    {name: "Item 1"}, {name: "Item 2"}, ]}; }, render: function() { var listItems = this.state.items.map(function(item) { return <ListItemComponent name={ item.name } />; }); return ( <ul>{ listItems }</ul> ); } }); One thing I really like about React is that you can use standard javascript operators to handle composing your UI. Example here we use map on an array of items to return another array of item components, which we then render in a list
  45. clickHandler: function(){ var items = this.state.items; items.push({ name: "New Item"

    }); this.setState({ items: items }); }, render: function() { var listItems = this.state.items.map(function(item) { return <ListItemComponent name={ item.name } />; }); return ( <div> <ul>{ listItems }</ul> <a onClick={ this.clickHandler }>Add Item</a> </div> ); } }); This makes adding new items to our list as simple as updating the state, in this case just pushing a new item on to the array.
  46. So.. what about business logic? So if components aren’t meant

    to contain business logic, where do we put this?
  47. React with Backbone One obvious solution is to use React

    with Backbone, which I’ve heard of people having a lot of success with.
  48. React with Flux However, one more interesting approach is Flux,

    a data flow pattern which facebook uses to make it easier to reason about the state of their application