Skip to content

Instantly share code, notes, and snippets.

@lorennorman
Created February 9, 2012 03:56
Show Gist options
  • Save lorennorman/1777157 to your computer and use it in GitHub Desktop.
Save lorennorman/1777157 to your computer and use it in GitHub Desktop.
ATLRUG Presentation: Asset Pipelines and You, 2-2012
app.get('/javascript/:buildable_js_file', function(req, res)
{
var jsFilename = req.params.buildable_js_file
, staticPath = 'public/javascript/' + jsFilename
, buildPath = 'app/client/' + jsFilename
, buildDir = buildPath.replace('.js', '')
, serveJS = function(jsString)
{
res.header('Content-Type', 'application/javascript')
res.send(jsString)
}
, sendStatic = function()
{
res.sendfile(staticPath)
}
, buildThenSendStatic = function()
{
exec('sprocketize -C app/client ' + req.params.buildable_js_file,
function(error, stdout, stderr)
{
if (error !== null) {
console.log('exec error: ' + error);
} else {
fs.writeFile(staticPath, stdout, function(err, written)
{
if(err)
{
console.log(err)
serveJS('alert("Failed to write static file: '+staticPath+'")')
} else {
sendStatic()
}
})
}
}
)
}
, ifStaticIsOlderThanBuildFilesElse = function(trueFunc, falseFunc)
{
var anyFilesInDirNewerThan = function(path, time)
{
var pathStat = fs.statSync(path)
// verify dirPath is a directory
if(pathStat.isDirectory())
{
return _(fs.readdirSync(path)).any(function(filename)
{
return anyFilesInDirNewerThan(path+'/'+filename, time)
})
} else {
return pathStat.mtime > time
}
}
timeToBeat = fs.statSync(staticPath).mtime
if(anyFilesInDirNewerThan(buildDir, timeToBeat))
{
trueFunc()
} else {
falseFunc()
}
}
path.exists(buildPath, function(buildExists)
{
path.exists(staticPath, function(staticExists)
{
if(buildExists)
{
if(!staticExists)
{
buildThenSendStatic()
} else {
ifStaticIsOlderThanBuildFilesElse(buildThenSendStatic, sendStatic)
}
} else if(staticExists) {
sendStatic()
} else {
// serve an error
serveJS('alert("No file to build or serve: '+jsFilename+'")')
}
})
})
})
def serve_coffeescript_app source_root
# load a configuration
manifest = YAML.load_file "#{source_root}/manifest.yml"
# collect all filenames we care about
filenames = manifest["files"]
# turn filenames into file contents
file_contents = filenames.map do |file|
filename = "#{source_root}/#{file}"
begin
File.read(filename)
rescue Exception => e
"console.log '#{e}'"
end
end
# concatenate it all together
concatenated_contents= file_contents.join("\n\n")
# turn it into coffeescript!
coffeescript = CoffeeScript.compile(concatenated_contents, bare: true)
end
get "/simulator.js" do
serve_coffeescript_app "simulator/src"
end

Asset Pipes and You

=====================

...a gist-driven presentation

What, Why, How?

The Pipe

[ lots of source files in various formats ] -> [ single file in target format ]

Eases Web Requests

Lets a web request ask for /assets/target_file.target_format

Eases Code Reuse

Lets the source files reference each other without knowing exactly where they are.

Common Uses:

Coffeescript -> Javascript + concatenation

Sass/Less -> CSS + concatenation

Images... less so. Eases lookup, but could (should!) do spritesheets.

Rails Asset Pipeline (Sprockets)

Opinions...

  • clean up the "junk drawer" of /public
  • treat javascript like actual code!
  • extensions get a Right Way to extend projects
  • implemented as Rack Middleware

...Are Like Assholes

  • compile BEFORE concat? why? before | after
  • can't override options to coffee/sass/etc compilers?
  • search path is always globally scoped? junk drawer is now a Level 80 Junk Drawer
  • dropped the CLI interface?

Not Railsy, and not Addressed in 3.2

  • "surely this will be sorted after people actually start USING it and find it wanting" -me
  • "we didn't touch the asset pipeline at all" - Rails 3.2
  • what to do?

Rake Pipeline

"An extension to Rake for dealing with a directory of inputs, a number of filters, and a directory of outputs"

Example

  • nice DSL for building custom pipes
  • feels like the middleware pattern, which feels about right
  • also provides middleware: put it where you want
  • runs on command line (rake under the covers for better/worse)

Roll Your Own: Ruby

Quick, dirty, Ruby!

Roll Your Own: Node

Longer, static-file-supporting, Javascript!

// output.js
(function() {
// transpiled contents of point.coffee
})();
(function() {
// transpiled contents of box.coffee
})();
(function() {
// transpiled contents of main.coffee
})();
// Nothing can see anything else by default!
Pipeline.build do
input "assets"
output "public"
# this block will take all JS inputs, wrap them in a closure,
# add some additional metadata, and concatenate them all into
# application.scripts.js.
match "*.js" do
filter ClosureWrapper
filter DataWrapper
filter Rake::Pipeline::ConcatFilter, "application.scripts.js"
end
# this block will take all HTML and CSS inputs, convert them
# into JavaScript
match "*/*.{html,css}" do
filter DataWrapper
filter Rake::Pipeline::ConcatFilter, "application.assets.js"
end
match "*.js" do
filter Rake::Pipeline::ConcatFilter, "application.js"
end
# copy any unprocessed files over to the output directory
filter Rake::Pipeline::ConcatFilter
end
## concat_filter.rb
class ConcatFilter < Rake::Pipeline::Filter
def generate_output(inputs, output)
inputs.each do |input|
output.write input.read
end
end
end
# point.coffee
class Point
constructor: (@x, @y) ->
# box.coffee
class Box
fromPoints: (pointA, pointB) ->
# main.coffee
point1 = new Point(5, 10)
point2 = new Point(15, 25)
box = new Box(point1, point2)
# shits the bed: can't find Box, or Point, or anything else because everything is wrapped
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment