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

A Tale of Two Apps

A Tale of Two Apps

Customers want to see your web application in a working state so they can make more meaningful and more rapid decisions on requirements. I used to accomplish this with some fake data hard coded in the html and some thrown together jQuery when I needed to simulate user feedback and transitions. This would get thrown away and reimplemented in production code and the interface was not fully polished.

Ember.js and front end tooling has changed that. Fat client, JavaScript heavy, web applications and server side code can now be built in segregation. Rich and polished user interfaces can be developed faster and easier. Developers and designers can reach 'done' without wasting development cycles.

Jamie Wright

February 25, 2014
Tweet

More Decks by Jamie Wright

Other Decks in Programming

Transcript

  1. Today, we are going to T A L K about

    3things A laid back Dude A rich Man
  2. Today, we are going to T A L K about

    3things A laid back Dude A rug A rich Man
  3. http://lebowskifest.com App #1 Google http://lebowskifest.com Web Page Title 1.Router handles

    the request 2.Controller is created 3.Controller action is called
  4. http://lebowskifest.com App #1 Google http://lebowskifest.com Web Page Title 1.Router handles

    the request 2.Controller is created 3.Controller action is called 4.Controller creates a template & provides data
  5. http://lebowskifest.com App #1 Google http://lebowskifest.com Web Page Title 1.Router handles

    the request 2.Controller is created 3.Controller action is called 4.Controller creates a template & provides data 5.Template is rendered into HTML
  6. http://lebowskifest.com App #1 Google http://lebowskifest.com Web Page Title 1.Router handles

    the request 2.Controller is created 3.Controller action is called 4.Controller creates a template & provides data 5.Template is rendered into HTML 6.HTML is returned for the response
  7. http://lebowskifest.com App #1 Google http://lebowskifest.com Web Page Title 1.Router handles

    the request 2.Controller is created 3.Controller action is called 4.Controller creates a template & provides data 5.Template is rendered into HTML 6.HTML is returned for the response MVC
  8. http://lebowskifest.com <html /> App #1 Google http://lebowskifest.com Web Page Title

    1.Browser starts page layout 2.Additional sources requested
  9. http://lebowskifest.com <html /> App #1 Google http://lebowskifest.com Web Page Title

    <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg />
  10. http://lebowskifest.com <html /> App #1 Google http://lebowskifest.com Web Page Title

    Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg />
  11. // app/assets/javascripts/availability.js ! $('select.monthYearSelect').change(function(e) { var country = $(e.target).data("country-code"); var

    city = $(e.target).data("city-permalink"); var tour = $(e.target).data("tour-id"); ! $.get("/" + country + "/" + city + "/" + tour + "/tickets/ availability/" + $(e.target).val() + "/dates"); ! e.preventDefault(); });
  12. # app/controllers/availability_controller.rb ! class AvailabilityController < ApplicationController def dates @dates

    = OfferAvailability.new(params[:id], Date.today, Date.new(year, month, -1)).dates end end
  13. // app/views/availability/dates.js.erb ! $("select.daySelect").empty(); $("select.daySelect").html("<%= j content_tag(:option, "Which Day?", value:

    "", class: "disabled", disabled: "disabled", selected: "selected") + options_from_collection_for_select(@dates, :id, :display) %>"); $("select.daySelect").removeClass("selected"); ! <% if @dates.size == 1 %> $("select.daySelect").val($("select.daySelect option:nth- child(2)").val()).change(); $("select.daySelect").addClass("selected"); <% end %>
  14. http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page Title

    1.Browser starts page layout 2.Additional sources requested
  15. http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page Title

    <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg />
  16. http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page Title

    Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg />
  17. http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page Title

    Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location
  18. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model http://lebowskifest.com/api/scenes MVC
  19. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model http://lebowskifest.com/api/scenes MVC
  20. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model 4.Route renders a View http://lebowskifest.com/api/scenes MVC
  21. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model 4.Route renders a View 5.Controller sets state on View http://lebowskifest.com/api/scenes MVC
  22. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model 4.Route renders a View 5.Controller sets state on View 6.View renders a Template http://lebowskifest.com/api/scenes MVC
  23. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model 4.Route renders a View 5.Controller sets state on View 6.View renders a Template http://lebowskifest.com/api/scenes MVC MVC
  24. http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page Title

    Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg />
  25. http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page Title

    Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location
  26. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model http://lebowskifest.com/api/scenes MVC
  27. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model http://lebowskifest.com/api/scenes MVC
  28. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model 4.Route renders a View http://lebowskifest.com/api/scenes MVC
  29. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model 4.Route renders a View 5.Controller sets state on View http://lebowskifest.com/api/scenes MVC
  30. {} http://lebowskifest.com <html /> App #2 Google http://lebowskifest.com Web Page

    Title Google http://lebowskifest.com Web Page Title <script src=js/main.js /> <link href=css/app.css /> <img src=images/donny.jpg /> 1.Router creates a Route based on location 2.Route loads a Model 3.Route creates a Controller & sets the Model 4.Route renders a View 5.Controller sets state on View 6.View renders a Template http://lebowskifest.com/api/scenes MVC
  31. Router <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.route("about"); this.resource("projects", function()

    { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); </script> The rug /about /projects /projects/new /projects/:project_id
  32. Routes The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); </script>
  33. Routes The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({}); </script>
  34. Routes The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({}); App.ProjectsNewRoute = Ember.Route.extend({}); </script>
  35. Routes The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({}); App.ProjectsNewRoute = Ember.Route.extend({}); App.ProjectRoute = Ember.Route.extend({}); </script>
  36. Models The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({}); App.ProjectsNewRoute = Ember.Route.extend({}); App.ProjectRoute = Ember.Route.extend({}); </script>
  37. Models The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({}); App.ProjectRoute = Ember.Route.extend({}); </script>
  38. Models The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({}); App.ProjectRoute = Ember.Route.extend({}); </script> GET /api/projects
  39. Models The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({ model: function() { return App.Project.create(); } }); ! App.ProjectRoute = Ember.Route.extend({}); </script>
  40. Models The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({ model: function() { return App.Project.create(); } }); ! App.ProjectRoute = Ember.Route.extend({ model: function(params) { return this.store.find('project', params.project_id); } }); </script>
  41. Models The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({ model: function() { return App.Project.create(); } }); ! App.ProjectRoute = Ember.Route.extend({ model: function(params) { return this.store.find('project', params.project_id); } }); </script> GET /api/projects/65
  42. Controllers The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({ model: function() { return App.Project.create(); } }); ! App.ProjectRoute = Ember.Route.extend({ model: function(params) { return this.store.find('project', params.project_id); } }); </script>
  43. Controllers The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({...}); App.ProjectRoute = Ember.Route.extend({...}); </script>
  44. Controllers The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({...}); App.ProjectRoute = Ember.Route.extend({...}); ! App.ProjectsIndexController = Ember.ArrayController.extend({}); </script>
  45. Controllers The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectsNewRoute = Ember.Route.extend({...}); App.ProjectRoute = Ember.Route.extend({...}); ! App.ProjectsIndexController = Ember.ArrayController.extend({ sortProperties: ['name', 'createdAt']; sortAscending: true; }); </script>
  46. Controllers The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({...}); App.ProjectsNewRoute = Ember.Route.extend({...}); App.ProjectRoute = Ember.Route.extend({...}); ! App.ProjectsIndexController = Ember.ArrayController.extend({...}); App.ProjectsNewController = Ember.ObjectController.extend({ actions: { resetForm: function() { this.get('model').rollback(); } } }); </script>
  47. Views The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({...}); App.ProjectsNewRoute = Ember.Route.extend({...}); App.ProjectRoute = Ember.Route.extend({...}); ! App.ProjectsIndexController = Ember.ArrayController.extend({...}); App.ProjectsNewController = Ember.ObjectController.extend({}); App.ProjectController = Ember.ObjectController.extend({}); </script>
  48. Views The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsNewRoute = Ember.Route.extend({...}); ! App.ProjectsNewController = Ember.ObjectController.extend({...}); </script>
  49. Views The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsNewRoute = Ember.Route.extend({...}); ! App.ProjectsNewController = Ember.ObjectController.extend({...}); ! App.ProjectsNewView = Ember.View.extend({ click: function(event) { this.get('controller').send('resetForm'); } }); </script>
  50. Templates The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({...}); App.ProjectsNewRoute = Ember.Route.extend({...}); App.ProjectRoute = Ember.Route.extend({...}); ! App.ProjectsIndexController = Ember.ArrayController.extend({...}); App.ProjectsNewController = Ember.ObjectController.extend({...}); App.ProjectController = Ember.ObjectController.extend({}); ! App.ProjectsIndexView = Ember.View.extend({}); App.ProjectsNewView = Ember.View.extend({}); App.ProjectView = Ember.View.extend({}); </script>
  51. Templates The rug <script> window.App = Ember.Application.create(); ! App.Router.reopen({ this.resource("projects",

    function() { this.route("new"); }); this.resource("project", { path: "/projects/:project_id" }); }); App.ProjectsIndexRoute = Ember.Route.extend({...}); App.ProjectsNewRoute = Ember.Route.extend({...}); App.ProjectRoute = Ember.Route.extend({...}); ! App.ProjectsIndexController = Ember.ArrayController.extend({...}); App.ProjectsNewController = Ember.ObjectController.extend({...}); App.ProjectController = Ember.ObjectController.extend({}); ! App.ProjectsIndexView = Ember.View.extend({}); App.ProjectsNewView = Ember.View.extend({}); App.ProjectView = Ember.View.extend({}); </script> ! <script type="text/x-handlebars"> <div> {{outlet}} </div> </script>
  52. Templates The rug <script type="text/x-handlebars" data-template-name="projects/index"> <table> <thead> <tr> <th>Name</th>

    <th>Created</th> </tr> </thead> <tbody> {{#each}} <tr> <td>{{name}}</td> <td>{{formattedDate format='LL' value=createdAt}}</td> </tr> {{/each}} </tbody> </table> </script>
  53. Integration Testing The rug <script> ! module("Project Creation", { setup:

    function() { App.reset(); } }); ! test("creating a project displays the new project", function(){ visit("/projects/new"); fillIn("input[placeholder='Name']", "A new project"); click(".submit"); andThen(function() { ok(find("h1:contains('A new project')").length, "The project's name should display"); }); }); ! </script>
  54. Unit Testing The rug <script> var model = null; var

    controller = null; ! module "App.ProjectsNewController::validations", { setup: function() { App.reset(); this.model = Ember.Object.create(); this.controller = App.ProjectsNewController.create(content: @model); } }); ! test("name must be present", function() { this.controller.set('name', '') errors = this.controller.get('errors') deepEqual errors.name, ["can't be blank"] }); </script>
  55. Fixtures The rug <script> window.App = Ember.Application.create(); ! App.ProjectsIndexRoute =

    Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); </script>
  56. Fixtures The rug <script> window.App = Ember.Application.create(); ! App.ProjectsIndexRoute =

    Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ApplicationAdapter = DS.FixtureAdapter.extend(); </script>
  57. Fixtures The rug <script> window.App = Ember.Application.create(); ! App.ProjectsIndexRoute =

    Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ApplicationAdapter = DS.FixtureAdapter.extend(); ! App.Project.FIXTURES = [ { id: 1, name: 'The Jeffery Lebowski Project', createdAt: new Date() }, { id: 2, name: 'The Dude\'s Project', createdAt: new Date() } ]; </script>
  58. Fixtures The rug <script> window.App = Ember.Application.create(); ! App.ProjectsIndexRoute =

    Ember.Route.extend({ model: function() { return this.store.findAll('project'); } }); ! App.ProjectAdapter = DS.ActiveModelAdapter.extend(); ! App.ApplicationAdapter = DS.FixtureAdapter.extend(); </script>
  59. API Stubbing The rug <script> // config/server.js ! module.exports =

    { drawRoutes: function(app) { app.get('/api/projects', function(req, res) { res.json([ { id: 1, name: 'Blah', created_at: new Date() }, { id: 2, name: 'Blah Again', created_at: new Date() }, ]); } } } ! </script>
  60. API Building The rug require 'spec_helper' ! describe Api::ProjectsController do

    describe "GET 'index'" do context 'with no authentication' do it 'should return a 401' do get :index, format: :json response.status.should == 401 end end end end
  61. API Building The rug require 'spec_helper' ! describe Api::ProjectsController do

    describe "GET 'index'" do context 'with no authentication' do it 'should return a 401' do get :index, format: :json response.status.should == 401 end end ! context 'with valid credentials' do let(:user) { FactoryGirl.create(:user) } ! it 'should return a 200' do get :index, format: :json, 'HTTP_AUTHORIZATION' => "Token token=#{user.api_token}" response.status.should == 200 end end end end
  62. API Building The rug require 'spec_helper' ! describe Api::ProjectsController do

    describe "GET 'index'" do context 'with no authentication' do it 'should return a 401' do get :index, format: :json response.status.should == 401 end end ! context 'with valid credentials' do let(:user) { FactoryGirl.create(:user) } ! it 'should return a 200' do get :index, format: :json, 'HTTP_AUTHORIZATION' => "Token token=#{user.api_token}" response.status.should == 200 end ! it 'returns all the current goals for the current user for that week' do projects = [Project.new(name: 'My first project')] Project.stubs(:where).returns projects get :index, format: :json, 'HTTP_AUTHORIZATION' => "Token token=#{user.api_token}" json = JSON.parse(response.body)['projects'] json.length.should == 1 json[0]['name'].should == 'My first project' end end end end
  63. API Building The rug class Api::ProjectsController < Api::ApiController respond_to :json

    ! def index authenticate! @projects = Project.where(owner_id: current_user.id) respond_with @projects, each_serializer: ProjectSerializer end end
  64. Building The rug Running "common" task ! Running "coffee:compile" (coffee)

    task File generated/js/app.coffee.js created. File generated/js/spec.coffee.js created. >> Destination (generated/js/spec-helpers.coffee.js) not written because compiled files were empty. ! Running "less:compile" (less) task >> Destination not written because no source files were found. File generated/css/app.less.css created. ! Running "jshint:files" (jshint) task ! Running "handlebars:compile" (handlebars) task >> Destination not written because compiled files were empty. ! Running "jst:compile" (jst) task File "generated/template/underscore.js" created. ! Running "concat:js" (concat) task File "generated/js/app-78512a8e6fcb4e70b8e2694693651800.js" created. ! Running "concat:spec" (concat) task File "generated/js/spec.js" created. ! Running "concat:css" (concat) task File "generated/css/app-f7a1da849888d2c913a5374c7dc0cbbe.css" created. ! Running "images:dev" (images) task Copying images to 'generated/img' ! Running "webfonts:dev" (webfonts) task Copying webfonts to 'generated/webfonts' ! Running "pages:dev" (pages) task generated/index.html generated from app/pages/index.us ! Running "dist" task ! Running "uglify:js" (uglify) task File "dist/js/app-78512a8e6fcb4e70b8e2694693651800.js" created. ! Running "cssmin:compress" (cssmin) task File dist/css/app-f7a1da849888d2c913a5374c7dc0cbbe.css created. ! Running "images:dist" (images) task Copying images to 'dist/img' ! Running "webfonts:dist" (webfonts) task Copying webfonts to 'dist/webfonts' ! Running "pages:dist" (pages) task dist/index.html generated from app/pages/index.us ! Done, without errors.
  65. Building The rug $ tree dist ! dist ├── css

    │ └── app.css ├── favicon.ico ├── index.html └── js └── app.js