Use the left/right arrow keys to navigate.

Managing CSS & JS with Rack

What? Really? Why???

Organizing CSS Sucks

One Massive Stylesheet

Terrible Naming Conventions

.container, .wrapper, .stage, .content, .left-rail {
  /* Which is what? */
}
.cart-screen-1 {
  /* What does this even mean?! */
}

JS is an Afterthought in Most Apps

$(document).ready(function() {
  globalVariables = 'out of control';

  $('.user').click(function() {
    // Anonymous functions
  });

  // Binding to document ready directly is gross
});

Production isn't Developer Friendly

1. Organize Our CSS

Add Resource & Action Names to Body Tag

module ApplicationHelper
  def body_css_classes
    [
      controller.controller_name,
      controller.action_name
    ].join ' '
  end
end


<body class="<%= body_css_classes -%>">
  <!-- Now everything is scoped! -->
</body>

Narrowly Scope Selectors

body {
  /* site-wide styles */
}

body.users {
  /* styles common to User resource */
}

body.users.index {
  /* users#index specific styles */
}

Many Smaller CSS Files

/* /public/stylesheets/layouts/application.css */
body div.container {
  width: 960px;
}

/* /public/stylesheets/users.css */
body.users img.avatar {
  border: 1px solid black;
}

/* /public/stylesheets/users/index.css */
body.users.index div.user {
  margin-bottom: 1em;
}

So, What Did We Learn?

Developer Friendly

"Guessable" Styles

Fewer Surprises

2. Organize Our JS

Many Smaller JS Files

// /public/javascripts/layouts/application.js
function initializeTracking() {
  // Global functions
}

// /public/javascripts/users/index.js
function initializeInlineEditing() {
  // Page-specific functions
}

Namespace by Resource & Action

var Users = {
  index: {
    initializeInlineEditing: function() {
      // Sianara global functions!
    }
  }
}

$(document).ready() Gets a Break

<%= content_for :head do %>
  <script type="text/javascript">
    $(document).ready(function() {
      Users.index.ready();
    });
  </script>
<% end %>
var Users = {
  index: {
    ready: function() {
      initializeInlineEditing();
    },

    initializeInlineEditing: function() {
      // Remember me?
    }
  }
}

Pack it Up

What are Our Options?

<head>
  <%=
    stylesheet_link_tag([
      'layouts/application',
      'users',
      'users/index'
    ], :cache => "application")
  %>

  <%=
    javascript_include_tag([
      'layouts/application',
      'users',
      'users/index'
    ], :cache => "application")
  %>
</head>
ActionView::Helpers::AssetTagHelper.
  register_javascript_expansion :application => [
    'layouts/application', 'users', 'users/index'
]

ActionView::Helpers::AssetTagHelper.
  register_stylesheet_expansion :application => [
    'layouts/application', 'users', 'users/index'
]


<head>
  <%= stylesheet_link_tag :application %>

  <%= javascript_include_tag :application %>
</head>
rake asset:packager:build_all

Rack it Up

class AssetsApp < ActionController::Metal
  include ActionController::Rendering

  def stylesheets
    # ...
  end

  def javascripts
    # ...
  end
end
class AssetsApp < ActionController::Metal
  def stylesheets
    # 1. Grab all the files
    # 2. Basic: concatenate and minify
    # 3. Advanced: compile with Sass
    # 4. Cache
  end
end
class AssetsApp < ActionController::Metal
  def stylesheets
    path_to_css = "path/to/css/#{params[:package]}/**/*.css"

    output = Dir.glob(path_to_css).inject('') do |s, f|
      s << File.open(f, 'r').read
    end

    # minify if you want

    response.headers['Cache-Control'] =
      "public, max-age=#{1.year.seconds.to_i}"
    response.content_type =
      'text/css'

    render :text => output
  end
end
class AssetsApp < ActionController::Metal
  def javascripts
    # 1. Use YAML create named packages
    # 2. Grab the files for a package
    # 3. Basic: concatenate and minify
    # 4. Advanced: optimize
  end
end
class AssetsApp < ActionController::Metal
  def javascripts
    packages = YAML.load_file("path/to/config/js_packages.yml")

    output = packages[params[:package]].inject('') do |s, f|
      s << "// #{f}\n"
      s << File.open(f, 'r').read
      s << "\n"
    end

    # optimize if you want

    response.headers['Cache-Control'] =
      "public, max-age=#{1.year.seconds.to_i}"
    response.content_type =
      'text/css'

    render :text => output
  end
end
application:
  - layouts/application
  - users
  - users/index

vendor:
  - vendor/jquery/1.5.1
  - vendor/jquery/easing.1.3
  - vendor/jquery/facebox.1.2
Application.routes.draw do
  match({
    "/stylesheets/:package.css" => AssetsApp.action(:stylesheets),
    :as                         => 'stylesheets'
  })

  match({
    "/javascripts/:package.js" => AssetsApp.action(:javascripts),
    :as                        => 'javascripts'
  })
end
<head>
  <%=
    javascript_include_tag \
      javascripts_path(:package => 'application')
  %>

  <%=
    stylesheet_link_tag \
      stylesheets_path(:package => 'application')
  %>
</head>

Boom! Done.

Devil's Advocate

Assets are Static!

Server Does More Work!

Wait, I Thought You Avoided Code to Solve Problems!

Stork

http://github.com/avand/stork