├── .npmignore
├── lib
├── admin
│ ├── styles
│ │ └── main.css
│ ├── images
│ │ ├── bg.jpg
│ │ └── asset-rack.png
│ └── templates
│ │ ├── header.jade
│ │ ├── error.jade
│ │ ├── admin.jade
│ │ └── head.jade
├── modules
│ ├── static.coffee
│ ├── browserify.coffee
│ ├── angular-templates.coffee
│ ├── snockets.coffee
│ ├── dynamic.coffee
│ ├── sass.coffee
│ ├── stylus.coffee
│ ├── less.coffee
│ ├── javascript.coffee
│ └── jade.coffee
├── index.coffee
├── util.coffee
├── asset.coffee
├── rack.coffee
└── README.md
├── test
├── fixtures
│ ├── walk
│ │ ├── fol1
│ │ │ ├── f1
│ │ │ ├── f2
│ │ │ ├── f3
│ │ │ ├── fol4
│ │ │ │ └── f4
│ │ │ ├── fol5
│ │ │ │ └── f5
│ │ │ └── fol6
│ │ │ │ └── f6
│ │ ├── fol2
│ │ │ ├── f7
│ │ │ ├── f8
│ │ │ ├── f9
│ │ │ └── fol7
│ │ │ │ ├── f10
│ │ │ │ ├── f11
│ │ │ │ ├── f12
│ │ │ │ └── fol8
│ │ │ │ ├── f13
│ │ │ │ ├── f14
│ │ │ │ └── f15
│ │ └── fol3
│ │ │ ├── f16.ext
│ │ │ ├── f17.ext
│ │ │ └── f18.ext
│ ├── less
│ │ ├── another.less
│ │ ├── extra.less
│ │ ├── another.css
│ │ ├── extra
│ │ │ ├── again.less
│ │ │ └── extra.less
│ │ ├── more
│ │ │ └── more.less
│ │ ├── syntax-error.less
│ │ ├── dependency.less
│ │ ├── simple.min.css
│ │ ├── simple.css
│ │ └── simple.less
│ ├── static
│ │ ├── blank.txt
│ │ └── crazy-man.svg
│ ├── jade
│ │ ├── fun
│ │ │ └── fun.jade
│ │ ├── dependency.jade
│ │ ├── dependency.html
│ │ ├── user.html
│ │ ├── image.png
│ │ ├── user.jade
│ │ ├── test.html
│ │ └── test.jade
│ ├── snockets
│ │ ├── other.coffee
│ │ ├── app.coffee
│ │ ├── app.min.js
│ │ └── app.js
│ ├── javascript
│ │ ├── fun.js
│ │ └── gorilla.coffee
│ ├── stylus
│ │ ├── dependency.styl
│ │ ├── other
│ │ │ └── other.styl
│ │ ├── simple.min.css
│ │ ├── simple.styl
│ │ └── simple.css
│ ├── browserify
│ │ ├── app.coffee
│ │ ├── app.min.js
│ │ └── app.js
│ └── sass
│ │ ├── simple.min.css
│ │ ├── another.scss
│ │ ├── simple.css
│ │ ├── simple.sass
│ │ ├── simple.scss
│ │ ├── more
│ │ └── include.scss
│ │ └── another.css
├── test.sh
├── mocha.opts
├── test.coffee
├── static.coffee
├── collection.coffee
├── javascript.coffee
├── snockets.coffee
├── browserify.coffee
├── walk.coffee
├── stylus.coffee
├── sass.coffee
├── jade.coffee
├── less.coffee
├── asset.coffee
├── dynamic.coffee
└── rack.coffee
├── .gitignore
├── switch.js
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/admin/styles/main.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol1/f1:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol1/f2:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol1/f3:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/f7:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/f8:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/f9:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol1/fol4/f4:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol1/fol5/f5:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol1/fol6/f6:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/fol7/f10:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/fol7/f11:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/fol7/f12:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol3/f16.ext:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol3/f17.ext:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol3/f18.ext:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/fol7/fol8/f13:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/fol7/fol8/f14:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/walk/fol2/fol7/fol8/f15:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/less/another.less:
--------------------------------------------------------------------------------
1 |
2 | @import "more";
3 |
--------------------------------------------------------------------------------
/test/fixtures/static/blank.txt:
--------------------------------------------------------------------------------
1 |
2 | Some blank file.
3 |
--------------------------------------------------------------------------------
/test/fixtures/jade/fun/fun.jade:
--------------------------------------------------------------------------------
1 |
2 | p i like to have fun
3 |
--------------------------------------------------------------------------------
/test/fixtures/snockets/other.coffee:
--------------------------------------------------------------------------------
1 |
2 | window.cone = 'gravy'
3 |
--------------------------------------------------------------------------------
/test/fixtures/less/extra.less:
--------------------------------------------------------------------------------
1 |
2 | p {
3 | color: green;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/less/another.css:
--------------------------------------------------------------------------------
1 | section {
2 | background: white;
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/less/extra/again.less:
--------------------------------------------------------------------------------
1 |
2 | h3 {
3 | color: teal;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/jade/dependency.jade:
--------------------------------------------------------------------------------
1 |
2 | img(src="#{assets.url('/image.png')}")
3 |
--------------------------------------------------------------------------------
/test/fixtures/javascript/fun.js:
--------------------------------------------------------------------------------
1 |
2 | alert('oh boy, richie, i"m having funnn');
3 |
--------------------------------------------------------------------------------
/test/fixtures/less/more/more.less:
--------------------------------------------------------------------------------
1 |
2 | section {
3 | background: white;
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/jade/dependency.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cd "$(dirname "$0")/.."
4 | ./node_modules/mocha/bin/mocha
5 |
--------------------------------------------------------------------------------
/lib/admin/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techpines/asset-rack/HEAD/lib/admin/images/bg.jpg
--------------------------------------------------------------------------------
/test/fixtures/jade/user.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/less/extra/extra.less:
--------------------------------------------------------------------------------
1 |
2 | @import "again";
3 |
4 | h1 {
5 | color: green;
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .exrc
2 | _exrc
3 | node_modules
4 | *.log
5 | *.swo
6 | *.swn
7 | *.swp
8 | compiled
9 | .sass-cache
--------------------------------------------------------------------------------
/test/fixtures/jade/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techpines/asset-rack/HEAD/test/fixtures/jade/image.png
--------------------------------------------------------------------------------
/test/fixtures/jade/user.jade:
--------------------------------------------------------------------------------
1 |
2 | #user
3 | ul
4 | li First: #{users[0]}
5 | li Second: #{users[1]}
6 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --compilers coffee:coffee-script
2 | --reporter spec
3 | --timeout 5000
4 | --ignore-leaks
5 |
--------------------------------------------------------------------------------
/lib/admin/images/asset-rack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techpines/asset-rack/HEAD/lib/admin/images/asset-rack.png
--------------------------------------------------------------------------------
/test/fixtures/less/syntax-error.less:
--------------------------------------------------------------------------------
1 | // see if LessAsset can properly handle the syntax error
2 | body {
3 | {
4 | }
--------------------------------------------------------------------------------
/test/fixtures/snockets/app.coffee:
--------------------------------------------------------------------------------
1 |
2 | #= require other
3 |
4 | for index in [0..12]
5 | window[index] = index
6 |
--------------------------------------------------------------------------------
/test/fixtures/less/dependency.less:
--------------------------------------------------------------------------------
1 |
2 | @import "simple";
3 |
4 | div {
5 | background: url('/background.png');
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixtures/stylus/dependency.styl:
--------------------------------------------------------------------------------
1 |
2 | @import "simple"
3 |
4 | div
5 | background: url('/background.png')
6 |
7 |
--------------------------------------------------------------------------------
/lib/admin/templates/header.jade:
--------------------------------------------------------------------------------
1 |
2 | #header
3 | a(href="/asset-rack")
4 | img(src="/asset-rack/images/asset-rack.png")
5 |
--------------------------------------------------------------------------------
/test/fixtures/browserify/app.coffee:
--------------------------------------------------------------------------------
1 |
2 | crypto = require 'crypto'
3 |
4 | for index in [0..12]
5 | window[index] = index
6 |
--------------------------------------------------------------------------------
/test/fixtures/jade/test.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/snockets/app.min.js:
--------------------------------------------------------------------------------
1 | (function(){window.cone="gravy"}).call(this),function(){var a,b;for(a=b=0;b<=12;a=++b)window[a]=a}.call(this)
--------------------------------------------------------------------------------
/test/fixtures/sass/simple.min.css:
--------------------------------------------------------------------------------
1 | .content-navigation{border-color:#3bbfce;color:#2ca2af}.border{padding:8px;margin:8px;border-color:#3bbfce}
2 |
--------------------------------------------------------------------------------
/test/fixtures/sass/another.scss:
--------------------------------------------------------------------------------
1 | @import "include";
2 |
3 | body {
4 | background-color: $purple;
5 | background-image: url('/background.png');
6 | }
--------------------------------------------------------------------------------
/test/fixtures/jade/test.jade:
--------------------------------------------------------------------------------
1 |
2 | .container
3 | table
4 | tr
5 | td I
6 | td like
7 | tr
8 | td Tables
9 | td !
10 |
--------------------------------------------------------------------------------
/test/fixtures/javascript/gorilla.coffee:
--------------------------------------------------------------------------------
1 |
2 | alert 'i am a gorilla, and i am in love with a dolphin'
3 | alert 'the dolphin does not share these feelings, why...'
4 |
--------------------------------------------------------------------------------
/test/fixtures/less/simple.min.css:
--------------------------------------------------------------------------------
1 | p{color:green}
2 | h3{color:teal}
3 | h1{color:green}
4 | body{background:blue}body p{text-align:center}body p a{color:'#999'}
5 |
--------------------------------------------------------------------------------
/test/fixtures/sass/simple.css:
--------------------------------------------------------------------------------
1 | .content-navigation {
2 | border-color: #3bbfce;
3 | color: #2ca2af; }
4 |
5 | .border {
6 | padding: 8px;
7 | margin: 8px;
8 | border-color: #3bbfce; }
9 |
--------------------------------------------------------------------------------
/switch.js:
--------------------------------------------------------------------------------
1 |
2 | try {
3 | module.exports = require('./compiled');
4 | } catch(error) {
5 | //require('./node_modules/coffee-script');
6 | require('coffee-script');
7 | module.exports = require('./lib');
8 | }
9 |
--------------------------------------------------------------------------------
/lib/admin/templates/error.jade:
--------------------------------------------------------------------------------
1 | !!! 5
2 | html
3 | head
4 | include head
5 | body
6 | include header
7 | #contents
8 | h2 #{stack[0]}
9 | ul.stack
10 | for line in stack
11 | li #{line}
12 |
--------------------------------------------------------------------------------
/test/fixtures/sass/simple.sass:
--------------------------------------------------------------------------------
1 | $blue: #3bbfce
2 | $margin: 16px
3 |
4 | .content-navigation
5 | border-color: $blue
6 | color: darken($blue, 9%)
7 |
8 | .border
9 | padding: $margin / 2
10 | margin: $margin / 2
11 | border-color: $blue
--------------------------------------------------------------------------------
/test/fixtures/less/simple.css:
--------------------------------------------------------------------------------
1 | p {
2 | color: green;
3 | }
4 | h3 {
5 | color: teal;
6 | }
7 | h1 {
8 | color: green;
9 | }
10 | body {
11 | background: blue;
12 | }
13 | body p {
14 | text-align: center;
15 | }
16 | body p a {
17 | color: '#999';
18 | }
19 |
--------------------------------------------------------------------------------
/test/fixtures/sass/simple.scss:
--------------------------------------------------------------------------------
1 | $blue: #3bbfce;
2 | $margin: 16px;
3 |
4 | .content-navigation {
5 | border-color: $blue;
6 | color: darken($blue, 9%);
7 | }
8 |
9 | .border {
10 | padding: $margin / 2;
11 | margin: $margin / 2;
12 | border-color: $blue;
13 | }
--------------------------------------------------------------------------------
/test/fixtures/snockets/app.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | window.cone = 'gravy';
3 |
4 | }).call(this);
5 |
6 | (function() {
7 | var index, _i;
8 |
9 | for (index = _i = 0; _i <= 12; index = ++_i) {
10 | window[index] = index;
11 | }
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/test/fixtures/stylus/other/other.styl:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | color: rgba(255, 200, 100, 0.5);
4 | color: rgba(red: 255, green: 200, blue: 100, alpha: 0.5);
5 | color: rgba(alpha: 0.5, blue: 100, red: 255, 200);
6 | color: rgba(alpha: 0.5, blue: 100, 255, 200);
7 | }
8 |
--------------------------------------------------------------------------------
/test/fixtures/stylus/simple.min.css:
--------------------------------------------------------------------------------
1 | body{color:rgba(255,200,100,0.5);color:rgba(255,200,100,0.5);color:rgba(255,200,100,0.5);color:rgba(255,200,100,0.5)}
2 | body{font:12px Helvetica,Arial,sans-serif}
3 | a.button{-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}
4 |
--------------------------------------------------------------------------------
/test/fixtures/less/simple.less:
--------------------------------------------------------------------------------
1 |
2 | @import "extra";
3 | @import "extra/extra";
4 |
5 | body {
6 | background: blue;
7 | p {
8 | text-align: center;
9 | @color: '#999';
10 | a {
11 | color: @color;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/fixtures/sass/more/include.scss:
--------------------------------------------------------------------------------
1 | $purple: #33aa33;
2 | $margin: 16px;
3 |
4 | .content-navigation {
5 | border-color: $purple;
6 | color: darken($purple, 9%);
7 | }
8 |
9 | .border {
10 | padding: $margin / 2;
11 | margin: $margin / 2;
12 | border-color: $purple;
13 | }
--------------------------------------------------------------------------------
/test/fixtures/sass/another.css:
--------------------------------------------------------------------------------
1 | .content-navigation {
2 | border-color: #33aa33;
3 | color: #288728; }
4 |
5 | .border {
6 | padding: 8px;
7 | margin: 8px;
8 | border-color: #33aa33; }
9 |
10 | body {
11 | background-color: #33aa33;
12 | background-image: url("/background.png"); }
13 |
--------------------------------------------------------------------------------
/test/fixtures/stylus/simple.styl:
--------------------------------------------------------------------------------
1 |
2 | @import "other/other"
3 |
4 | border-radius()
5 | -webkit-border-radius arguments
6 | -moz-border-radius arguments
7 | border-radius arguments
8 |
9 | body
10 | font 12px Helvetica, Arial, sans-serif
11 |
12 | a.button
13 | border-radius 5px
14 |
--------------------------------------------------------------------------------
/test/fixtures/stylus/simple.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: rgba(255,200,100,0.5);
3 | color: rgba(255,200,100,0.5);
4 | color: rgba(255,200,100,0.5);
5 | color: rgba(255,200,100,0.5);
6 | }
7 | body {
8 | font: 12px Helvetica, Arial, sans-serif;
9 | }
10 | a.button {
11 | -webkit-border-radius: 5px;
12 | -moz-border-radius: 5px;
13 | border-radius: 5px;
14 | }
15 |
--------------------------------------------------------------------------------
/test/test.coffee:
--------------------------------------------------------------------------------
1 |
2 | require './asset'
3 | require './rack'
4 | require './javascript'
5 | require './browserify'
6 | require './jade'
7 | require './less'
8 | require './snockets'
9 | require './static'
10 | require './dynamic'
11 | require './stylus'
12 | require './collection'
13 | require './walk'
14 |
15 | # Sass is commented out because it requires ruby
16 | # require './sass'
17 |
--------------------------------------------------------------------------------
/lib/admin/templates/admin.jade:
--------------------------------------------------------------------------------
1 | !!! 5
2 | html
3 | head
4 | include head
5 | body
6 | include header
7 | table.table
8 | tr
9 | th URL
10 | th MD5
11 | th Hash
12 | th Gzip
13 | th Mimetype
14 | th Headers
15 | th Max Age
16 | for asset in assets
17 | tr
18 | td
19 | a(href="#{asset.specificUrl}") #{asset.url}
20 | td #{asset.md5}
21 | td #{asset.hash}
22 | td #{asset.gzip}
23 | td #{asset.mimetype}
24 | td #{JSON.stringify(asset.headers)}
25 | td #{asset.maxAge}
26 |
--------------------------------------------------------------------------------
/lib/modules/static.coffee:
--------------------------------------------------------------------------------
1 |
2 | fs = require 'fs'
3 | pathutil = require 'path'
4 | async = require 'async'
5 | mime = require 'mime'
6 | {Asset} = require '../.'
7 | {DynamicAssets} = require './dynamic'
8 |
9 | class StaticAsset extends Asset
10 | create: (options) ->
11 | @filename = pathutil.resolve options.filename
12 | @mimetype ?= mime.types[pathutil.extname(@filename).slice 1] || 'text/plain'
13 |
14 | fs.readFile @filename, (error, data) =>
15 | return @emit 'error', error if error?
16 | @emit 'created', contents: data
17 |
18 | class exports.StaticAssets extends DynamicAssets
19 | constructor: (options) ->
20 | options?.type = StaticAsset
21 | super options
22 |
--------------------------------------------------------------------------------
/lib/admin/templates/head.jade:
--------------------------------------------------------------------------------
1 |
2 | title AssetRack
3 | link(href="/asset-rack/styles/bootstrap.min.css", rel="stylesheet")
4 | link(href="/asset-rack/styles/main.css", rel="stylesheet")
5 | link(href='http://fonts.googleapis.com/css?family=Droid+Sans+Mono', rel='stylesheet', type='text/css')
6 | style
7 | body {
8 | padding-left: 50px;
9 | font-family: 'Droid Sans Mono', sans-serif;
10 | color: #49240d;
11 | background: url('/asset-rack/images/bg.jpg');
12 | }
13 | #header {
14 | padding-top: 20px;
15 | padding-bottom: 20px;
16 | }
17 | #header img {
18 | width: 300px;
19 | }
20 | ul.stack {
21 | list-style: none;
22 | font-size: 16px;
23 | line-height: 22px;
24 | }
25 | a, a:hover {
26 | color: #8e2927;
27 | }
28 |
--------------------------------------------------------------------------------
/test/static.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 |
8 | describe 'a static asset builder', ->
9 | app = null
10 |
11 | it 'should work', (done) ->
12 | staticPath = "#{__dirname}/fixtures/static"
13 | compiled = fs.readFileSync "#{staticPath}/blank.txt", 'utf8'
14 | app = express().http()
15 | app.use new rack.StaticAssets
16 | dirname: staticPath
17 | urlPrefix: '/static'
18 | app.listen 7076, ->
19 | easyrequest 'http://localhost:7076/static/blank.txt', (error, response, body) ->
20 | response.headers['content-type'].should.equal 'text/plain'
21 | body.should.equal compiled
22 | done()
23 |
24 | afterEach (done) -> process.nextTick ->
25 | app.server.close done
26 |
--------------------------------------------------------------------------------
/test/collection.coffee:
--------------------------------------------------------------------------------
1 | should = require('chai').should()
2 | rack = require '../.'
3 |
4 | class DelayedAsset extends rack.Asset
5 | create: (options) ->
6 | @delay = options.delay
7 | build = =>
8 | @emit 'created', contents: "delay#{@delay}"
9 | setTimeout build, @delay
10 |
11 | class CollectionAsset extends rack.Asset
12 | create: (options) ->
13 | for i in [1..options.size]
14 | @addAsset new DelayedAsset
15 | delay: i * 100
16 | url: '/delayed' + i
17 | @emit 'created'
18 |
19 | describe 'an asset collection', ->
20 |
21 | it 'should wait for all sub-assets to build', (done) ->
22 | asset = new CollectionAsset size: 3
23 | asset.on 'complete', ->
24 | subassetsBuilt = true
25 | for subasset in asset.assets
26 | subassetsBuilt = false unless subasset.completed
27 | subassetsBuilt.should.be.ok
28 | done()
29 |
--------------------------------------------------------------------------------
/lib/index.coffee:
--------------------------------------------------------------------------------
1 |
2 | # Index.coffee - Entry point puts all the code together
3 |
4 | exports.Asset = require('./asset').Asset
5 | exports.Rack = require('./rack').Rack
6 | exports.fromConfigFile = require('./rack').fromConfigFile
7 | exports.AssetRack = require('./rack').Rack # backwards compatibility with 1.x
8 | exports.DynamicAssets = require('./modules/dynamic').DynamicAssets
9 | exports.LessAsset = require('./modules/less').LessAsset
10 | exports.SassAsset = require('./modules/sass').SassAsset
11 | exports.StylusAsset = require('./modules/stylus').StylusAsset
12 | exports.BrowserifyAsset = require('./modules/browserify').BrowserifyAsset
13 | exports.JadeAsset = require('./modules/jade').JadeAsset
14 | exports.JavascriptAsset = require('./modules/javascript').JavascriptAsset
15 | exports.StaticAssets = require('./modules/static').StaticAssets
16 | exports.SnocketsAsset = require('./modules/snockets').SnocketsAsset
17 | exports.AngularTemplatesAsset = require('./modules/angular-templates').AngularTemplatesAsset
18 |
19 | util = require './util'
20 | exports.util =
21 | walk: util.walk
22 |
--------------------------------------------------------------------------------
/test/javascript.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | pathutil = require 'path'
4 | rack = require '../.'
5 | express = require 'express.io'
6 | easyrequest = require 'request'
7 | fs = require 'fs'
8 |
9 | describe 'a javascript asset', ->
10 | app = null
11 |
12 | it 'should work', (done) ->
13 | app = express().http()
14 | app.use new rack.JavascriptAsset {
15 | url: '/app.js'
16 | dirname: pathutil.join __dirname, 'fixtures/javascript'
17 | code: [
18 | 'fun.js'
19 | 'gorilla.coffee'
20 | ]
21 | }
22 | app.listen 7076, ->
23 | easyrequest 'http://localhost:7076/fun.js', (error, response, body) ->
24 | response.headers['content-type'].should.equal 'text/javascript'
25 | easyrequest 'http://localhost:7076/gorilla.js', (error, response, body) ->
26 | response.headers['content-type'].should.equal 'text/javascript'
27 | done()
28 |
29 | afterEach (done) -> process.nextTick ->
30 | app.server.close done
31 |
32 |
--------------------------------------------------------------------------------
/lib/modules/browserify.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | pathutil = require 'path'
3 | browserify = require 'browserify'
4 | uglify = require('uglify-js')
5 | Asset = require('../index').Asset
6 |
7 | class exports.BrowserifyAsset extends Asset
8 | mimetype: 'text/javascript'
9 |
10 | create: (options) ->
11 | @filename = options.filename
12 | @toWatch = pathutil.dirname pathutil.resolve @filename
13 | @require = options.require
14 | @debug = options.debug or false
15 | @compress = options.compress
16 | @compress ?= false
17 | @extensionHandlers = options.extensionHandlers or []
18 | agent = browserify watch: false, debug: @debug
19 | for handler in @extensionHandlers
20 | agent.register(handler.ext, handler.handler)
21 | agent.addEntry @filename
22 | agent.require @require if @require
23 | if @compress is true
24 | uncompressed = agent.bundle()
25 | @contents = uglify.minify(uncompressed, {fromString: true}).code
26 | @emit 'created'
27 | else
28 | @emit 'created', contents: agent.bundle()
29 |
30 |
--------------------------------------------------------------------------------
/lib/modules/angular-templates.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | pathutil = require 'path'
3 | uglify = require 'uglify-js'
4 | Asset = require('../index').Asset
5 |
6 | class exports.AngularTemplatesAsset extends Asset
7 | mimetype: 'text/javascript'
8 |
9 | create: (options) ->
10 | options.dirname ?= options.directory # for backwards compatiblity
11 | @dirname = pathutil.resolve options.dirname
12 | @toWatch = @dirname
13 | @compress = options.compress or false
14 | files = fs.readdirSync @dirname
15 | templates = []
16 |
17 | for file in files when file.match(/\.html$/)
18 | template = fs.readFileSync(pathutil.join(@dirname, file), 'utf8').replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/'/g, '\\\'')
19 | templates.push "$templateCache.put('#{file}', '#{template}')"
20 |
21 | javascript = "var angularTemplates = function($templateCache) {\n#{templates.join('\n')}}"
22 | if options.compress is true
23 | @contents = uglify.minify(javascript, { fromString: true }).code
24 | else
25 | @contents = javascript
26 | @emit 'created'
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "asset-rack",
3 | "version": "2.2.2",
4 | "description": "Static Web Framework for Nodejs",
5 | "author": "Brad Carleton ",
6 | "repository": "https://github.com/techpines/asset-rack",
7 | "dependencies": {
8 | "browserify": "1.17.3",
9 | "snockets": "~1.3.8",
10 | "uglify-js": "~2.4.0",
11 | "async": "~0.2.9",
12 | "pkgcloud": "~0.8.12",
13 | "less": "~1.4.2",
14 | "jade": "~0.35.0",
15 | "mime": "1.2.11",
16 | "nib": "~1.0.1",
17 | "stylus": "~0.38.0",
18 | "underscore": "~1.5.2",
19 | "coffee-script": "~1.6.3",
20 | "markdown": "~0.5.0",
21 | "node-sassy": "~0.0.1"
22 | },
23 | "devDependencies": {
24 | "express.io": "1.1.8",
25 | "request": "2.12.0",
26 | "mocha": "1.8.1",
27 | "chai": "1.4.2"
28 | },
29 | "scripts": {
30 | "test": "./node_modules/mocha/bin/mocha test/test.coffee",
31 | "compile": "./node_modules/coffee-script/bin/coffee -o compiled/ -c lib/",
32 | "prepublish": "echo $(pwd) > /tmp/.pwd; ./node_modules/coffee-script/bin/coffee -o compiled/ -c lib/;",
33 | "postpublish": "rm -rf $(cat /tmp/.pwd)/compiled"
34 | },
35 | "main": "switch.js",
36 | "engines": {
37 | "node": ">= 0.5.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/modules/snockets.coffee:
--------------------------------------------------------------------------------
1 | pathutil = require('path')
2 | Snockets = require 'snockets'
3 | Asset = require('../index').Asset
4 |
5 | class exports.SnocketsAsset extends Asset
6 | mimetype: 'text/javascript'
7 |
8 | create: (options) ->
9 | try
10 | @filename = pathutil.resolve options.filename
11 | @toWatch = pathutil.dirname @filename
12 | @compress = options.compress or false
13 | @debug = options.debug or false
14 | snockets = new Snockets()
15 | if @debug
16 | files = snockets.getCompiledChain @filename, { async: false }
17 | scripts = []
18 | for file in files
19 | script = file.js
20 | .replace(/\\/g, '\\\\')
21 | .replace(/\n/g, '\\n')
22 | .replace(/\r/g, '')
23 | .replace(/'/g, '\\\'')
24 | filename = pathutil.relative(pathutil.dirname(@filename), file.filename)
25 | .replace(/\\/g, '\/')
26 | scripts.push "// #{filename}\neval('#{script}\\n//@ sourceURL=#{filename}')\n"
27 | @contents = scripts.join('\n')
28 | else
29 | @contents = snockets.getConcatenation @filename, { async: false, minify: @compress }
30 | catch e
31 | @emit('error', e)
32 | @emit 'created'
33 |
--------------------------------------------------------------------------------
/lib/modules/dynamic.coffee:
--------------------------------------------------------------------------------
1 |
2 | fs = require 'fs'
3 | pathutil = require 'path'
4 | async = require 'async'
5 | mime = require 'mime'
6 | {Asset} = require '../.'
7 | {walk} = require '../util'
8 |
9 | class exports.DynamicAssets extends Asset
10 | create: (options) ->
11 | @dirname = pathutil.resolve options.dirname
12 | @toWatch = @dirname
13 | {@type, @urlPrefix, @options, @filter, @rewriteExt} = options
14 | @urlPrefix ?= '/'
15 | @urlPrefix += '/' unless @urlPrefix.slice(-1) is '/'
16 | @rewriteExt ?= mime.extensions[@type::mimetype] if @type::mimetype?
17 | @rewriteExt = '.' + @rewriteExt if @rewriteExt? and @rewriteExt[0] isnt '.'
18 | @options ?= {}
19 | @options.hash = @hash
20 | @options.maxAge = @maxAge
21 |
22 | @assets = []
23 | walk @dirname,
24 | ignoreFolders: true
25 | filter: @filter
26 | , (file, done) =>
27 | url = pathutil.dirname(file.relpath)
28 | url = url.split pathutil.sep
29 | url = [] if url[0] is '.'
30 | if @rewriteExt?
31 | url.push file.namenoext + @rewriteExt
32 | else
33 | url.push file.name
34 |
35 | opts =
36 | url: @urlPrefix + url.join '/'
37 | filename: file.path
38 | opts[k] = v for own k, v of @options
39 |
40 | @addAsset new @type opts
41 | done()
42 | , => @emit 'created'
43 |
--------------------------------------------------------------------------------
/test/snockets.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 |
8 | describe 'a snockets asset', ->
9 | app = null
10 | fixturesDir = "#{__dirname}/fixtures/snockets"
11 |
12 | it 'should work', (done) ->
13 | compiled = fs.readFileSync "#{fixturesDir}/app.js", 'utf8'
14 | app = express().http()
15 | app.use new rack.SnocketsAsset
16 | filename: "#{fixturesDir}/app.coffee"
17 | url: '/app.js'
18 | app.listen 7076, ->
19 | easyrequest 'http://localhost:7076/app.js', (error, response, body) ->
20 | response.headers['content-type'].should.equal 'text/javascript'
21 | body.should.equal compiled
22 | done()
23 |
24 | it 'should work compressed', (done) ->
25 | compiled = fs.readFileSync "#{fixturesDir}/app.min.js", 'utf8'
26 | app = express().http()
27 | app.use new rack.SnocketsAsset
28 | filename: "#{fixturesDir}/app.coffee"
29 | url: '/app.js'
30 | compress: true
31 | app.listen 7076, ->
32 | easyrequest 'http://localhost:7076/app.js', (error, response, body) ->
33 | response.headers['content-type'].should.equal 'text/javascript'
34 | body.should.equal compiled
35 | done()
36 |
37 | afterEach (done) -> process.nextTick ->
38 | app.server.close done
39 |
--------------------------------------------------------------------------------
/lib/modules/sass.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | pathutil = require 'path'
3 |
4 | sassy = require "node-sassy"
5 |
6 | {Asset} = require '../.'
7 |
8 | urlRegex = /url\s*\(\s*(['"])((?:(?!\1).)+)\1\s*\)/
9 | urlRegexGlobal = /url\s*\(\s*(['"])((?:(?!\1).)+)\1\s*\)/g
10 |
11 | class exports.SassAsset extends Asset
12 | mimetype: 'text/css'
13 |
14 | create: (options) ->
15 | @filename = pathutil.resolve options.filename
16 | @toWatch = pathutil.dirname @filename
17 | @paths = options.paths
18 | @paths ?= []
19 | @paths.push pathutil.dirname options.filename
20 |
21 | @compress = options.compress
22 | @compress ?= false
23 |
24 | sassOpts = {}
25 | if options.paths
26 | sassOpts.includeFrom = options.paths
27 |
28 | if @compress
29 | sassOpts["--style"] = "compressed"
30 |
31 | # Render the sass to css
32 | sassy.compile @filename, sassOpts, (err, css) =>
33 | return @emit 'error', err if err?
34 |
35 | if @rack?
36 | results = css.match urlRegexGlobal
37 | if results
38 | for result in results
39 | match = urlRegex.exec result
40 | quote = match[1]
41 | url = match[2]
42 | specificUrl = @rack.url url
43 | if specificUrl?
44 | css = css.replace result, "url(#{quote}#{specificUrl}#{quote})"
45 |
46 | @emit 'created',
47 | contents: css
48 |
--------------------------------------------------------------------------------
/test/browserify.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 |
8 | describe 'a browserify asset', ->
9 | app = null
10 | fixturesDir = "#{__dirname}/fixtures/browserify"
11 |
12 | it 'should work', (done) ->
13 | compiled = fs.readFileSync "#{fixturesDir}/app.js", 'utf8'
14 | app = express().http()
15 | app.use new rack.BrowserifyAsset {
16 | filename: "#{fixturesDir}/app.coffee"
17 | url: '/app.js'
18 | }
19 | app.listen 7076, ->
20 | easyrequest 'http://localhost:7076/app.js', (error, response, body) ->
21 | response.headers['content-type'].should.equal 'text/javascript'
22 | done()
23 |
24 | it 'should work compressed', (done) ->
25 | compiled = fs.readFileSync "#{fixturesDir}/app.min.js", 'utf8'
26 | app = express().http()
27 | app.use asset = new rack.BrowserifyAsset
28 | filename: "#{fixturesDir}/app.coffee"
29 | url: '/app.js'
30 | compress: true
31 | app.listen 7076, ->
32 | easyrequest 'http://localhost:7076/app.js', (error, response, body) ->
33 | response.headers['content-type'].should.equal 'text/javascript'
34 | done()
35 |
36 | #it 'should work with extension handlers', (done) ->
37 | # done()
38 |
39 | #it 'should work with debug option', (done) ->
40 | # done()
41 |
42 |
43 | afterEach (done) -> process.nextTick ->
44 | app.server.close done
45 |
--------------------------------------------------------------------------------
/lib/modules/stylus.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | pathutil = require 'path'
3 | nib = require 'nib'
4 | stylus = require 'stylus'
5 | Asset = require('../.').Asset
6 | urlRegex = /url\s*\(\s*(['"])((?:(?!\1).)+)\1\s*\)/
7 | urlRegexGlobal = /url\s*\(\s*(['"])((?:(?!\1).)+)\1\s*\)/g
8 |
9 | class exports.StylusAsset extends Asset
10 | mimetype: 'text/css'
11 |
12 | create: (options) ->
13 | @filename = pathutil.resolve options.filename
14 | @toWatch = pathutil.dirname @filename
15 | @compress = options.compress
16 | @compress ?= process.env.NODE_ENV == 'production'
17 | @config = options.config
18 | @config ?= ->
19 | @use nib()
20 |
21 | fs.readFile @filename, 'utf8', (error, data) =>
22 | return @emit 'error', error if error?
23 | styl = stylus(data)
24 | .set('compress', @compress)
25 | .set('include css', true)
26 |
27 | @config.call styl, styl
28 |
29 | styl
30 | .set('filename', @filename)
31 | .render (error, css) =>
32 | return @emit 'error', error if error?
33 | if @rack?
34 | results = css.match urlRegexGlobal
35 | if results
36 | for result in results
37 | match = urlRegex.exec result
38 | quote = match[1]
39 | url = match[2]
40 | specificUrl = @rack.url url
41 | if specificUrl?
42 | css = css.replace result, "url(#{quote}#{specificUrl}#{quote})"
43 | @emit 'created', contents: css
44 |
--------------------------------------------------------------------------------
/test/walk.coffee:
--------------------------------------------------------------------------------
1 | should = require('chai').should()
2 | {join} = require 'path'
3 | {walk} = require('../.').util
4 |
5 | describe 'util.walk', ->
6 | app = null
7 | fixtures = join __dirname, 'fixtures/walk'
8 | numberOfFilesAndFolders = 26
9 | numberOfFiles = 18
10 | numberOfFilesWithExt = 3
11 |
12 | it 'should work', (done) ->
13 | count = 0
14 | walk fixtures, {}, (file, done) ->
15 | count++
16 | done()
17 | , ->
18 | count.should.equal numberOfFilesAndFolders
19 | done()
20 |
21 | it 'should work with ignoreFolders option', (done) ->
22 | count = 0
23 | walk fixtures, ignoreFolders: true, (file, done) ->
24 | count++
25 | done()
26 | , ->
27 | count.should.equal numberOfFiles
28 | done()
29 |
30 | it 'should work with filter', (done) ->
31 | count = 0
32 | walk fixtures, filter: 'ext', (file, done) ->
33 | count++
34 | done()
35 | , ->
36 | count.should.equal numberOfFilesWithExt
37 | done()
38 |
39 | it 'should work without passing options', (done) ->
40 | count = 0
41 | walk fixtures, (file, done) ->
42 | count++
43 | done()
44 | , ->
45 | count.should.equal numberOfFilesAndFolders
46 | done()
47 |
48 | it 'should work without passing a callback', (doneTest) ->
49 | count = 0
50 | walk fixtures, {}, (file, done) ->
51 | done()
52 | doneTest() if ++count is numberOfFilesAndFolders
53 |
54 | it 'should work without passing options or a callback', (doneTest) ->
55 | count = 0
56 | walk fixtures, (file, done) ->
57 | done()
58 | doneTest() if ++count is numberOfFilesAndFolders
59 |
--------------------------------------------------------------------------------
/test/stylus.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 |
8 | describe 'a stylus asset', ->
9 | app = null
10 | fixturesDir = "#{__dirname}/fixtures/stylus"
11 |
12 | it 'should work', (done) ->
13 | compiled = fs.readFileSync "#{fixturesDir}/simple.css", 'utf8'
14 | app = express().http()
15 | app.use new rack.StylusAsset
16 | filename: "#{fixturesDir}/simple.styl"
17 | url: '/style.css'
18 | app.listen 7076, ->
19 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
20 | response.headers['content-type'].should.equal 'text/css'
21 | body.should.equal compiled
22 | done()
23 |
24 | it 'should work compressed', (done) ->
25 | compiled = fs.readFileSync "#{fixturesDir}/simple.min.css", 'utf8'
26 | app = express().http()
27 | app.use new rack.StylusAsset
28 | filename: "#{fixturesDir}/simple.styl"
29 | url: '/style.css'
30 | compress: true
31 | app.listen 7076, ->
32 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
33 | response.headers['content-type'].should.equal 'text/css'
34 | body.should.equal compiled
35 | done()
36 |
37 | it 'should work with a rack', (done) ->
38 | app = express().http()
39 | app.use new rack.Rack [
40 | new rack.Asset
41 | url: '/background.png'
42 | contents: 'not a real png'
43 | new rack.StylusAsset
44 | filename: "#{__dirname}/fixtures/stylus/simple.styl"
45 | url: '/simple.css'
46 | new rack.StylusAsset
47 | filename: "#{__dirname}/fixtures/stylus/dependency.styl"
48 | url: '/dependency.css'
49 | compress: true
50 | ]
51 | app.listen 7076, ->
52 | easyrequest 'http://localhost:7076/dependency.css', (error, response, body) ->
53 | response.headers['content-type'].should.equal 'text/css'
54 | # TODO: Test more thoroughly.
55 | done()
56 |
57 | afterEach (done) -> process.nextTick ->
58 | app.server.close done
59 |
--------------------------------------------------------------------------------
/lib/modules/less.coffee:
--------------------------------------------------------------------------------
1 | less = require 'less'
2 | fs = require 'fs'
3 | pathutil = require 'path'
4 | Asset = require('../.').Asset
5 | urlRegex = /url\s*\(\s*(['"])((?:(?!\1).)+)\1\s*\)/
6 | urlRegexGlobal = /url\s*\(\s*(['"])((?:(?!\1).)+)\1\s*\)/g
7 |
8 | class exports.LessAsset extends Asset
9 | mimetype: 'text/css'
10 | create: (options) ->
11 | if options.filename
12 | @filename = pathutil.resolve options.filename
13 | fileContents = fs.readFileSync @filename, 'utf8'
14 | else fileContents ?= options.contents
15 | @paths = options.paths
16 | @paths ?= []
17 | @paths.push pathutil.dirname options.filename
18 | @toWatch = pathutil.dirname @filename
19 |
20 | @compress = options.compress
21 | @compress ?= false
22 | try
23 | parser = new less.Parser
24 | filename: @filename
25 | paths: @paths
26 | parser.parse fileContents, (error, tree) =>
27 | return @emit 'error', ensureLessError(error) if error?
28 | raw = tree.toCSS compress: @compress
29 | if @rack?
30 | results = raw.match urlRegexGlobal
31 | if results
32 | for result in results
33 | match = urlRegex.exec result
34 | quote = match[1]
35 | url = match[2]
36 | specificUrl = @rack.url url
37 | if specificUrl?
38 | raw = raw.replace result, "url(#{quote}#{specificUrl}#{quote})"
39 | @emit 'created', contents: raw
40 | catch error
41 | # sometimes less throws an error instead of returning the error
42 | # via the callback.
43 | error = ensureLessError error
44 | @emit 'error', error
45 |
46 |
47 | # less will throw an object that isn't actually an error
48 | ensureLessError = (error) ->
49 | if !(error instanceof Error)
50 | error.filename = "[provided asset content]" if not error.filename
51 | msg = """Less error: #{error.message}
52 | \tfilename: #{error.filename}
53 | \tline #{error.line} column #{error.column}"""
54 | line = error.line
55 | msg += "\n\t..."
56 | if error.extract?
57 | for extract in error.extract
58 | if extract
59 | msg += "\n\t " + (line++) + ": " + extract
60 | msg += "\n\t..."
61 | error = new Error msg
62 | return error
63 |
--------------------------------------------------------------------------------
/lib/util.coffee:
--------------------------------------------------------------------------------
1 |
2 | # Util.coffee - A few utility methods
3 |
4 | # Pull in dependencies
5 | EventEmitter = require('events').EventEmitter
6 | Buffer = require('buffer').Buffer
7 | _ = require 'underscore'
8 | fs = require 'fs'
9 | pathutil = require 'path'
10 | async = require 'async'
11 |
12 | # Fake stream for buffer that pretends like it's a stream
13 | class exports.BufferStream extends EventEmitter
14 | constructor: (buffer) ->
15 | @data = new Buffer buffer
16 | super()
17 |
18 | pipe: (destination) ->
19 | destination.write @data
20 | destination.end()
21 | @emit 'close'
22 | @emit 'end'
23 | pause: ->
24 | resume: ->
25 | destroy: ->
26 | readable: true
27 |
28 | # Ability to extend a base class
29 | exports.extend = (object) ->
30 | class Asset extends this
31 | for key, value of object
32 | Asset::[key] = value
33 | Asset
34 |
35 | # Generalized walk function
36 | exports.walk = (root, options, iterator, cb) ->
37 | if _.isFunction options
38 | cb = iterator
39 | iterator = options
40 | options = {}
41 | cb ?= ->
42 | ignoreFolders = options.ignoreFolders || false
43 | filter = options.filter || -> true
44 |
45 | if _.isString filter
46 | # if filter is a string, folders shouldn't be passed to the iterator
47 | # but should be recursed
48 | ignoreFolders = true
49 | filter = ((ext) ->
50 | ext = if ext[0] is '.' then ext else '.' + ext
51 | (file) -> file.stats.isDirectory() or file.ext == ext
52 | ) filter
53 |
54 | readdir = (dir, cb) ->
55 | fs.readdir dir, (err, files) ->
56 | return cb err if err?
57 | iter = (file, done) ->
58 | path = pathutil.join dir, file
59 | fs.stat path, (err, stats) ->
60 | return done err if err?
61 | fobj =
62 | name: pathutil.basename file
63 | namenoext: pathutil.basename file, pathutil.extname file
64 | relpath: pathutil.relative root, path
65 | path: path
66 | dirname: pathutil.dirname path
67 | ext: pathutil.extname file
68 | stats: stats
69 | skip = !filter fobj
70 | if stats.isDirectory()
71 | if skip
72 | done()
73 | else
74 | readdir path, (err) ->
75 | return done err if err?
76 | if ignoreFolders
77 | done()
78 | else
79 | iterator fobj, done
80 | else
81 | if skip then done() else iterator fobj, done
82 | async.forEach files, iter, cb
83 |
84 | readdir root, cb
85 |
--------------------------------------------------------------------------------
/lib/modules/javascript.coffee:
--------------------------------------------------------------------------------
1 |
2 | fs = require 'fs'
3 | pathutil = require 'path'
4 | async = require 'async'
5 | {Asset} = require '../.'
6 |
7 | class exports.JavascriptAsset extends Asset
8 | mimetype: 'text/javascript'
9 | create: (options) ->
10 | @options = options
11 | @code = options.code
12 | @dirname = options.dirname or '/'
13 | @compress = options.compress
14 | @compress ?= false
15 | @contents = ''
16 | if @compress
17 | else
18 | @assets = []
19 | async.eachSeries @code, (path, next) =>
20 | try
21 | if path instanceof Asset
22 | asset = path
23 | return asset.on 'complete', =>
24 | if @compress
25 | @contents += asset.contents
26 | else
27 | @addAsset asset
28 | next()
29 |
30 | fileContent = fs.readFileSync pathutil.join(@dirname, path), 'utf8'
31 | assetUrl = '/' + path.replace('.coffee', '.js')
32 | .replace(/\\/g, '\/')
33 | jsContent = ''
34 | switch
35 | when path.indexOf('.coffee') isnt -1
36 | @setupCoffeescript()
37 | try
38 | jsContent = @coffeescript.compile fileContent
39 | catch error
40 | error.stack = "Syntax Error: In #{pathutil.join(@dirname, path)} on line #{error.location.first_line}\n" + error.stack
41 | throw error
42 | else
43 | jsContent = fileContent
44 | if @compress
45 | @contents += jsContent + '\n'
46 | else
47 | @addAsset new Asset {
48 | url: assetUrl
49 | contents: jsContent
50 | }
51 | next()
52 | catch error
53 | @emit 'error', error
54 | , =>
55 | @emit 'created'
56 |
57 | tag: ->
58 | if @assets?
59 | tag = ''
60 | for asset in @assets
61 | tag += "\n"
63 | return tag
64 | if @contents? and @contents isnt ''
65 | tag = "\n"
67 |
68 | setupCoffeescript: ->
69 | @coffeescript ?= @options.coffeescript or require 'coffee-script'
70 |
71 | setupTypescript: ->
72 | @typescript ?= @options.typescript or require 'node-typescript'
73 |
74 |
--------------------------------------------------------------------------------
/lib/modules/jade.coffee:
--------------------------------------------------------------------------------
1 | fs = require 'fs'
2 | pathutil = require 'path'
3 | uglify = require 'uglify-js'
4 | async = require 'async'
5 | jade = require 'jade'
6 | Asset = require('../index').Asset
7 |
8 | class exports.JadeAsset extends Asset
9 | mimetype: 'text/javascript'
10 |
11 | create: (options) ->
12 | @dirname = pathutil.resolve options.dirname
13 | @separator = options.separator or '/'
14 | @compress = options.compress or false
15 | @toWatch = @dirname
16 | @clientVariable = options.clientVariable or 'Templates'
17 | @beforeCompile = options.beforeCompile or null
18 | @fileObjects = @getFileobjects @dirname
19 | if @rack?
20 | assets = {}
21 | for asset in @rack.assets
22 | assets[asset.url] = asset.specificUrl
23 |
24 | @assetsMap = """
25 | var assets = {
26 | assets: #{JSON.stringify(assets)},
27 | url: #{(url) -> @assets[url]}
28 | };
29 | """
30 | @createContents()
31 |
32 | createContents: ->
33 | @contents = fs.readFileSync require.resolve('jade').replace 'index.js', 'runtime.js'
34 | @contents += '(function(){ \n' if @assetsMap?
35 | @contents += @assetsMap if @assetsMap?
36 | @contents += "window.#{@clientVariable} = {\n"
37 | @fileContents = ""
38 |
39 | for fileObject in @fileObjects
40 | if @fileContents.length > 0
41 | @fileContents += ","
42 |
43 | if @assetsMap?
44 | @fileContents += """'#{fileObject.funcName}': function(locals) {
45 | locals = locals || {};
46 | locals['assets'] = assets;
47 | return (#{fileObject.compiled})(locals)
48 | }"""
49 | else
50 | @fileContents += "'#{fileObject.funcName}': #{fileObject.compiled}"
51 |
52 | @contents += @fileContents
53 | @contents += '};'
54 | @contents += '})();' if @assetsMap?
55 | @contents = uglify.minify(@contents, {fromString: true}).code if @compress
56 | unless @hasError
57 | @emit 'created'
58 |
59 | getFileobjects: (dirname, prefix='') ->
60 | filenames = fs.readdirSync dirname
61 | paths = []
62 | for filename in filenames
63 | continue if filename.slice(0, 1) is '.'
64 | path = pathutil.join dirname, filename
65 | stats = fs.statSync path
66 | if stats.isDirectory()
67 | newPrefix = "#{prefix}#{pathutil.basename(path)}#{@separator}"
68 | paths = paths.concat @getFileobjects path, newPrefix
69 | else
70 | continue if filename.indexOf('.jade') is -1
71 | funcName = "#{prefix}#{pathutil.basename(path, '.jade')}"
72 | fileContents = fs.readFileSync path, 'utf8'
73 | fileContents = @beforeCompile fileContents if @beforeCompile?
74 | try
75 | compiled = jade.compile fileContents,
76 | client: true,
77 | compileDebug: false,
78 | filename: path
79 | paths.push
80 | path: path
81 | funcName: funcName
82 | compiled: compiled
83 | catch error
84 | @hasError = true
85 | @emit 'error', error
86 | paths
87 |
88 |
89 |
--------------------------------------------------------------------------------
/test/sass.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 |
8 | describe 'a sass asset', ->
9 | app = null
10 |
11 | it 'should work with .scss', (done) ->
12 | compiled = fs.readFileSync "#{__dirname}/fixtures/sass/simple.css", 'utf8'
13 | app = express().http()
14 | app.use new rack.SassAsset
15 | filename: "#{__dirname}/fixtures/sass/simple.scss"
16 | url: '/style.css'
17 | app.listen 7076, ->
18 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
19 | response.headers['content-type'].should.equal 'text/css'
20 | body.should.equal compiled
21 | done()
22 |
23 | it 'should work with .sass', (done) ->
24 | compiled = fs.readFileSync "#{__dirname}/fixtures/sass/simple.css", 'utf8'
25 | app = express().http()
26 | app.use new rack.SassAsset
27 | filename: "#{__dirname}/fixtures/sass/simple.sass"
28 | url: '/style.css'
29 | app.listen 7076, ->
30 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
31 | response.headers['content-type'].should.equal 'text/css'
32 | body.should.equal compiled
33 | done()
34 |
35 | it 'should work compressed', (done) ->
36 | compiled = fs.readFileSync "#{__dirname}/fixtures/sass/simple.min.css", 'utf8'
37 | app = express().http()
38 | app.use new rack.SassAsset
39 | filename: "#{__dirname}/fixtures/sass/simple.scss"
40 | url: '/style.css'
41 | compress: true
42 | app.listen 7076, ->
43 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
44 | response.headers['content-type'].should.equal 'text/css'
45 | body.should.equal compiled
46 | done()
47 |
48 | it 'should work with paths', (done) ->
49 | compiled = fs.readFileSync "#{__dirname}/fixtures/sass/another.css", 'utf8'
50 | app = express().http()
51 | app.use new rack.SassAsset
52 | filename: "#{__dirname}/fixtures/sass/another.scss"
53 | url: '/style.css'
54 | paths: ["#{__dirname}/fixtures/sass/more"]
55 | app.listen 7076, ->
56 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
57 | response.headers['content-type'].should.equal 'text/css'
58 | body.should.equal compiled
59 | done()
60 |
61 | it 'should work with the rack', (done) ->
62 | app = express().http()
63 | app.use assets = new rack.AssetRack [
64 | new rack.Asset
65 | url: '/background.png'
66 | contents: 'not a real png'
67 | new rack.SassAsset
68 | filename: "#{__dirname}/fixtures/sass/simple.scss"
69 | url: '/simple.css'
70 | new rack.SassAsset
71 | filename: "#{__dirname}/fixtures/sass/another.scss"
72 | url: '/style.css'
73 | paths: ["#{__dirname}/fixtures/sass/more"]
74 | ]
75 | app.listen 7076, ->
76 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
77 | backgroundUrl = assets.url('/background.png')
78 | body.indexOf(backgroundUrl).should.not.equal -1
79 | done()
80 |
81 | afterEach (done) -> process.nextTick ->
82 | app.server.close done
83 |
--------------------------------------------------------------------------------
/test/jade.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 |
8 | # Note: Direct file comparisons for tests exhibited
9 | # cross platform testing issues.
10 |
11 | describe 'a jade asset', ->
12 | app = null
13 | fixturesDir = "#{__dirname}/fixtures/jade"
14 |
15 | beforeEach (done) ->
16 | app = express().http()
17 | app.listen 7076, done
18 |
19 | it 'should work', (done) ->
20 | app.use new rack.JadeAsset
21 | dirname: fixturesDir
22 | url: '/templates.js'
23 | easyrequest 'http://localhost:7076/templates.js', (error, response, body) ->
24 | response.headers['content-type'].should.equal 'text/javascript'
25 | window = {}
26 | eval(body)
27 |
28 | # Due to updates to Jade, runtime.js no longer generates an object called 'jade' but instead attempts
29 | # to export the module via various ways (exports, window, etc.). Thus, for unit tests run in node,
30 | # we can get the variable from module.exports.
31 | jade = module.exports;
32 |
33 | testFile = fs.readFileSync "#{fixturesDir}/test.html", 'utf8'
34 | window.Templates.test().should.equal testFile
35 | userFile = fs.readFileSync "#{fixturesDir}/user.html", 'utf8'
36 | window.Templates.user(users: ['fred', 'steve']).should.equal userFile
37 | done()
38 |
39 | it 'should work in a rack', (done) ->
40 | app.use new rack.AssetRack [
41 | new rack.Asset
42 | url: '/image.png'
43 | contents: fs.readFileSync "#{fixturesDir}/image.png", 'utf8'
44 | new rack.JadeAsset
45 | dirname: fixturesDir
46 | url: '/templates-rack.js'
47 | ]
48 | easyrequest 'http://localhost:7076/templates-rack.js', (error, response, body) ->
49 | response.headers['content-type'].should.equal 'text/javascript'
50 | window = {}
51 | eval(body)
52 | jade = module.exports;
53 | testFile = fs.readFileSync "#{fixturesDir}/test.html", 'utf8'
54 | window.Templates.test().should.equal testFile
55 | userFile = fs.readFileSync "#{fixturesDir}/user.html", 'utf8'
56 | window.Templates.user(users: ['fred', 'steve']).should.equal userFile
57 | dependencyFile = fs.readFileSync "#{fixturesDir}/dependency.html", 'utf8'
58 | console.log window.Templates.dependency()
59 | window.Templates.dependency().should.equal dependencyFile
60 | done()
61 |
62 | it 'should work compressed', (done) ->
63 | app.use new rack.Rack [
64 | new rack.JadeAsset
65 | dirname: "#{fixturesDir}"
66 | url: '/templates.js'
67 | new rack.JadeAsset
68 | dirname: "#{fixturesDir}"
69 | url: '/templates.min.js'
70 | compress: true
71 | ]
72 |
73 | easyrequest 'http://localhost:7076/templates.min.js', (error, response, body) ->
74 | response.headers['content-type'].should.equal 'text/javascript'
75 | window = {}
76 | eval(body)
77 | jade = module.exports;
78 | testFile = fs.readFileSync "#{fixturesDir}/test.html", 'utf8'
79 | window.Templates.test().should.equal testFile
80 | userFile = fs.readFileSync "#{fixturesDir}/user.html", 'utf8'
81 | window.Templates.user(users: ['fred', 'steve']).should.equal userFile
82 | easyrequest 'http://localhost:7076/templates.js', (error, response, bodyLong) ->
83 | bodyLong.length.should.be.above(body.length)
84 | done()
85 |
86 | afterEach (done) ->
87 | app.server.close done
88 |
89 |
--------------------------------------------------------------------------------
/test/less.coffee:
--------------------------------------------------------------------------------
1 |
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 |
8 | describe 'a less asset', ->
9 | app = null
10 |
11 | it 'should work', (done) ->
12 | compiled = fs.readFileSync "#{__dirname}/fixtures/less/simple.css", 'utf8'
13 | app = express().http()
14 | app.use new rack.LessAsset
15 | filename: "#{__dirname}/fixtures/less/simple.less"
16 | url: '/style.css'
17 | app.listen 7076, ->
18 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
19 | response.headers['content-type'].should.equal 'text/css'
20 | body.should.equal compiled
21 | done()
22 |
23 | it 'should work compressed', (done) ->
24 | compiled = fs.readFileSync "#{__dirname}/fixtures/less/simple.min.css", 'utf8'
25 | app = express().http()
26 | app.use new rack.LessAsset
27 | filename: "#{__dirname}/fixtures/less/simple.less"
28 | url: '/style.css'
29 | compress: true
30 | app.listen 7076, ->
31 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
32 | response.headers['content-type'].should.equal 'text/css'
33 | body.should.equal compiled
34 | done()
35 |
36 | it 'should work with paths', (done) ->
37 | compiled = fs.readFileSync "#{__dirname}/fixtures/less/another.css", 'utf8'
38 | app = express().http()
39 | app.use new rack.LessAsset
40 | filename: "#{__dirname}/fixtures/less/another.less"
41 | url: '/style.css'
42 | paths: ["#{__dirname}/fixtures/less/more"]
43 | app.listen 7076, ->
44 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
45 | response.headers['content-type'].should.equal 'text/css'
46 | body.should.equal compiled
47 | done()
48 |
49 | it 'should work with the rack', (done) ->
50 | app = express().http()
51 | app.use assets = new rack.AssetRack [
52 | new rack.Asset
53 | url: '/background.png'
54 | contents: 'not a real png'
55 | new rack.LessAsset
56 | filename: "#{__dirname}/fixtures/less/simple.less"
57 | url: '/simple.css'
58 | new rack.LessAsset
59 | filename: "#{__dirname}/fixtures/less/dependency.less"
60 | url: '/style.css'
61 | ]
62 | app.listen 7076, ->
63 | easyrequest 'http://localhost:7076/style.css', (error, response, body) ->
64 | backgroundUrl = assets.url('/background.png')
65 | body.indexOf(backgroundUrl).should.not.equal -1
66 | done()
67 |
68 | it.skip 'should throw a meaningful error', (done) ->
69 | should.Throw ->
70 | app.use assets = new rack.AssetRack [
71 | asset = new rack.LessAsset
72 | filename: "#{__dirname}/fixtures/less/syntax-error.less"
73 | url: '/style.css'
74 | ]
75 | should.Throw ->
76 | app.use assets = new rack.AssetRack [
77 | asset = new rack.LessAsset
78 | contents : """
79 | @import "file-that-doesnt-exist.less";
80 | body {
81 | background-color: blue;
82 | div {
83 | background-color: pink;
84 | }
85 | }
86 | """
87 | url: 'style.css'
88 | ]
89 |
90 | # just start a server so that `afterEach` can close it without issues
91 | app = express().http()
92 | app.listen 7076, ->
93 | done()
94 |
95 | afterEach (done) -> process.nextTick ->
96 | app.server.close done
97 |
--------------------------------------------------------------------------------
/test/asset.coffee:
--------------------------------------------------------------------------------
1 |
2 | async = require 'async'
3 | should = require('chai').should()
4 | rack = require '../.'
5 | express = require 'express.io'
6 | easyrequest = require 'request'
7 | fs = require 'fs'
8 |
9 | describe 'an asset', ->
10 | app = null
11 |
12 | it 'should work with no hash', (done) ->
13 | app = express().http()
14 | app.use asset = new rack.Asset
15 | url: '/blank.txt',
16 | contents: 'asset-rack'
17 | app.listen 7076, ->
18 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
19 | response.headers['content-type'].should.equal 'text/plain'
20 | should.not.exist response.headers['cache-control']
21 | body.should.equal 'asset-rack'
22 | done()
23 |
24 | it 'should work with hash', (done) ->
25 | app = express().http()
26 | app.use new rack.Asset
27 | url: '/blank.txt',
28 | contents: 'asset-rack'
29 | app.listen 7076, ->
30 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
31 | response.headers['content-type'].should.equal 'text/plain'
32 | response.headers['cache-control'].should.equal 'public, max-age=31536000'
33 | body.should.equal 'asset-rack'
34 | done()
35 |
36 | it 'should work with no hash option', (done) ->
37 | app = express().http()
38 | app.use asset = new rack.Asset
39 | url: '/blank.txt',
40 | contents: 'asset-rack'
41 | hash: false
42 | app.listen 7076, ->
43 | async.parallel [
44 | (next) ->
45 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
46 | response.headers['content-type'].should.equal 'text/plain'
47 | should.not.exist response.headers['cache-control']
48 | body.should.equal 'asset-rack'
49 | next()
50 | (next) ->
51 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
52 | response.statusCode.should.equal 404
53 | next()
54 | ], done
55 |
56 | it 'should work with hash option', (done) ->
57 | app = express().http()
58 | app.use new rack.Asset
59 | url: '/blank.txt'
60 | contents: 'asset-rack'
61 | hash: true
62 | app.listen 7076, ->
63 | async.parallel [
64 | (next) ->
65 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
66 | response.statusCode.should.equal 404
67 | next()
68 | (next) ->
69 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
70 | response.headers['content-type'].should.equal 'text/plain'
71 | response.headers['cache-control'].should.equal 'public, max-age=31536000'
72 | body.should.equal 'asset-rack'
73 | next()
74 | ], done
75 |
76 | it 'should set caches', (done) ->
77 | app = express().http()
78 | app.use new rack.Asset
79 | url: '/blank.txt'
80 | contents: 'asset-rack'
81 | maxAge: 3600
82 | app.listen 7076, ->
83 | async.parallel [
84 | (next) ->
85 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
86 | response.headers['content-type'].should.equal 'text/plain'
87 | should.not.exist response.headers['cache-control']
88 | body.should.equal 'asset-rack'
89 | next()
90 | (next) ->
91 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
92 | response.headers['content-type'].should.equal 'text/plain'
93 | response.headers['cache-control'].should.equal 'public, max-age=3600'
94 | body.should.equal 'asset-rack'
95 | next()
96 | ], done
97 |
98 | it 'should set caches with allow no hash option', (done) ->
99 | app = express().http()
100 | app.use new rack.Asset
101 | url: '/blank.txt'
102 | contents: 'asset-rack'
103 | maxAge: 3600
104 | allowNoHashCache: true
105 | app.listen 7076, ->
106 | async.parallel [
107 | (next) ->
108 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
109 | response.headers['content-type'].should.equal 'text/plain'
110 | response.headers['cache-control'].should.equal 'public, max-age=3600'
111 | body.should.equal 'asset-rack'
112 | next()
113 | (next) ->
114 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
115 | response.headers['content-type'].should.equal 'text/plain'
116 | response.headers['cache-control'].should.equal 'public, max-age=3600'
117 | body.should.equal 'asset-rack'
118 | next()
119 | ], done
120 |
121 | afterEach (done) -> process.nextTick ->
122 | app.server.close done
123 |
--------------------------------------------------------------------------------
/test/dynamic.coffee:
--------------------------------------------------------------------------------
1 | async = require 'async'
2 | should = require('chai').should()
3 | rack = require '../.'
4 | express = require 'express.io'
5 | easyrequest = require 'request'
6 | fs = require 'fs'
7 | {join} = require 'path'
8 |
9 | class CustomAsset extends rack.Asset
10 | create: (options) ->
11 | @emit 'created', contents: fs.readFileSync options.filename
12 |
13 | describe 'a dynamic asset builder', ->
14 | app = null
15 | fixturesDir = join __dirname, 'fixtures'
16 |
17 | it 'should work with any custom asset that takes filename option', (done) ->
18 | app = express().http()
19 | app.use new rack.DynamicAssets
20 | type: CustomAsset
21 | urlPrefix: '/static'
22 | dirname: join fixturesDir, 'static'
23 | app.listen 7076, ->
24 | async.parallel [
25 | (next) ->
26 | easyrequest 'http://localhost:7076/static/blank.txt', (error, response, body) ->
27 | response.headers['content-type'].should.equal 'text/plain'
28 | body.should.equal fs.readFileSync join(fixturesDir, 'static/blank.txt'), 'utf8'
29 | next()
30 | (next) ->
31 | easyrequest 'http://localhost:7076/static/crazy-man.svg', (error, response, body) ->
32 | response.headers['content-type'].should.equal 'image/svg+xml'
33 | body.should.equal fs.readFileSync join(fixturesDir, 'static/crazy-man.svg'), 'utf8'
34 | next()
35 | ], done
36 |
37 | it 'should work with a rack', (done) ->
38 | app = express().http()
39 | app.use new rack.Rack [
40 | new rack.DynamicAssets
41 | type: CustomAsset
42 | urlPrefix: '/static'
43 | dirname: join fixturesDir, 'static'
44 | ]
45 | app.listen 7076, ->
46 | easyrequest 'http://localhost:7076/static/blank.txt', (error, response, body) ->
47 | body.should.equal fs.readFileSync join(fixturesDir, 'static/blank.txt'), 'utf8'
48 | done()
49 |
50 | it 'should work with no urlPrefix option', (done) ->
51 | app = express().http()
52 | app.use new rack.DynamicAssets
53 | type: CustomAsset
54 | dirname: join fixturesDir, 'static'
55 | app.listen 7076, ->
56 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
57 | response.statusCode.should.equal 200
58 | done()
59 |
60 | it 'should work with options option', (done) ->
61 | app = express().http()
62 | app.use new rack.DynamicAssets
63 | type: CustomAsset
64 | dirname: join fixturesDir, 'static'
65 | options:
66 | mimetype: 'text/css'
67 | app.listen 7076, ->
68 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
69 | response.headers['content-type'].should.equal 'text/css'
70 | done()
71 |
72 | it 'should work with a filter', (done) ->
73 | app = express().http()
74 | app.use new rack.Rack [
75 | new rack.DynamicAssets
76 | type: CustomAsset
77 | urlPrefix: '/string-filter'
78 | dirname: join fixturesDir, 'static'
79 | filter: 'txt'
80 | new rack.DynamicAssets
81 | type: CustomAsset
82 | urlPrefix: '/function-filter'
83 | dirname: join fixturesDir, 'static'
84 | filter: (file) -> file.ext is '.svg'
85 | ]
86 | app.listen 7076, ->
87 | async.parallel [
88 | (next) ->
89 | easyrequest 'http://localhost:7076/string-filter/blank.txt', (error, response, body) ->
90 | response.statusCode.should.equal 200
91 | next()
92 | (next) ->
93 | easyrequest 'http://localhost:7076/string-filter/crazy-man.svg', (error, response, body) ->
94 | response.statusCode.should.equal 404
95 | next()
96 | (next) ->
97 | easyrequest 'http://localhost:7076/function-filter/blank.txt', (error, response, body) ->
98 | response.statusCode.should.equal 404
99 | next()
100 | (next) ->
101 | easyrequest 'http://localhost:7076/function-filter/crazy-man.svg', (error, response, body) ->
102 | response.statusCode.should.equal 200
103 | next()
104 | ], done
105 |
106 | it 'should work with StylusAsset', (done) ->
107 | app = express().http()
108 | app.use new rack.DynamicAssets
109 | type: rack.StylusAsset
110 | dirname: join fixturesDir, 'stylus'
111 | filter: 'styl'
112 | app.listen 7076, ->
113 | easyrequest 'http://localhost:7076/simple.css', (error, response, body) ->
114 | response.headers['content-type'].should.equal 'text/css'
115 | body.should.equal fs.readFileSync join(fixturesDir, 'stylus/simple.css'), 'utf8'
116 | done()
117 |
118 | it 'should work with LessAsset', (done) ->
119 | app = express().http()
120 | app.use new rack.DynamicAssets
121 | type: rack.LessAsset
122 | dirname: join fixturesDir, 'less'
123 | filter: (file) -> file.ext is '.less' and file.name isnt 'another.less' and file.name isnt 'syntax-error.less'
124 | app.listen 7076, ->
125 | easyrequest 'http://localhost:7076/simple.css', (error, response, body) ->
126 | response.headers['content-type'].should.equal 'text/css'
127 | body.should.equal fs.readFileSync join(fixturesDir, 'less/simple.css'), 'utf8'
128 | done()
129 |
130 | # TODO: re-enable thi test
131 | """
132 | it 'should work with SassAsset', (done) ->
133 | app = express().http()
134 | app.use new rack.DynamicAssets
135 | type: rack.SassAsset
136 | dirname: join fixturesDir, 'sass'
137 | filter: 'sass'
138 | app.listen 7076, ->
139 | easyrequest 'http://localhost:7076/simple.css', (error, response, body) ->
140 | response.headers['content-type'].should.equal 'text/css'
141 | body.should.equal fs.readFileSync join(fixturesDir, 'sass/simple.css'), 'utf8'
142 | done()
143 | """
144 |
145 | it 'should work with SnocketsAsset', (done) ->
146 | app = express().http()
147 | app.use new rack.DynamicAssets
148 | type: rack.SnocketsAsset
149 | dirname: join fixturesDir, 'snockets'
150 | filter: 'coffee'
151 | app.listen 7076, ->
152 | easyrequest 'http://localhost:7076/app.js', (error, response, body) ->
153 | response.headers['content-type'].should.equal 'text/javascript'
154 | body.should.equal fs.readFileSync join(fixturesDir, 'snockets/app.js'), 'utf8'
155 | done()
156 |
157 | it 'should work with BrowserifyAsset', (done) ->
158 | app = express().http()
159 | app.use new rack.DynamicAssets
160 | type: rack.BrowserifyAsset
161 | dirname: join fixturesDir, 'browserify'
162 | filter: 'coffee'
163 | app.listen 7076, ->
164 | easyrequest 'http://localhost:7076/app.js', (error, response, body) ->
165 | response.headers['content-type'].should.equal 'text/javascript'
166 | #body.should.equal fs.readFileSync join(fixturesDir, 'browserify/app.js'), 'utf8'
167 | done()
168 |
169 | afterEach (done) -> process.nextTick ->
170 | app.server.close done
171 |
--------------------------------------------------------------------------------
/lib/asset.coffee:
--------------------------------------------------------------------------------
1 |
2 | # Asset.coffee - The Asset class is the core abstraction for the framework
3 |
4 | # Pull in our dependencies
5 | async = require 'async'
6 | crypto = require 'crypto'
7 | pathutil = require 'path'
8 | fs = require 'fs'
9 | zlib = require 'zlib'
10 | mime = require 'mime'
11 | {extend} = require './util'
12 | {EventEmitter} = require 'events'
13 |
14 | # IE8 Compatibility
15 | mime.types.js = 'text/javascript'
16 | mime.extensions['text/javascript'] = 'js'
17 |
18 | # Asset class handles compilation and a lot of other functionality
19 | class exports.Asset extends EventEmitter
20 |
21 | # Default max age is set to one year
22 | defaultMaxAge: 60*60*24*365
23 |
24 | constructor: (options) ->
25 | super()
26 | options ?= {}
27 |
28 | # Set the url
29 | @url = options.url if options.url?
30 |
31 | # Set the cotents if given
32 | @contents = options.contents if options.contents?
33 |
34 | # Set headers if given
35 | @headers = if options.headers then options.headers else {}
36 | headers = {}
37 | for key, value of @headers
38 | headers[key.toLowerCase()] = value
39 | @headers = headers
40 |
41 | # Get the extension from the url
42 | @ext = pathutil.extname @url
43 |
44 | # Set whether to watch or not
45 | @watch = options.watch
46 | @watch ?= false
47 |
48 | # Set the correct mimetype
49 | @mimetype = options.mimetype if options.mimetype?
50 | @mimetype ?= mime.types[@ext.slice(1, @ext.length)]
51 | @mimetype ?= 'text/plain'
52 |
53 | # Whether to gzip the asset or not
54 | @gzip = options.gzip
55 |
56 | # Whether to hash the url or not or both
57 | @hash = options.hash if options.hash?
58 |
59 | # Max age for HTTP cache control
60 | @maxAge = options.maxAge if options.maxAge?
61 |
62 | # Whether to allow caching of non-hashed urls
63 | @allowNoHashCache = options.allowNoHashCache if options.allowNoHashCache?
64 |
65 | # Fire callback if someone listens for a "complete" event
66 | # and it has already been called
67 | @on 'newListener', (event, listener) =>
68 | if event is 'complete' and @completed is true
69 | listener()
70 |
71 | # This event is triggered after the contents have been created
72 | @on 'created', (data) =>
73 |
74 | # If content then it's a single asset
75 | if data?.contents?
76 | @contents = data.contents
77 |
78 | # If assets then it's a mutil asset
79 | if data?.assets?
80 | @assets = data.assets
81 |
82 | # If this is a single asset then do some post processing
83 | if @contents?
84 | @createSpecificUrl()
85 | @createHeaders()
86 |
87 |
88 | # If it's a muti asset then make sure they are all completed
89 | if @assets?
90 | async.forEach @assets, (asset, done) ->
91 | asset.on 'error', done
92 | asset.on 'complete', done
93 | , (error) =>
94 | return @emit 'error', error if error?
95 | @completed = true
96 | @emit 'complete'
97 | else
98 | @completed = true
99 |
100 | # Handles gzipping
101 | if @gzip
102 | zlib.gzip @contents, (error, gzip) =>
103 | @gzipContents = gzip
104 | @emit 'complete'
105 | else
106 | @emit 'complete'
107 |
108 | # Does the file watching
109 | if @watch
110 | @watcher = fs.watch @toWatch, (event, filename) =>
111 | if event is 'change'
112 | @watcher.close()
113 | @completed = false
114 | @assets = false
115 | process.nextTick =>
116 | @emit 'start'
117 |
118 | # Listen for errors and throw if no listeners
119 | @on 'error', (error) =>
120 | throw error if @listeners('error') is 1
121 | @on 'start', =>
122 | @maxAge ?= @rack?.maxAge
123 | @maxAge ?= @defaultMaxAge unless @hash is false
124 | @allowNoHashCache ?= @rack?.allowNoHashCache
125 | @create options
126 |
127 | # Next tick because we need to wait on a possible rack
128 | process.nextTick =>
129 |
130 | # Setting max age for HTTP cache control
131 | @maxAge ?= @defaultMaxAge
132 |
133 | # Create the asset unless it is part of a rack
134 | # then the rack will trigger the "start" event
135 | return @create options unless @rack?
136 |
137 | # Add an asset for multi asset support
138 | addAsset: (asset) ->
139 | @assets = [] unless @assets?
140 | @assets.push asset
141 |
142 | # Responds to an express route
143 | respond: (request, response) ->
144 | headers = {}
145 | if request.path is @url and @allowNoHashCache isnt true
146 | for key, value of @headers
147 | headers[key] = value
148 | delete headers['cache-control']
149 | else
150 | headers = @headers
151 | for key, value of headers
152 | response.header key, value
153 | if @gzip
154 | response.send @gzipContents
155 | else response.send @contents
156 |
157 | # Check if a given url "matches" this asset
158 | checkUrl: (url) ->
159 | url is @specificUrl or (not @hash? and url is @url)
160 |
161 | # Used so that an asset can be express middleware
162 | handle: (request, response, next) ->
163 | handle = =>
164 | if @assets?
165 | for asset in @assets
166 | if asset.checkUrl request.path
167 | return asset.respond request, response
168 | if @checkUrl(request.path)
169 | @respond request, response
170 | else next()
171 | if @completed is true
172 | handle()
173 | else @on 'complete', ->
174 | handle()
175 |
176 | # Default create method, usually overwritten
177 | create: (options) ->
178 |
179 | # At the end of a create method you always call
180 | # the created event
181 | @emit 'created'
182 |
183 | # Create the headers for an asset
184 | createHeaders: ->
185 | @headers['content-type'] ?= "#{@mimetype}"
186 | if @gzip
187 | @headers['content-encoding'] ?= 'gzip'
188 | if @maxAge?
189 | @headers['cache-control'] ?= "public, max-age=#{@maxAge}"
190 |
191 | # Gets the HTML tag for an asset
192 | tag: ->
193 | switch @mimetype
194 | when 'text/javascript'
195 | tag = "\n"
197 | when 'text/css'
198 | return "\n"
199 |
200 | # Creates and md5 hash of the url for caching
201 | createSpecificUrl: ->
202 | @md5 = crypto.createHash('md5').update(@contents).digest 'hex'
203 |
204 | # This is the no hash option
205 | if @hash is false
206 | @useDefaultMaxAge = false
207 | return @specificUrl = @url
208 |
209 | # Construction of the hashed url
210 | @specificUrl = "#{@url.slice(0, @url.length - @ext.length)}-#{@md5}#{@ext}"
211 |
212 | # Might need a hostname if not on same server
213 | if @hostname?
214 | @specificUrl = "//#{@hostname}#{@specificUrl}"
215 |
216 |
217 | # For extending this class in javascript
218 | # for coffeescript you can use the builtin extends
219 | @extend: extend
220 |
--------------------------------------------------------------------------------
/lib/rack.coffee:
--------------------------------------------------------------------------------
1 |
2 | # Rack.coffee - A Rack is an asset manager
3 |
4 | # Pull in our dependencies
5 | async = require 'async'
6 | pkgcloud = require 'pkgcloud'
7 | fs = require 'fs'
8 | jade = require 'jade'
9 | pathutil = require 'path'
10 | {BufferStream, extend} = require('./util')
11 | {EventEmitter} = require 'events'
12 |
13 | # Rack - Manages multiple assets
14 | class exports.Rack extends EventEmitter
15 | constructor: (assets, options) ->
16 | super()
17 |
18 | # Set a default options object
19 | options ?= {}
20 |
21 | # Max age for HTTP Cache-Control
22 | @maxAge = options.maxAge
23 |
24 | # Allow non-hahshed urls to be cached
25 | @allowNoHashCache = options.allowNoHashCache
26 |
27 | # Once complete always set the completed flag
28 | @on 'complete', =>
29 | @completed = true
30 |
31 | # If someone listens for the "complete" event
32 | # check if it's already been called
33 | @on 'newListener', (event, listener) =>
34 | if event is 'complete' and @completed is true
35 | listener()
36 |
37 | # Listen for the error event, throw if no listeners
38 | @on 'error', (error) =>
39 | console.log error
40 | @hasError = true
41 | @currentError = error
42 |
43 | # Give assets in the rack a reference to the rack
44 | for asset in assets
45 | asset.rack = this
46 |
47 | # Create a flattened array of assets
48 | @assets = []
49 |
50 | # Do this in series for dependency conflicts
51 | async.forEachSeries assets, (asset, next) =>
52 |
53 | # Listen for any asset error events
54 | asset.on 'error', (error) =>
55 | next error
56 |
57 | # Wait for assets to finish completing
58 | asset.on 'complete', =>
59 |
60 | # This is necessary because of asset recompilation
61 | return if @completed
62 |
63 | # If the asset has contents, it's a single asset
64 | if asset.contents?
65 | @assets.push asset
66 |
67 | # If it has assets, then it's multi-asset
68 | if asset.assets?
69 | @assets = @assets.concat asset.assets
70 | next()
71 |
72 | # This tells our asset to start
73 | asset.emit 'start'
74 |
75 | # Handle any errors for the assets
76 | , (error) =>
77 | return @emit 'error', error if error?
78 | @emit 'complete'
79 |
80 | # Makes the rack function as express middleware
81 | handle: (request, response, next) ->
82 | response.locals assets: this
83 | if request.url.slice(0,11) is '/asset-rack'
84 | return @handleAdmin request, response, next
85 | if @hasError
86 | for asset in @assets
87 | check = asset.checkUrl request.path
88 | return asset.respond request, response if check
89 | return response.redirect '/asset-rack/error'
90 | handle = =>
91 | for asset in @assets
92 | check = asset.checkUrl request.path
93 | return asset.respond request, response if check
94 | next()
95 | if @completed
96 | handle()
97 | else @on 'complete', handle
98 |
99 | handleError: (request, response, next) ->
100 | # No admin in production for now
101 | return next() if process.env.NODE_ENV is 'production'
102 | errorPath = pathutil.join __dirname, 'admin/templates/error.jade'
103 | fs.readFile errorPath, 'utf8', (error, contents) =>
104 | return next error if error?
105 | compiled = jade.compile contents,
106 | filename: errorPath
107 | response.send compiled
108 | stack: @currentError.stack.split '\n'
109 |
110 | handleAdmin: (request, response, next) ->
111 | # No admin in production for now
112 | return next() if process.env.NODE_ENV is 'production'
113 | split = request.url.split('/')
114 | if split.length > 2
115 | path = request.url.replace '/asset-rack/', ''
116 | if path is 'error'
117 | return @handleError request, response, next
118 | response.sendfile pathutil.join __dirname, 'admin', path
119 | else
120 | adminPath = pathutil.join __dirname, 'admin/templates/admin.jade'
121 | fs.readFile adminPath, 'utf8', (error, contents) =>
122 | return next error if error?
123 | compiled = jade.compile contents,
124 | filename: adminPath
125 | response.send compiled
126 | assets: @assets
127 |
128 | # Writes a config file of urls to hashed urls for CDN use
129 | writeConfigFile: (filename) ->
130 | config = {}
131 | for asset in @assets
132 | config[asset.url] = asset.specificUrl
133 | fs.writeFileSync filename, JSON.stringify(config)
134 |
135 | # Deploy assets to a CDN
136 | deploy: (options, next) ->
137 | options.keyId = options.accessKey
138 | options.key = options.secretKey
139 | deploy = =>
140 | client = pkgcloud.storage.createClient options
141 | assets = @assets
142 | # Big time hack for rackspace, first asset doesn't upload, very strange.
143 | # Might be bug with pkgcloud. This hack just uploads the first file again
144 | # at the end.
145 | assets = @assets.concat @assets[0] if options.provider is 'rackspace'
146 | async.forEachSeries assets, (asset, next) =>
147 | stream = null
148 | headers = {}
149 | if asset.gzip
150 | stream = new BufferStream asset.gzipContents
151 | headers['content-encoding'] = 'gzip'
152 | else
153 | stream = new BufferStream asset.contents
154 | url = asset.specificUrl.slice 1, asset.specificUrl.length
155 | for key, value of asset.headers
156 | headers[key] = value
157 | headers['x-amz-acl'] = 'public-read' if options.provider is 'amazon'
158 | clientOptions =
159 | container: options.container
160 | remote: url
161 | headers: headers
162 | stream: stream
163 | client.upload clientOptions, (error) ->
164 | return next error if error?
165 | next()
166 | , (error) =>
167 | if error?
168 | return next error if next?
169 | throw error
170 | if options.configFile?
171 | @writeConfigFile options.configFile
172 | next() if next?
173 | if @completed
174 | deploy()
175 | else @on 'complete', deploy
176 |
177 | # Creates an HTML tag for a given asset
178 | tag: (url) ->
179 | for asset in @assets
180 | return asset.tag() if asset.url is url
181 | throw new Error "No asset found for url: #{url}"
182 |
183 | # Gets the hashed url for a given url
184 | url: (url) ->
185 | for asset in @assets
186 | return asset.specificUrl if url is asset.url
187 |
188 | # Extend the class for javascript
189 | @extend: extend
190 |
191 | # The ConfigRack uses a json file and a hostname to map assets to a url
192 | # without actually compiling them
193 | class ConfigRack
194 | constructor: (options) ->
195 | # Check for required options
196 | throw new Error('options.configFile is required') unless options.configFile?
197 | throw new Error('options.hostname is required') unless options.hostname?
198 |
199 | # Setup our options
200 | @assetMap = require options.configFile
201 | @hostname = options.hostname
202 |
203 | # For hooking up as express middleware
204 | handle: (request, response, next) ->
205 | response.locals assets: this
206 | for url, specificUrl of @assetMap
207 | if request.path is url or request.path is specificUrl
208 |
209 | # Redirect to the CDN, the config does not have the files
210 | return response.redirect "//#{@hostname}#{specificUrl}"
211 | next()
212 |
213 | # Simple function to get the tag for a url
214 | tag: (url) ->
215 | switch pathutil.extname(url)
216 | when '.js'
217 | tag = "\n"
219 | when '.css'
220 | return "\n"
221 |
222 | # Get the hashed url for a given url
223 | url: (url) ->
224 | return "//#{@hostname}#{@assetMap[url]}"
225 |
226 |
227 | # Shortcut function
228 | exports.fromConfigFile = (options) ->
229 | return new ConfigRack(options)
230 |
--------------------------------------------------------------------------------
/test/rack.coffee:
--------------------------------------------------------------------------------
1 |
2 | async = require 'async'
3 | should = require('chai').should()
4 | rack = require '../.'
5 | express = require 'express.io'
6 | easyrequest = require 'request'
7 | fs = require 'fs'
8 |
9 | describe 'a rack', ->
10 | app = null
11 | it 'should work with no hash', (done) ->
12 | app = express().http()
13 | app.use assets = new rack.Rack [
14 | new rack.Asset
15 | url: '/blank.txt'
16 | contents: 'test'
17 | new rack.Asset
18 | url: '/blank-again.txt'
19 | contents: 'test-again'
20 | ]
21 | app.listen 7076, ->
22 | async.parallel [
23 | (next) ->
24 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
25 | response.headers['content-type'].should.equal 'text/plain'
26 | should.not.exist response.headers['cache-control']
27 | body.should.equal 'test'
28 | next()
29 | (next) ->
30 | easyrequest 'http://localhost:7076/blank-again.txt', (error, response, body) ->
31 | response.headers['content-type'].should.equal 'text/plain'
32 | should.not.exist response.headers['cache-control']
33 | body.should.equal 'test-again'
34 | next()
35 | ], done
36 |
37 | it 'should work with hash', (done) ->
38 | app = express().http()
39 | app.use new rack.AssetRack [
40 | new rack.Asset
41 | url: '/blank.txt'
42 | contents: 'asset-rack'
43 | ]
44 | app.listen 7076, ->
45 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
46 | response.headers['content-type'].should.equal 'text/plain'
47 | response.headers['cache-control'].should.equal 'public, max-age=31536000'
48 | body.should.equal 'asset-rack'
49 | done()
50 |
51 | it 'should work with no hash option', (done) ->
52 | app = express().http()
53 | app.use asset = new rack.AssetRack [
54 | new rack.Asset
55 | url: '/blank.txt'
56 | contents: 'asset-rack'
57 | hash: false
58 | ]
59 | app.listen 7076, ->
60 | async.parallel [
61 | (next) ->
62 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
63 | response.headers['content-type'].should.equal 'text/plain'
64 | should.not.exist response.headers['cache-control']
65 | body.should.equal 'asset-rack'
66 | next()
67 | (next) ->
68 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
69 | response.statusCode.should.equal 404
70 | next()
71 | ], done
72 |
73 | it 'should work with hash option', (done) ->
74 | app = express().http()
75 | app.use new rack.AssetRack [
76 | new rack.Asset
77 | url: '/blank.txt'
78 | contents: 'asset-rack'
79 | hash: true
80 | ]
81 | app.listen 7076, ->
82 | async.parallel [
83 | (next) ->
84 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
85 | response.headers['content-type'].should.equal 'text/plain'
86 | response.headers['cache-control'].should.equal 'public, max-age=31536000'
87 | body.should.equal 'asset-rack'
88 | next()
89 | (next) ->
90 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
91 | response.statusCode.should.equal 404
92 | next()
93 | ], done
94 |
95 | it 'should set caches', (done) ->
96 | app = express().http()
97 | app.use new rack.AssetRack [
98 | new rack.Asset
99 | url: '/blank.txt'
100 | contents: 'asset-rack'
101 | maxAge: 3600
102 | ]
103 | app.listen 7076, ->
104 | async.parallel [
105 | (next) ->
106 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
107 | response.headers['content-type'].should.equal 'text/plain'
108 | should.not.exist response.headers['cache-control']
109 | body.should.equal 'asset-rack'
110 | next()
111 | (next) ->
112 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
113 | response.headers['content-type'].should.equal 'text/plain'
114 | response.headers['cache-control'].should.equal 'public, max-age=3600'
115 | body.should.equal 'asset-rack'
116 | next()
117 | ], done
118 |
119 | it 'should set caches with allow no hash option', (done) ->
120 | app = express().http()
121 | app.use new rack.AssetRack [
122 | new rack.Asset
123 | url: '/blank.txt'
124 | contents: 'asset-rack'
125 | maxAge: 3600
126 | allowNoHashCache: true
127 | ]
128 | app.listen 7076, ->
129 | async.parallel [
130 | (next) ->
131 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
132 | response.headers['content-type'].should.equal 'text/plain'
133 | response.headers['cache-control'].should.equal 'public, max-age=3600'
134 | body.should.equal 'asset-rack'
135 | next()
136 | (next) ->
137 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
138 | response.headers['content-type'].should.equal 'text/plain'
139 | response.headers['cache-control'].should.equal 'public, max-age=3600'
140 | body.should.equal 'asset-rack'
141 | next()
142 | ], done
143 |
144 | it 'should set caches for globals', (done) ->
145 | app = express().http()
146 | app.use new rack.AssetRack [
147 | new rack.Asset
148 | url: '/blank.txt'
149 | contents: 'asset-rack'
150 | ],
151 | maxAge: 3600
152 | app.listen 7076, ->
153 | async.parallel [
154 | (next) ->
155 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
156 | response.headers['content-type'].should.equal 'text/plain'
157 | should.not.exist response.headers['cache-control']
158 | body.should.equal 'asset-rack'
159 | next()
160 | (next) ->
161 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
162 | response.headers['content-type'].should.equal 'text/plain'
163 | response.headers['cache-control'].should.equal 'public, max-age=3600'
164 | body.should.equal 'asset-rack'
165 | next()
166 | ], done
167 |
168 | it 'should set caches with allow no hash option for globals', (done) ->
169 | app = express().http()
170 | app.use new rack.AssetRack [
171 | new rack.Asset
172 | url: '/blank.txt'
173 | contents: 'asset-rack'
174 | ],
175 | maxAge: 3600
176 | allowNoHashCache: true
177 | app.listen 7076, ->
178 | async.parallel [
179 | (next) ->
180 | easyrequest 'http://localhost:7076/blank.txt', (error, response, body) ->
181 | response.headers['content-type'].should.equal 'text/plain'
182 | response.headers['cache-control'].should.equal 'public, max-age=3600'
183 | body.should.equal 'asset-rack'
184 | next()
185 | (next) ->
186 | easyrequest 'http://localhost:7076/blank-8ac5a0913aa77cb8570e8f2b96e0a1e7.txt', (error, response, body) ->
187 | response.headers['content-type'].should.equal 'text/plain'
188 | response.headers['cache-control'].should.equal 'public, max-age=3600'
189 | body.should.equal 'asset-rack'
190 | next()
191 | ], done
192 |
193 | afterEach (done) -> process.nextTick ->
194 | app.server.close done
195 |
196 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # The Static Web is here
4 |
5 | The Static Web is __blisteringly fast__. The Static Web is __ultra efficient__. The Static Web is __cutting edge__. And now it has a hero.
6 |
7 | ```coffeescript
8 | rack = require 'asset-rack'
9 | ```
10 |
11 | The Static Web is an incredibly modern, high-performance platform for delivering apps and services. But before you dive-in, you need to start with the basics. You need to understand the fundamental building block of the static web, the __asset__.
12 |
13 |
14 | ## What is an Asset?
15 |
16 | > __An asset is a resource on the web that has the following three features:__
17 |
18 | 1. __Location (URL)__: Where on the web the resource is located.
19 | 2. __Contents (HTTP Response Body)__: The body of the response received by a web client.
20 | 3. __Meta Data (HTTP Headers)__: Gives information about the resource, like content-type, caching info.
21 |
22 | This simple definition is the theoretical bedrock of this entire framework.
23 |
24 | ## Getting Started
25 |
26 | Let's look at a simple example.
27 |
28 | ```js
29 | asset = new rack.Asset({
30 | url: '/hello.txt',
31 | contents: 'hello world'
32 | })
33 | ```
34 |
35 | Need to serve that asset with a blisteringly fast in memory cache using express?
36 |
37 | ```
38 | app.use(asset)
39 | ```
40 |
41 | ### Hash for speed and efficiency
42 |
43 | What's cool is that this new asset is available both here:
44 |
45 | ```
46 | /hello.txt
47 | ```
48 |
49 | And here
50 |
51 | ```
52 | /hello-5eb63bbbe01eeed093cb22bb8f5acdc3.txt
53 | ```
54 |
55 | That long string of letters and numbers is the md5 hash of the contents. If you hit the hash url, then we automatically set the HTTP cache to __never expire__.
56 |
57 | Now proxies, browsers, cloud storage, content delivery networks only need to download your asset one single time. You have versioning, conflict resolution all in one simple mechanism. You can update your entire entire app instantaneously. Fast, efficient, static.
58 |
59 | ### One Rack to rule them All
60 |
61 | Assets need to be managed. Enter the Rack. A Rack serializes your assets, allows you to deploy to the cloud, and reference urls and tags in your templates.
62 |
63 | Say you have a directory structure like this:
64 |
65 | ```
66 | /static # all your images, fonts, etc.
67 | /style.less # a less files with your styles
68 | ```
69 |
70 | You can create a Rack to put all your assets in.
71 |
72 | ```js
73 | assets = new rack.Rack([
74 | new rack.StaticAssets({
75 | urlPrefix: '/static',
76 | dirname: __dirname + '/static'
77 | }),
78 | new rack.LessAsset({
79 | url: '/style.css',
80 | filename: __dirname + '/style.less'
81 | })
82 | ])
83 | ```
84 |
85 | ### Use in your Templates
86 |
87 | After you hook into express, you can reference your assets in your server side templates.
88 |
89 | ```js
90 | assets.tag('/style.css')
91 | ```
92 |
93 | Which gives you the html tag.
94 |
95 | ```html
96 |
97 | ```
98 |
99 | Or you can grab just the url.
100 |
101 | ```js
102 | assets.url('/logo.png')
103 | ```
104 |
105 | Which gives the hashed url.
106 |
107 | ```
108 | /logo-34t90j0re9g034o4f3o4f3.png
109 | ```
110 |
111 | # Batteries Included
112 |
113 | We have some professional grade assets included.
114 |
115 | #### For Javascript
116 | * [Browserify](https://github.com/techpines/asset-rack/tree/master/lib#browserifyasset-jscoffeescript) - Create browserify assets that allow you to use "node-style" requires on the client-side.
117 | * [Snockets](https://github.com/techpines/asset-rack/tree/master/lib#snocketsasset-jscoffeescript) - Create snockets assets, to get the node-flavor of the "sprockets" from rails.
118 |
119 | #### For Stylesheets
120 | * [Less](http://github.com/techpines/asset-rack/tree/master/lib#lessasset) - Compile less assets, ability to use dependencies, minification.
121 | * [Stylus](https://github.com/techpines/asset-rack/tree/master/lib#stylusasset) - Compile stylu assets, ability to use dependencies, minification.
122 |
123 | #### Templates
124 | * [Jade](https://github.com/techpines/asset-rack/tree/master/lib#jadeasset) - High, performance jade templates precompiled for the browser.
125 | * [AngularTemplates](https://github.com/techpines/asset-rack/tree/master/lib#angulartemplatesasset) - AngularJS templates for you AngularJS folks.
126 |
127 | #### Other
128 | * [StaticAssets](https://github.com/techpines/asset-rack/tree/master/lib#staticassets) - Images(png, jpg, gif), fonts, whatever you got.
129 | * [DynamicAssets](https://github.com/techpines/asset-rack/tree/master/lib#dynamicassets) - For compiling file-based assets like Less or Stylus in an entire directory.
130 |
131 | ## Roll your own
132 |
133 | Asset Rack is extremely flexible. Extend the __Asset__ class and override the __create__ method to roll your own awesomeness, and watch them get automatically ka-pow'ed by your rack.
134 |
135 | ```js
136 | SuperCoolAsset = rack.Asset.extend({
137 | create: function(options) {
138 | this.contents = 'easy, easy',
139 | this.emit 'created',
140 | }
141 | })
142 | ```
143 | Or, for those with more refined taste:
144 |
145 | ```coffee
146 | class SuperCoolAsset extends rack.Asset
147 | create: (options) ->
148 | @contents = 'even easier with coffee'
149 | @emit 'created'
150 | ```
151 |
152 | Checkout the [tutorial.](https://github.com/techpines/asset-rack/tree/master/lib#extending-the-asset-class)
153 |
154 |
155 | ## Deploying to the Cloud
156 | Your assets need to be deployed! Here are the current providers that are supported.
157 |
158 | ### Amazon S3
159 |
160 | ```js
161 | assets.deploy({
162 | provider: 'amazon',
163 | container: 'some-bucket',
164 | accessKey: 'aws-access-key',
165 | secretKey: 'aws-secret-key',
166 | }, function(error) {})
167 | ```
168 |
169 | ### Rackspace Cloud Files
170 | ```js
171 | assets.deploy(
172 | provider: 'rackspace',
173 | container: 'some-container',
174 | username: 'rackspace-username',
175 | apiKey: 'rackspace-api-key',
176 | }, function(error) {})
177 | ```
178 |
179 | ### Azure Storage
180 | ```js
181 | assets.deploy(
182 | provider: 'azure',
183 | container: 'some-container',
184 | storageAccount: 'test-storage-account',
185 | storageAccessKey: 'test-storage-access-key'
186 | }, function(error) {})
187 | ```
188 |
189 | ### Running in Production Mode
190 |
191 | If you provide the options `configFile` in your deploy options then a config file will be written:
192 |
193 | ```js
194 | assets.deploy(
195 | configFile: __dirname + '/rack.json',
196 | provider: 'amazon',
197 | container: ...
198 | )
199 | ```
200 |
201 | Then you can create your assets from the file like this:
202 |
203 | ```js
204 | assets = rack.fromConfigFile({
205 | configFile: __dirname + '/rack.json',
206 | hostname: 'cdn.example.com'
207 | });
208 | app.use(assets);
209 | ```
210 |
211 | And now all of your server side templates will reference your CDN. Also, if you do happen to hit one of your static urls on the server, then you will be redirected to the CDN.
212 |
213 | ## FAQ
214 |
215 | #### __Why is this better than Connect-Assets?__
216 |
217 | That's easy!
218 |
219 | * It works with node.js multi-process and cluster.
220 | * More built-in assets.
221 | * Un-opionated, connect-assets dictates your url structure AND directory structure.
222 | * Ability to deploy to the cloud.
223 | * Easy to extend.
224 | * Simpler to use.
225 |
226 | With all that said, much thanks to Trevor for writing connect-assets.
227 |
228 | #### __Why is this better than Grunt?__
229 |
230 | Grunt is a great build tool. Asset Rack is not a build a tool. It never writes files to disk, there is no "build step". Everything happens "just in time".
231 |
232 | If you have "genuine" build issues, then by all means use Grunt. You can even use Grunt with Asset Rack.
233 |
234 | However, if you are only using Grunt to manage your static assets, then you should consider upgrading to Asset Rack.
235 |
236 | #### __Why is this better than Wintersmith(Blacksmith)?__
237 |
238 | Asset Rack is a static web framework, and at it's core there are only two abstractions, the `Asset` and `Rack` classes. Wintersmith is a high level framework that solves a more specific problem.
239 |
240 | Wintersmith could consume Asset Rack as a dependency, and if something more high-level fits your specific use case, then by all means that is probably a good fit. If you need more flexibilty and power, then go with Asset Rack.
241 |
242 | # Changelog
243 |
244 | ### 2.2.1
245 |
246 | * A few small tweaks.
247 |
248 | ### 2.2.0
249 |
250 | * Watch and asset recreation is now working. This should be considered experimental for this version.
251 |
252 | ```js
253 | new StylusAsset({
254 | watch: true,
255 | ...
256 | });
257 | ```
258 |
259 | * Gzip is here finally.
260 |
261 | ```js
262 | new BrowserifyAsset({
263 | gzip: true,
264 | ...
265 | });
266 | ```
267 |
268 | * Now adding sub assets to an asset is much simpler, just use `addAsset`.
269 |
270 | ```js
271 | this.addAsset(asset);
272 | this.emit('created');
273 | ```
274 |
275 | Thanks @moellenbeck, @d1plo1d, @undashes, and @noc7c9 for contributing!
276 |
277 | ### 2.1.4
278 |
279 | * @vicapow Better error handling for `LessAsset`.
280 |
281 | ### 2.1.3
282 |
283 | * @noc7c9 Added generalized `rack.util.walk` function, need to document the function.
284 | * @noc7c9 Added `DynamicAssets` class.
285 | * @noc7c9 is awesome.
286 |
287 | ### 2.1.2
288 |
289 | * Added ability to configure Stylus, thanks to @noc7c9.
290 |
291 | ```coffee
292 | new StylusAsset
293 | url: '/style.css'
294 | filename: __dirname + '/style/fun.styl'
295 | config: ->
296 | @use bootstrap()
297 | @define 'setting', 90
298 | ```
299 |
300 | And for javascript:
301 |
302 | ```js
303 | new StylusAsset({
304 | url: '/style.css',
305 | filename: __dirname + '/style/fun.styl',
306 | config: function (stylus) {
307 | stylus // using "this" here seems a little unnatural
308 | .use(bootstrap())
309 | .define('setting', 90);
310 | }
311 | });
312 | ```
313 |
314 | # Test
315 |
316 | Testing is easy and fun!
317 |
318 | ```js
319 | cd asset-rack
320 | npm install
321 | npm test
322 | ```
323 |
324 | # License
325 |
326 | ©2012 Brad Carleton, Tech Pines and available under the [MIT license](http://www.opensource.org/licenses/mit-license.php):
327 |
328 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
329 |
330 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
331 |
332 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
333 |
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # API Reference
5 |
6 | ## Install
7 |
8 | ```bash
9 | npm install asset-rack
10 | ```
11 |
12 | ## Asset
13 |
14 | This is the base class from which all assets derive. It can represent both a single asset or a collection of assets.
15 |
16 | ```js
17 | asset = new Asset({
18 | url: '/fun.txt',
19 | contents: 'I am having fun!'
20 | })
21 | ```
22 |
23 | ### Use with Express
24 |
25 | Generally, you should wrap your assets in a rack, but for quick and dirty smaller projects you can just use the asset directly.
26 |
27 | ```
28 | app.use(asset);
29 | ```
30 |
31 | ### Options
32 | * `url`: The url where our resource will be served.
33 | * `contents`: The actual contents to be deliverd by the url.
34 | * `headers`: Any specific headers you want associated with this asset.
35 | * `mimetype`: The content type for the asset.
36 | * `hash`: (defaults to undefined) Serves both hashed and unhashed urls. If set to `true` then it only serves the hashed version, and if false then it only serves the unhashed version.
37 | * `watch`: (defaults to false) Watches for file changes and recreates assets.
38 | * `gzip`: (defaults to false) Whether to gzip the contents
39 | * `allowNoHashCache`: By default unhashed urls will not be cached. To allow them to be hashed, set this option to `true`.
40 | * `maxAge`: How long to cache the resource, defaults to 1 year for hashed urls, and no cache for unhashed urls.
41 | * `specificUrl`: The hashed version of the url.
42 | * `assets`: If the asset is actually a collection of assets, then this is where all of it's assets are.
43 |
44 | ### Methods
45 | * `tag()`: Returns the tag that should be used in HTML. (js and css assets only)
46 | * `respond(req,res)`: Given an express request and response object, this will respond with the contents and headers for the asset.
47 |
48 | ### Events
49 | * `complete`: Triggered once the asset is fully initialized with contents or assets, and has headers, hashed url etc.
50 | * `created`: Emitted when just the contents or assets have been created, before headers and hashing.
51 | * `error`: Emitted if there is an error with the asset.
52 |
53 | ### Extending the Asset Class
54 |
55 | It is easy to extend the base Asset class. The procedure for javascript is similar to that of Backbone.js. You must override the create method for your asset.
56 |
57 | ```js
58 | MyCoolAsset = rack.Asset.extend({
59 | create: function(options) {
60 | this.contents = 'hey there'
61 | this.emit 'created'
62 | }
63 | })
64 | ```
65 |
66 | In coffescript it is a little simpler:
67 |
68 | ```coffee
69 | class MyCoolAsset extends rack.Asset
70 | create: (options) ->
71 | @contents = 'yea!'
72 | @emit 'created'
73 | ```
74 |
75 | Whenever you finish creating your contents you emit a __created__ event.
76 |
77 | The options object passed to create is the same options object that gets passed to the constructor of new objects.
78 |
79 | ```coffee
80 | asset = new MyCoolAsset(options)
81 | ```
82 |
83 | You can also create create a collection of assets by extending the `Asset` class, but instead of setting the contents, you would set an array of assets.
84 |
85 | ```js
86 | LotsOfAssets = rack.Asset.extend({
87 | create: function(options) {
88 | this.assets = []
89 |
90 | // add assets to the collection
91 |
92 | this.emit('created')
93 | }
94 | })
95 | ```
96 |
97 | This is pretty self-explanatory, the only caveat is that you need to wait for the assets that you create to `complete` or else you will probably run into some strange behavior.
98 |
99 | ## Rack
100 |
101 | Manage your assets more easily with a rack.
102 |
103 | ```js
104 | new rack.Rack(assets)
105 | ```
106 | #### Options
107 |
108 | * `assets`: A collection of assets.
109 |
110 | #### Methods
111 | * `tag(url)`: Given a url, returns the tag that should be used in HTML.
112 | * `url(url)`: Get the hashed url from the unhashed url.
113 | * `deploy(options, callback)`: Deploy to the cloud see below.
114 |
115 | #### Events
116 |
117 | * `complete`: Emitted after all assets have been created.
118 | * `error`: Emitted for any errors.
119 |
120 | ### With Express
121 |
122 | ```javascript
123 | app.use(assets);
124 | ```
125 | __Important__: You have to call `app.use(assets)` before `app.use(app.router)` or else the `assets` markup functions will not be available in your templates. The assets middleware needs to come first.
126 |
127 | ### Deploying
128 |
129 | #### Amazon S3
130 |
131 | ```js
132 | assets.deploy({
133 | provider: 'amazon',
134 | container: 'some-bucket',
135 | accessKey: 'aws-access-key',
136 | secretKey: 'aws-secret-key',
137 | }, function(error) {})
138 | ```
139 |
140 | #### Rackspace Cloud Files
141 | ```js
142 | assets.deploy(
143 | provider: 'rackspace',
144 | container: 'some-container',
145 | username: 'rackspace-username',
146 | apiKey: 'rackspace-api-key',
147 | }, function(error) {})
148 | ```
149 |
150 | #### Azure Storage
151 | ```js
152 | assets.deploy(
153 | provider: 'azure',
154 | container: 'some-container',
155 | storageAccount: 'test-storage-account',
156 | storageAccessKey: 'test-storage-access-key'
157 | }, function(error) {})
158 | ```
159 |
160 | ## Javascript/Coffeescript
161 |
162 | ### BrowserifyAsset (js/coffeescript)
163 |
164 | Browserify is an awesome node project that converts node-style requires
165 | to requirejs for the frontend. For more details, check it out,
166 | [here](https://github.com/substack/node-browserify).
167 |
168 | ```javascript
169 | new BrowserifyAsset({
170 | url: '/app.js',
171 | filename: __dirname + '/client/app.js',
172 | compress: true
173 | });
174 | ```
175 |
176 | #### Options
177 |
178 | * `url`: The url that should retrieve this resource.
179 | * `filename`: A filename or list of filenames to be executed by the browser.
180 | * `require`: A filename or list of filenames to require, should not be necessary
181 | as the `filename` argument should pull in any requires you need.
182 | * `debug` (defaults to false): enables the browserify debug option.
183 | * `compress` (defaults to false): whether to run the javascript through a minifier.
184 | * `extensionHandlers` (defaults to []): an array of custom extensions and associated handler function. eg: `[{ ext: 'handlebars', handler: handlebarsCompilerFunction }]`
185 |
186 | ### SnocketsAsset (js/coffeescript)
187 |
188 | Snockets is a JavaScript/CoffeeScript concatenation tool for Node.js inspired by Sprockets. Used by connect-assets to create a Rails 3.1-style asset pipeline. For more details, check it out,
189 | [here](https://github.com/TrevorBurnham/snockets).
190 |
191 | ```javascript
192 | new SnocketsAsset({
193 | url: '/app.js',
194 | filename: __dirname + '/client/app.js',
195 | compress: true
196 | });
197 | ```
198 |
199 | #### Options
200 |
201 | * `url`: The url that should retrieve this resource.
202 | * `filename`: A filename or list of filenames to be executed by the browser.
203 | * `compress` (defaults to false): whether to run the javascript through a minifier.
204 | * `extensionHandlers` (defaults to []): an array of custom extensions and associated handler function. eg: `[{ ext: 'handlebars', handler: handlebarsCompilerFunction }]`
205 | * `debug` (defaults to false): output scripts via eval with trailing //@ sourceURL
206 |
207 |
208 | ## Stylesheets
209 |
210 | ### LessAsset
211 |
212 | The less asset basically compiles up and serves your less files as css. You
213 | can read more about less [here](https://github.com/cloudhead/less.js).
214 |
215 | ```javascript
216 | new LessAsset({
217 | url: '/style.css',
218 | filename: __dirname + '/style/app.less'
219 | });
220 | ```
221 |
222 | #### Options
223 |
224 | * `url`: The url that should retrieve this resource.
225 | * `filename`: Filename of the less file you want to serve.
226 | * `compress` (defaults to false): Whether to minify the css.
227 | * `paths`: List of paths to search for `@import` directives.
228 |
229 | ### StylusAsset
230 |
231 | The stylus asset serves up your stylus assets.
232 |
233 | ```javascript
234 | new StylusAsset({
235 | url: '/style.css',
236 | filename: __dirname + '/style/fun.styl'
237 | });
238 | ```
239 |
240 | #### Options
241 |
242 | * `url`: The url that should retrieve this resource.
243 | * `filename`: Filename of the stylus file you want to serve.
244 | * `compress` (defaults to false, or true in production mode): Whether to minify the css.
245 | * `config`: A function that allows custom configuration of the stylus object:
246 | ```coffee
247 | new StylusAsset
248 | url: '/style.css'
249 | filename: __dirname + '/style/fun.styl'
250 | config: ->
251 | @use bootstrap()
252 | @define 'setting', 90
253 | ```
254 |
255 | And javascript:
256 | ```js
257 | new StylusAsset({
258 | url: '/style.css',
259 | filename: __dirname + '/style/fun.styl',
260 | config: function (stylus) {
261 | stylus // using "this" here seems a little unnatural
262 | .use(bootstrap())
263 | .define('setting', 90);
264 | }
265 | });
266 | ```
267 |
268 |
269 | ## Templates
270 |
271 | ### JadeAsset
272 | This is an awesome asset. Ever wanted the simplicity of jade templates
273 | on the browser with lightning fast performance. Here you go.
274 |
275 | ```javascript
276 | new JadeAsset({
277 | url: '/templates.js',
278 | dirname: './templates'
279 | });
280 | ```
281 |
282 | So if your template directory looked like this:
283 |
284 | ```
285 | index.jade
286 | contact.jade
287 | user/
288 | profile.jade
289 | info.jade
290 | ```
291 |
292 | Then reference your templates on the client like this:
293 |
294 | ```javascript
295 | $('body').append(Templates['index']());
296 | $('body').append(Templates['user/profile']({username: 'brad', status: 'fun'}));
297 | $('body').append(Templates['user/info']());
298 | ```
299 | #### Options
300 |
301 | * `url`: The url that should retrieve this resource.
302 | * `dirname`: Directory where template files are located, will grab them recursively.
303 | * `separator` (defaults to '/'): The character that separates directories.
304 | * `compress` (defaults to false): Whether to minify the javascript or not.
305 | * `clientVariable` (defaults to 'Templates'): Client side template
306 | variable.
307 | * `beforeCompile`: A function that takes the jade template as a string and returns a new jade template string before it's compiled into javascript.
308 |
309 | ### AngularTemplatesAsset
310 |
311 | The angular templates asset packages all .html templates ready to be injected into the client side angularjs template cache.
312 | You can read more about angularjs [here](http://angularjs.org/).
313 |
314 | ```javascript
315 | new AngularTemplatesAsset({
316 | url: '/js/templates.js',
317 | dirname: __dirname + '/templates'
318 | });
319 | ```
320 |
321 | Then see the following example client js code which loads templates into the template cache, where `angularTemplates` is the function provided by AngularTemplatesAsset:
322 |
323 | ```javascript
324 | //replace this with your module initialization logic
325 | var myApp = angular.module("myApp", []);
326 |
327 | //use this line to add the templates to the cache
328 | myApp.run(['$templateCache', angularTemplates]);
329 | ```
330 |
331 | #### Options
332 |
333 | * `url`: The url that should retrieve this resource.
334 | * `dirname`: Directory where the .html templates are stored.
335 | * `compress` (defaults to false): Whether to unglify the js.
336 |
337 | ## Other
338 |
339 | ### StaticAssets
340 |
341 | ```js
342 | new StaticAssets({
343 | dirname: '/path/to/static'
344 | urlPrefix: '/static'
345 | })
346 | ```
347 |
348 | #### Options
349 | * `dirname`: The folder to recursively pull assets from.
350 | * `urlPrefix`: Base url where all assets will be available from.
351 |
352 | ### DynamicAssets
353 |
354 | ```js
355 | new DyanmicAssets({
356 | type: LessAsset
357 | urlPrefix: '/style'
358 | dirname: './style'
359 | })
360 | ```
361 |
362 | Then this would be the equivalent of going through every file in `/style` and doing this:
363 |
364 | ```js
365 | new LessAsset({
366 | filename: './style/some-file.less'
367 | url: '/style/some-file.css'
368 | })
369 | ```
370 |
371 | #### Options
372 | * `dirname`: The folder to recursively grab files from.
373 | * `type`: The type of Asset to use for each file.
374 | * `urlPrefix`: The url prefix to serve the assets from.
375 | * `options`: Other options to pass to the individual assets.
376 |
--------------------------------------------------------------------------------
/test/fixtures/browserify/app.min.js:
--------------------------------------------------------------------------------
1 | (function(){var r=function(e,t){var n=r.resolve(e,t||"/"),o=r.modules[n];if(!o)throw Error("Failed to resolve module "+e+", tried "+n);var a=r.cache[n],i=a?a.exports:o();return i};r.paths=[],r.modules={},r.cache={},r.extensions=[".js",".coffee",".json"],r._core={assert:!0,events:!0,fs:!0,path:!0,vm:!0},r.resolve=function(){return function(e,t){function n(e){if(e=s.normalize(e),r.modules[e])return e;for(var t=0;r.extensions.length>t;t++){var n=r.extensions[t];if(r.modules[e+n])return e+n}}function o(e){e=e.replace(/\/+$/,"");var t=s.normalize(e+"/package.json");if(r.modules[t]){var o=r.modules[t](),a=o.browserify;if("object"==typeof a&&a.main){var i=n(s.resolve(e,a.main));if(i)return i}else if("string"==typeof a){var i=n(s.resolve(e,a));if(i)return i}else if(o.main){var i=n(s.resolve(e,o.main));if(i)return i}}return n(e+"/index")}function a(r,e){for(var t=i(e),a=0;t.length>a;a++){var s=t[a],u=n(s+"/"+r);if(u)return u;var c=o(s+"/"+r);if(c)return c}var u=n(r);return u?u:void 0}function i(r){var e;e="/"===r?[""]:s.normalize(r).split("/");for(var t=[],n=e.length-1;n>=0;n--)if("node_modules"!==e[n]){var o=e.slice(0,n+1).join("/")+"/node_modules";t.push(o)}return t}if(t||(t="/"),r._core[e])return e;var s=r.modules.path();t=s.resolve("/",t);var u=t||"/";if(e.match(/^(?:\.\.?\/|\/)/)){var c=n(s.resolve(u,e))||o(s.resolve(u,e));if(c)return c}var f=a(e,u);if(f)return f;throw Error("Cannot find module '"+e+"'")}}(),r.alias=function(e,t){var n=r.modules.path(),o=null;try{o=r.resolve(e+"/package.json","/")}catch(a){o=r.resolve(e,"/")}for(var i=n.dirname(o),s=(Object.keys||function(r){var e=[];for(var t in r)e.push(t);return e})(r.modules),u=0;s.length>u;u++){var c=s[u];if(c.slice(0,i.length+1)===i+"/"){var f=c.slice(i.length);r.modules[t+f]=r.modules[i+f]}else c===i&&(r.modules[t]=r.modules[i])}},function(){var e={},t="undefined"!=typeof window?window:{},n=!1;r.define=function(o,a){!n&&r.modules.__browserify_process&&(e=r.modules.__browserify_process(),n=!0);var i=r._core[o]?"":r.modules.path().dirname(o),s=function(e){var t=r(e,i),n=r.cache[r.resolve(e,i)];return n&&null===n.parent&&(n.parent=u),t};s.resolve=function(e){return r.resolve(e,i)},s.modules=r.modules,s.define=r.define,s.cache=r.cache;var u={id:o,filename:o,exports:{},loaded:!1,parent:null};r.modules[o]=function(){return r.cache[o]=u,a.call(u.exports,s,u,u.exports,i,o,e,t),u.loaded=!0,u.exports}}}(),r.define("path",function(r,e,t,n,o,a){function i(r,e){for(var t=[],n=0;r.length>n;n++)e(r[n],n,r)&&t.push(r[n]);return t}function s(r,e){for(var t=0,n=r.length;n>=0;n--){var o=r[n];"."==o?r.splice(n,1):".."===o?(r.splice(n,1),t++):t&&(r.splice(n,1),t--)}if(e)for(;t--;t)r.unshift("..");return r}var u=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;t.resolve=function(){for(var r="",e=!1,t=arguments.length;t>=-1&&!e;t--){var n=t>=0?arguments[t]:a.cwd();"string"==typeof n&&n&&(r=n+"/"+r,e="/"===n.charAt(0))}return r=s(i(r.split("/"),function(r){return!!r}),!e).join("/"),(e?"/":"")+r||"."},t.normalize=function(r){var e="/"===r.charAt(0),t="/"===r.slice(-1);return r=s(i(r.split("/"),function(r){return!!r}),!e).join("/"),r||e||(r="."),r&&t&&(r+="/"),(e?"/":"")+r},t.join=function(){var r=Array.prototype.slice.call(arguments,0);return t.normalize(i(r,function(r){return r&&"string"==typeof r}).join("/"))},t.dirname=function(r){var e=u.exec(r)[1]||"",t=!1;return e?1===e.length||t&&3>=e.length&&":"===e.charAt(1)?e:e.substring(0,e.length-1):"."},t.basename=function(r,e){var t=u.exec(r)[2]||"";return e&&t.substr(-1*e.length)===e&&(t=t.substr(0,t.length-e.length)),t},t.extname=function(r){return u.exec(r)[3]||""},t.relative=function(r,e){function n(r){for(var e=0;r.length>e&&""===r[e];e++);for(var t=r.length-1;t>=0&&""===r[t];t--);return e>t?[]:r.slice(e,t-e+1)}r=t.resolve(r).substr(1),e=t.resolve(e).substr(1);for(var o=n(r.split("/")),a=n(e.split("/")),i=Math.min(o.length,a.length),s=i,u=0;i>u;u++)if(o[u]!==a[u]){s=u;break}for(var c=[],u=s;o.length>u;u++)c.push("..");return c=c.concat(a.slice(s)),c.join("/")}}),r.define("__browserify_process",function(r,e,t,n,o,a){var a=e.exports={};a.nextTick=function(){var r="undefined"!=typeof window&&window.setImmediate,e="undefined"!=typeof window&&window.postMessage&&window.addEventListener;if(r)return function(r){return window.setImmediate(r)};if(e){var t=[];return window.addEventListener("message",function(r){if(r.source===window&&"browserify-tick"===r.data&&(r.stopPropagation(),t.length>0)){var e=t.shift();e()}},!0),function(r){t.push(r),window.postMessage("browserify-tick","*")}}return function(r){setTimeout(r,0)}}(),a.title="browser",a.browser=!0,a.env={},a.argv=[],a.binding=function(e){if("evals"===e)return r("vm");throw Error("No such module. (Possibly not yet loaded)")},function(){var e,t="/";a.cwd=function(){return t},a.chdir=function(n){e||(e=r("path")),t=e.resolve(n,t)}}()}),r.define("crypto",function(r,e){e.exports=r("crypto-browserify")}),r.define("/node_modules/crypto-browserify/package.json",function(r,e){e.exports={}}),r.define("/node_modules/crypto-browserify/index.js",function(r,e,t){function n(){var r=[].slice.call(arguments).join(" ");throw Error([r,"we accept pull requests","http://github.com/dominictarr/crypto-browserify"].join("\n"))}var o=r("./sha"),a=r("./rng"),i=r("./md5"),s={sha1:{hex:o.hex_sha1,binary:o.b64_sha1,ascii:o.str_sha1},md5:{hex:i.hex_md5,binary:i.b64_md5,ascii:i.any_md5}};t.createHash=function(r){r=r||"sha1",s[r]||n("algorithm:",r,"is not yet supported");var e="",t=s[r];return{update:function(r){return e+=r,this},digest:function(o){o=o||"binary";var a;(a=t[o])||n("encoding:",o,"is not yet supported for algorithm",r);var i=a(e);return e=null,i}}},t.randomBytes=function(r,e){if(!e||!e.call)return a(r);try{e.call(this,void 0,a(r))}catch(t){e(t)}},["createCredentials","createHmac","createCypher","createCypheriv","createDecipher","createDecipheriv","createSign","createVerify","createDeffieHellman","pbkdf2"].forEach(function(r){t[r]=function(){n("sorry,",r,"is not implemented yet")}})}),r.define("/node_modules/crypto-browserify/sha.js",function(r,e,t){function n(r){return m(c(v(r),r.length*j))}function o(r){return y(c(v(r),r.length*j))}function a(r){return g(c(v(r),r.length*j))}function i(r,e){return m(h(r,e))}function s(r,e){return y(h(r,e))}function u(r,e){return g(h(r,e))}function c(r,e){r[e>>5]|=128<<24-e%32,r[(e+64>>9<<4)+15]=e;for(var t=Array(80),n=1732584193,o=-271733879,a=-1732584194,i=271733878,s=-1009589776,u=0;r.length>u;u+=16){for(var c=n,h=o,v=a,g=i,m=s,y=0;80>y;y++){t[y]=16>y?r[u+y]:p(t[y-3]^t[y-8]^t[y-14]^t[y-16],1);var w=d(d(p(n,5),f(y,o,a,i)),d(d(s,t[y]),l(y)));s=i,i=a,a=p(o,30),o=n,n=w}n=d(n,c),o=d(o,h),a=d(a,v),i=d(i,g),s=d(s,m)}return[n,o,a,i,s]}function f(r,e,t,n){return 20>r?e&t|~e&n:40>r?e^t^n:60>r?e&t|e&n|t&n:e^t^n}function l(r){return 20>r?1518500249:40>r?1859775393:60>r?-1894007588:-899497514}function h(r,e){var t=v(r);t.length>16&&(t=c(t,r.length*j));for(var n=Array(16),o=Array(16),a=0;16>a;a++)n[a]=909522486^t[a],o[a]=1549556828^t[a];var i=c(n.concat(v(e)),512+e.length*j);return c(o.concat(i),672)}function d(r,e){var t=(65535&r)+(65535&e),n=(r>>16)+(e>>16)+(t>>16);return n<<16|65535&t}function p(r,e){return r<>>32-e}function v(r){for(var e=[],t=(1<n;n+=j)e[n>>5]|=(r.charCodeAt(n/j)&t)<<32-j-n%32;return e}function g(r){for(var e="",t=(1<n;n+=j)e+=String.fromCharCode(r[n>>5]>>>32-j-n%32&t);return e}function m(r){for(var e=w?"0123456789ABCDEF":"0123456789abcdef",t="",n=0;4*r.length>n;n++)t+=e.charAt(15&r[n>>2]>>8*(3-n%4)+4)+e.charAt(15&r[n>>2]>>8*(3-n%4));return t}function y(r){for(var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",t="",n=0;4*r.length>n;n+=3)for(var o=(255&r[n>>2]>>8*(3-n%4))<<16|(255&r[n+1>>2]>>8*(3-(n+1)%4))<<8|255&r[n+2>>2]>>8*(3-(n+2)%4),a=0;4>a;a++)t+=8*n+6*a>32*r.length?b:e.charAt(63&o>>6*(3-a));return t}t.hex_sha1=n,t.b64_sha1=o,t.str_sha1=a,t.hex_hmac_sha1=i,t.b64_hmac_sha1=s,t.str_hmac_sha1=u;var w=0,b="",j=8}),r.define("/node_modules/crypto-browserify/rng.js",function(r,e){(function(){var r,t,n=this;if(r=function(r){for(var e,e,t=Array(r),n=0;r>n;n++)0==(3&n)&&(e=4294967296*Math.random()),t[n]=255&e>>>((3&n)<<3);return t},n.crypto&&crypto.getRandomValues){var o=new Uint32Array(4);t=function(r){var e=Array(r);crypto.getRandomValues(o);for(var t=0;r>t;t++)e[t]=255&o[t>>2]>>>8*(3&t);return e}}e.exports=t||r})()}),r.define("/node_modules/crypto-browserify/md5.js",function(r,e,t){function n(r){return s(i(f(r)))}function o(r){return u(i(f(r)))}function a(r,e){return c(i(f(r)),e)}function i(r){return h(d(l(r),8*r.length))}function s(r){try{}catch(e){j=0}for(var t,n=j?"0123456789ABCDEF":"0123456789abcdef",o="",a=0;r.length>a;a++)t=r.charCodeAt(a),o+=n.charAt(15&t>>>4)+n.charAt(15&t);return o}function u(r){try{}catch(e){A=""}for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n="",o=r.length,a=0;o>a;a+=3)for(var i=r.charCodeAt(a)<<16|(o>a+1?r.charCodeAt(a+1)<<8:0)|(o>a+2?r.charCodeAt(a+2):0),s=0;4>s;s++)n+=8*a+6*s>8*r.length?A:t.charAt(63&i>>>6*(3-s));return n}function c(r,e){var t,n,o,a,i,s=e.length,u=Array(Math.ceil(r.length/2));for(t=0;u.length>t;t++)u[t]=r.charCodeAt(2*t)<<8|r.charCodeAt(2*t+1);var c=Math.ceil(8*r.length/(Math.log(e.length)/Math.log(2))),f=Array(c);for(n=0;c>n;n++){for(i=[],a=0,t=0;u.length>t;t++)a=(a<<16)+u[t],o=Math.floor(a/s),a-=o*s,(i.length>0||o>0)&&(i[i.length]=o);f[n]=a,u=i}var l="";for(t=f.length-1;t>=0;t--)l+=e.charAt(f[t]);return l}function f(r){for(var e,t,n="",o=-1;++oo+1?r.charCodeAt(o+1):0,e>=55296&&56319>=e&&t>=56320&&57343>=t&&(e=65536+((1023&e)<<10)+(1023&t),o++),127>=e?n+=String.fromCharCode(e):2047>=e?n+=String.fromCharCode(192|31&e>>>6,128|63&e):65535>=e?n+=String.fromCharCode(224|15&e>>>12,128|63&e>>>6,128|63&e):2097151>=e&&(n+=String.fromCharCode(240|7&e>>>18,128|63&e>>>12,128|63&e>>>6,128|63&e));return n}function l(r){for(var e=Array(r.length>>2),t=0;e.length>t;t++)e[t]=0;for(var t=0;8*r.length>t;t+=8)e[t>>5]|=(255&r.charCodeAt(t/8))<t;t+=8)e+=String.fromCharCode(255&r[t>>5]>>>t%32);return e}function d(r,e){r[e>>5]|=128<>>9<<4)+14]=e;for(var t=1732584193,n=-271733879,o=-1732584194,a=271733878,i=0;r.length>i;i+=16){var s=t,u=n,c=o,f=a;t=v(t,n,o,a,r[i+0],7,-680876936),a=v(a,t,n,o,r[i+1],12,-389564586),o=v(o,a,t,n,r[i+2],17,606105819),n=v(n,o,a,t,r[i+3],22,-1044525330),t=v(t,n,o,a,r[i+4],7,-176418897),a=v(a,t,n,o,r[i+5],12,1200080426),o=v(o,a,t,n,r[i+6],17,-1473231341),n=v(n,o,a,t,r[i+7],22,-45705983),t=v(t,n,o,a,r[i+8],7,1770035416),a=v(a,t,n,o,r[i+9],12,-1958414417),o=v(o,a,t,n,r[i+10],17,-42063),n=v(n,o,a,t,r[i+11],22,-1990404162),t=v(t,n,o,a,r[i+12],7,1804603682),a=v(a,t,n,o,r[i+13],12,-40341101),o=v(o,a,t,n,r[i+14],17,-1502002290),n=v(n,o,a,t,r[i+15],22,1236535329),t=g(t,n,o,a,r[i+1],5,-165796510),a=g(a,t,n,o,r[i+6],9,-1069501632),o=g(o,a,t,n,r[i+11],14,643717713),n=g(n,o,a,t,r[i+0],20,-373897302),t=g(t,n,o,a,r[i+5],5,-701558691),a=g(a,t,n,o,r[i+10],9,38016083),o=g(o,a,t,n,r[i+15],14,-660478335),n=g(n,o,a,t,r[i+4],20,-405537848),t=g(t,n,o,a,r[i+9],5,568446438),a=g(a,t,n,o,r[i+14],9,-1019803690),o=g(o,a,t,n,r[i+3],14,-187363961),n=g(n,o,a,t,r[i+8],20,1163531501),t=g(t,n,o,a,r[i+13],5,-1444681467),a=g(a,t,n,o,r[i+2],9,-51403784),o=g(o,a,t,n,r[i+7],14,1735328473),n=g(n,o,a,t,r[i+12],20,-1926607734),t=m(t,n,o,a,r[i+5],4,-378558),a=m(a,t,n,o,r[i+8],11,-2022574463),o=m(o,a,t,n,r[i+11],16,1839030562),n=m(n,o,a,t,r[i+14],23,-35309556),t=m(t,n,o,a,r[i+1],4,-1530992060),a=m(a,t,n,o,r[i+4],11,1272893353),o=m(o,a,t,n,r[i+7],16,-155497632),n=m(n,o,a,t,r[i+10],23,-1094730640),t=m(t,n,o,a,r[i+13],4,681279174),a=m(a,t,n,o,r[i+0],11,-358537222),o=m(o,a,t,n,r[i+3],16,-722521979),n=m(n,o,a,t,r[i+6],23,76029189),t=m(t,n,o,a,r[i+9],4,-640364487),a=m(a,t,n,o,r[i+12],11,-421815835),o=m(o,a,t,n,r[i+15],16,530742520),n=m(n,o,a,t,r[i+2],23,-995338651),t=y(t,n,o,a,r[i+0],6,-198630844),a=y(a,t,n,o,r[i+7],10,1126891415),o=y(o,a,t,n,r[i+14],15,-1416354905),n=y(n,o,a,t,r[i+5],21,-57434055),t=y(t,n,o,a,r[i+12],6,1700485571),a=y(a,t,n,o,r[i+3],10,-1894986606),o=y(o,a,t,n,r[i+10],15,-1051523),n=y(n,o,a,t,r[i+1],21,-2054922799),t=y(t,n,o,a,r[i+8],6,1873313359),a=y(a,t,n,o,r[i+15],10,-30611744),o=y(o,a,t,n,r[i+6],15,-1560198380),n=y(n,o,a,t,r[i+13],21,1309151649),t=y(t,n,o,a,r[i+4],6,-145523070),a=y(a,t,n,o,r[i+11],10,-1120210379),o=y(o,a,t,n,r[i+2],15,718787259),n=y(n,o,a,t,r[i+9],21,-343485551),t=w(t,s),n=w(n,u),o=w(o,c),a=w(a,f)}return[t,n,o,a]}function p(r,e,t,n,o,a){return w(b(w(w(e,r),w(n,a)),o),t)}function v(r,e,t,n,o,a,i){return p(e&t|~e&n,r,e,o,a,i)}function g(r,e,t,n,o,a,i){return p(e&n|t&~n,r,e,o,a,i)}function m(r,e,t,n,o,a,i){return p(e^t^n,r,e,o,a,i)}function y(r,e,t,n,o,a,i){return p(t^(e|~n),r,e,o,a,i)}function w(r,e){var t=(65535&r)+(65535&e),n=(r>>16)+(e>>16)+(t>>16);return n<<16|65535&t}function b(r,e){return r<>>32-e}var j=0,A="";t.hex_md5=n,t.b64_md5=o,t.any_md5=a}),r.define("/app.coffee",function(r){(function(){var e,t,n;for(e=r("crypto"),t=n=0;12>=n;t=++n)window[t]=t}).call(this)}),r("/app.coffee")})();
--------------------------------------------------------------------------------
/test/fixtures/static/crazy-man.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
247 |
--------------------------------------------------------------------------------
/test/fixtures/browserify/app.js:
--------------------------------------------------------------------------------
1 | (function(){var require = function (file, cwd) {
2 | var resolved = require.resolve(file, cwd || '/');
3 | var mod = require.modules[resolved];
4 | if (!mod) throw new Error(
5 | 'Failed to resolve module ' + file + ', tried ' + resolved
6 | );
7 | var cached = require.cache[resolved];
8 | var res = cached? cached.exports : mod();
9 | return res;
10 | };
11 |
12 | require.paths = [];
13 | require.modules = {};
14 | require.cache = {};
15 | require.extensions = [".js",".coffee",".json"];
16 |
17 | require._core = {
18 | 'assert': true,
19 | 'events': true,
20 | 'fs': true,
21 | 'path': true,
22 | 'vm': true
23 | };
24 |
25 | require.resolve = (function () {
26 | return function (x, cwd) {
27 | if (!cwd) cwd = '/';
28 |
29 | if (require._core[x]) return x;
30 | var path = require.modules.path();
31 | cwd = path.resolve('/', cwd);
32 | var y = cwd || '/';
33 |
34 | if (x.match(/^(?:\.\.?\/|\/)/)) {
35 | var m = loadAsFileSync(path.resolve(y, x))
36 | || loadAsDirectorySync(path.resolve(y, x));
37 | if (m) return m;
38 | }
39 |
40 | var n = loadNodeModulesSync(x, y);
41 | if (n) return n;
42 |
43 | throw new Error("Cannot find module '" + x + "'");
44 |
45 | function loadAsFileSync (x) {
46 | x = path.normalize(x);
47 | if (require.modules[x]) {
48 | return x;
49 | }
50 |
51 | for (var i = 0; i < require.extensions.length; i++) {
52 | var ext = require.extensions[i];
53 | if (require.modules[x + ext]) return x + ext;
54 | }
55 | }
56 |
57 | function loadAsDirectorySync (x) {
58 | x = x.replace(/\/+$/, '');
59 | var pkgfile = path.normalize(x + '/package.json');
60 | if (require.modules[pkgfile]) {
61 | var pkg = require.modules[pkgfile]();
62 | var b = pkg.browserify;
63 | if (typeof b === 'object' && b.main) {
64 | var m = loadAsFileSync(path.resolve(x, b.main));
65 | if (m) return m;
66 | }
67 | else if (typeof b === 'string') {
68 | var m = loadAsFileSync(path.resolve(x, b));
69 | if (m) return m;
70 | }
71 | else if (pkg.main) {
72 | var m = loadAsFileSync(path.resolve(x, pkg.main));
73 | if (m) return m;
74 | }
75 | }
76 |
77 | return loadAsFileSync(x + '/index');
78 | }
79 |
80 | function loadNodeModulesSync (x, start) {
81 | var dirs = nodeModulesPathsSync(start);
82 | for (var i = 0; i < dirs.length; i++) {
83 | var dir = dirs[i];
84 | var m = loadAsFileSync(dir + '/' + x);
85 | if (m) return m;
86 | var n = loadAsDirectorySync(dir + '/' + x);
87 | if (n) return n;
88 | }
89 |
90 | var m = loadAsFileSync(x);
91 | if (m) return m;
92 | }
93 |
94 | function nodeModulesPathsSync (start) {
95 | var parts;
96 | if (start === '/') parts = [ '' ];
97 | else parts = path.normalize(start).split('/');
98 |
99 | var dirs = [];
100 | for (var i = parts.length - 1; i >= 0; i--) {
101 | if (parts[i] === 'node_modules') continue;
102 | var dir = parts.slice(0, i + 1).join('/') + '/node_modules';
103 | dirs.push(dir);
104 | }
105 |
106 | return dirs;
107 | }
108 | };
109 | })();
110 |
111 | require.alias = function (from, to) {
112 | var path = require.modules.path();
113 | var res = null;
114 | try {
115 | res = require.resolve(from + '/package.json', '/');
116 | }
117 | catch (err) {
118 | res = require.resolve(from, '/');
119 | }
120 | var basedir = path.dirname(res);
121 |
122 | var keys = (Object.keys || function (obj) {
123 | var res = [];
124 | for (var key in obj) res.push(key);
125 | return res;
126 | })(require.modules);
127 |
128 | for (var i = 0; i < keys.length; i++) {
129 | var key = keys[i];
130 | if (key.slice(0, basedir.length + 1) === basedir + '/') {
131 | var f = key.slice(basedir.length);
132 | require.modules[to + f] = require.modules[basedir + f];
133 | }
134 | else if (key === basedir) {
135 | require.modules[to] = require.modules[basedir];
136 | }
137 | }
138 | };
139 |
140 | (function () {
141 | var process = {};
142 | var global = typeof window !== 'undefined' ? window : {};
143 | var definedProcess = false;
144 |
145 | require.define = function (filename, fn) {
146 | if (!definedProcess && require.modules.__browserify_process) {
147 | process = require.modules.__browserify_process();
148 | definedProcess = true;
149 | }
150 |
151 | var dirname = require._core[filename]
152 | ? ''
153 | : require.modules.path().dirname(filename)
154 | ;
155 |
156 | var require_ = function (file) {
157 | var requiredModule = require(file, dirname);
158 | var cached = require.cache[require.resolve(file, dirname)];
159 |
160 | if (cached && cached.parent === null) {
161 | cached.parent = module_;
162 | }
163 |
164 | return requiredModule;
165 | };
166 | require_.resolve = function (name) {
167 | return require.resolve(name, dirname);
168 | };
169 | require_.modules = require.modules;
170 | require_.define = require.define;
171 | require_.cache = require.cache;
172 | var module_ = {
173 | id : filename,
174 | filename: filename,
175 | exports : {},
176 | loaded : false,
177 | parent: null
178 | };
179 |
180 | require.modules[filename] = function () {
181 | require.cache[filename] = module_;
182 | fn.call(
183 | module_.exports,
184 | require_,
185 | module_,
186 | module_.exports,
187 | dirname,
188 | filename,
189 | process,
190 | global
191 | );
192 | module_.loaded = true;
193 | return module_.exports;
194 | };
195 | };
196 | })();
197 |
198 |
199 | require.define("path",function(require,module,exports,__dirname,__filename,process,global){function filter (xs, fn) {
200 | var res = [];
201 | for (var i = 0; i < xs.length; i++) {
202 | if (fn(xs[i], i, xs)) res.push(xs[i]);
203 | }
204 | return res;
205 | }
206 |
207 | // resolves . and .. elements in a path array with directory names there
208 | // must be no slashes, empty elements, or device names (c:\) in the array
209 | // (so also no leading and trailing slashes - it does not distinguish
210 | // relative and absolute paths)
211 | function normalizeArray(parts, allowAboveRoot) {
212 | // if the path tries to go above the root, `up` ends up > 0
213 | var up = 0;
214 | for (var i = parts.length; i >= 0; i--) {
215 | var last = parts[i];
216 | if (last == '.') {
217 | parts.splice(i, 1);
218 | } else if (last === '..') {
219 | parts.splice(i, 1);
220 | up++;
221 | } else if (up) {
222 | parts.splice(i, 1);
223 | up--;
224 | }
225 | }
226 |
227 | // if the path is allowed to go above the root, restore leading ..s
228 | if (allowAboveRoot) {
229 | for (; up--; up) {
230 | parts.unshift('..');
231 | }
232 | }
233 |
234 | return parts;
235 | }
236 |
237 | // Regex to split a filename into [*, dir, basename, ext]
238 | // posix version
239 | var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
240 |
241 | // path.resolve([from ...], to)
242 | // posix version
243 | exports.resolve = function() {
244 | var resolvedPath = '',
245 | resolvedAbsolute = false;
246 |
247 | for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) {
248 | var path = (i >= 0)
249 | ? arguments[i]
250 | : process.cwd();
251 |
252 | // Skip empty and invalid entries
253 | if (typeof path !== 'string' || !path) {
254 | continue;
255 | }
256 |
257 | resolvedPath = path + '/' + resolvedPath;
258 | resolvedAbsolute = path.charAt(0) === '/';
259 | }
260 |
261 | // At this point the path should be resolved to a full absolute path, but
262 | // handle relative paths to be safe (might happen when process.cwd() fails)
263 |
264 | // Normalize the path
265 | resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
266 | return !!p;
267 | }), !resolvedAbsolute).join('/');
268 |
269 | return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
270 | };
271 |
272 | // path.normalize(path)
273 | // posix version
274 | exports.normalize = function(path) {
275 | var isAbsolute = path.charAt(0) === '/',
276 | trailingSlash = path.slice(-1) === '/';
277 |
278 | // Normalize the path
279 | path = normalizeArray(filter(path.split('/'), function(p) {
280 | return !!p;
281 | }), !isAbsolute).join('/');
282 |
283 | if (!path && !isAbsolute) {
284 | path = '.';
285 | }
286 | if (path && trailingSlash) {
287 | path += '/';
288 | }
289 |
290 | return (isAbsolute ? '/' : '') + path;
291 | };
292 |
293 |
294 | // posix version
295 | exports.join = function() {
296 | var paths = Array.prototype.slice.call(arguments, 0);
297 | return exports.normalize(filter(paths, function(p, index) {
298 | return p && typeof p === 'string';
299 | }).join('/'));
300 | };
301 |
302 |
303 | exports.dirname = function(path) {
304 | var dir = splitPathRe.exec(path)[1] || '';
305 | var isWindows = false;
306 | if (!dir) {
307 | // No dirname
308 | return '.';
309 | } else if (dir.length === 1 ||
310 | (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
311 | // It is just a slash or a drive letter with a slash
312 | return dir;
313 | } else {
314 | // It is a full dirname, strip trailing slash
315 | return dir.substring(0, dir.length - 1);
316 | }
317 | };
318 |
319 |
320 | exports.basename = function(path, ext) {
321 | var f = splitPathRe.exec(path)[2] || '';
322 | // TODO: make this comparison case-insensitive on windows?
323 | if (ext && f.substr(-1 * ext.length) === ext) {
324 | f = f.substr(0, f.length - ext.length);
325 | }
326 | return f;
327 | };
328 |
329 |
330 | exports.extname = function(path) {
331 | return splitPathRe.exec(path)[3] || '';
332 | };
333 |
334 | exports.relative = function(from, to) {
335 | from = exports.resolve(from).substr(1);
336 | to = exports.resolve(to).substr(1);
337 |
338 | function trim(arr) {
339 | var start = 0;
340 | for (; start < arr.length; start++) {
341 | if (arr[start] !== '') break;
342 | }
343 |
344 | var end = arr.length - 1;
345 | for (; end >= 0; end--) {
346 | if (arr[end] !== '') break;
347 | }
348 |
349 | if (start > end) return [];
350 | return arr.slice(start, end - start + 1);
351 | }
352 |
353 | var fromParts = trim(from.split('/'));
354 | var toParts = trim(to.split('/'));
355 |
356 | var length = Math.min(fromParts.length, toParts.length);
357 | var samePartsLength = length;
358 | for (var i = 0; i < length; i++) {
359 | if (fromParts[i] !== toParts[i]) {
360 | samePartsLength = i;
361 | break;
362 | }
363 | }
364 |
365 | var outputParts = [];
366 | for (var i = samePartsLength; i < fromParts.length; i++) {
367 | outputParts.push('..');
368 | }
369 |
370 | outputParts = outputParts.concat(toParts.slice(samePartsLength));
371 |
372 | return outputParts.join('/');
373 | };
374 |
375 | });
376 |
377 | require.define("__browserify_process",function(require,module,exports,__dirname,__filename,process,global){var process = module.exports = {};
378 |
379 | process.nextTick = (function () {
380 | var canSetImmediate = typeof window !== 'undefined'
381 | && window.setImmediate;
382 | var canPost = typeof window !== 'undefined'
383 | && window.postMessage && window.addEventListener
384 | ;
385 |
386 | if (canSetImmediate) {
387 | return function (f) { return window.setImmediate(f) };
388 | }
389 |
390 | if (canPost) {
391 | var queue = [];
392 | window.addEventListener('message', function (ev) {
393 | if (ev.source === window && ev.data === 'browserify-tick') {
394 | ev.stopPropagation();
395 | if (queue.length > 0) {
396 | var fn = queue.shift();
397 | fn();
398 | }
399 | }
400 | }, true);
401 |
402 | return function nextTick(fn) {
403 | queue.push(fn);
404 | window.postMessage('browserify-tick', '*');
405 | };
406 | }
407 |
408 | return function nextTick(fn) {
409 | setTimeout(fn, 0);
410 | };
411 | })();
412 |
413 | process.title = 'browser';
414 | process.browser = true;
415 | process.env = {};
416 | process.argv = [];
417 |
418 | process.binding = function (name) {
419 | if (name === 'evals') return (require)('vm')
420 | else throw new Error('No such module. (Possibly not yet loaded)')
421 | };
422 |
423 | (function () {
424 | var cwd = '/';
425 | var path;
426 | process.cwd = function () { return cwd };
427 | process.chdir = function (dir) {
428 | if (!path) path = require('path');
429 | cwd = path.resolve(dir, cwd);
430 | };
431 | })();
432 |
433 | });
434 |
435 | require.define("crypto",function(require,module,exports,__dirname,__filename,process,global){module.exports = require("crypto-browserify")
436 | });
437 |
438 | require.define("/node_modules/crypto-browserify/package.json",function(require,module,exports,__dirname,__filename,process,global){module.exports = {}
439 | });
440 |
441 | require.define("/node_modules/crypto-browserify/index.js",function(require,module,exports,__dirname,__filename,process,global){var sha = require('./sha')
442 | var rng = require('./rng')
443 | var md5 = require('./md5')
444 |
445 | var algorithms = {
446 | sha1: {
447 | hex: sha.hex_sha1,
448 | binary: sha.b64_sha1,
449 | ascii: sha.str_sha1
450 | },
451 | md5: {
452 | hex: md5.hex_md5,
453 | binary: md5.b64_md5,
454 | ascii: md5.any_md5
455 | }
456 | }
457 |
458 | function error () {
459 | var m = [].slice.call(arguments).join(' ')
460 | throw new Error([
461 | m,
462 | 'we accept pull requests',
463 | 'http://github.com/dominictarr/crypto-browserify'
464 | ].join('\n'))
465 | }
466 |
467 | exports.createHash = function (alg) {
468 | alg = alg || 'sha1'
469 | if(!algorithms[alg])
470 | error('algorithm:', alg, 'is not yet supported')
471 | var s = ''
472 | var _alg = algorithms[alg]
473 | return {
474 | update: function (data) {
475 | s += data
476 | return this
477 | },
478 | digest: function (enc) {
479 | enc = enc || 'binary'
480 | var fn
481 | if(!(fn = _alg[enc]))
482 | error('encoding:', enc , 'is not yet supported for algorithm', alg)
483 | var r = fn(s)
484 | s = null //not meant to use the hash after you've called digest.
485 | return r
486 | }
487 | }
488 | }
489 |
490 | exports.randomBytes = function(size, callback) {
491 | if (callback && callback.call) {
492 | try {
493 | callback.call(this, undefined, rng(size));
494 | } catch (err) { callback(err); }
495 | } else {
496 | return rng(size);
497 | }
498 | }
499 |
500 | // the least I can do is make error messages for the rest of the node.js/crypto api.
501 | ;['createCredentials'
502 | , 'createHmac'
503 | , 'createCypher'
504 | , 'createCypheriv'
505 | , 'createDecipher'
506 | , 'createDecipheriv'
507 | , 'createSign'
508 | , 'createVerify'
509 | , 'createDeffieHellman'
510 | , 'pbkdf2'].forEach(function (name) {
511 | exports[name] = function () {
512 | error('sorry,', name, 'is not implemented yet')
513 | }
514 | })
515 |
516 | });
517 |
518 | require.define("/node_modules/crypto-browserify/sha.js",function(require,module,exports,__dirname,__filename,process,global){/*
519 | * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
520 | * in FIPS PUB 180-1
521 | * Version 2.1a Copyright Paul Johnston 2000 - 2002.
522 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
523 | * Distributed under the BSD License
524 | * See http://pajhome.org.uk/crypt/md5 for details.
525 | */
526 |
527 | exports.hex_sha1 = hex_sha1;
528 | exports.b64_sha1 = b64_sha1;
529 | exports.str_sha1 = str_sha1;
530 | exports.hex_hmac_sha1 = hex_hmac_sha1;
531 | exports.b64_hmac_sha1 = b64_hmac_sha1;
532 | exports.str_hmac_sha1 = str_hmac_sha1;
533 |
534 | /*
535 | * Configurable variables. You may need to tweak these to be compatible with
536 | * the server-side, but the defaults work in most cases.
537 | */
538 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
539 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
540 | var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
541 |
542 | /*
543 | * These are the functions you'll usually want to call
544 | * They take string arguments and return either hex or base-64 encoded strings
545 | */
546 | function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
547 | function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
548 | function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
549 | function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
550 | function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
551 | function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
552 |
553 | /*
554 | * Perform a simple self-test to see if the VM is working
555 | */
556 | function sha1_vm_test()
557 | {
558 | return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
559 | }
560 |
561 | /*
562 | * Calculate the SHA-1 of an array of big-endian words, and a bit length
563 | */
564 | function core_sha1(x, len)
565 | {
566 | /* append padding */
567 | x[len >> 5] |= 0x80 << (24 - len % 32);
568 | x[((len + 64 >> 9) << 4) + 15] = len;
569 |
570 | var w = Array(80);
571 | var a = 1732584193;
572 | var b = -271733879;
573 | var c = -1732584194;
574 | var d = 271733878;
575 | var e = -1009589776;
576 |
577 | for(var i = 0; i < x.length; i += 16)
578 | {
579 | var olda = a;
580 | var oldb = b;
581 | var oldc = c;
582 | var oldd = d;
583 | var olde = e;
584 |
585 | for(var j = 0; j < 80; j++)
586 | {
587 | if(j < 16) w[j] = x[i + j];
588 | else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
589 | var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
590 | safe_add(safe_add(e, w[j]), sha1_kt(j)));
591 | e = d;
592 | d = c;
593 | c = rol(b, 30);
594 | b = a;
595 | a = t;
596 | }
597 |
598 | a = safe_add(a, olda);
599 | b = safe_add(b, oldb);
600 | c = safe_add(c, oldc);
601 | d = safe_add(d, oldd);
602 | e = safe_add(e, olde);
603 | }
604 | return Array(a, b, c, d, e);
605 |
606 | }
607 |
608 | /*
609 | * Perform the appropriate triplet combination function for the current
610 | * iteration
611 | */
612 | function sha1_ft(t, b, c, d)
613 | {
614 | if(t < 20) return (b & c) | ((~b) & d);
615 | if(t < 40) return b ^ c ^ d;
616 | if(t < 60) return (b & c) | (b & d) | (c & d);
617 | return b ^ c ^ d;
618 | }
619 |
620 | /*
621 | * Determine the appropriate additive constant for the current iteration
622 | */
623 | function sha1_kt(t)
624 | {
625 | return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
626 | (t < 60) ? -1894007588 : -899497514;
627 | }
628 |
629 | /*
630 | * Calculate the HMAC-SHA1 of a key and some data
631 | */
632 | function core_hmac_sha1(key, data)
633 | {
634 | var bkey = str2binb(key);
635 | if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
636 |
637 | var ipad = Array(16), opad = Array(16);
638 | for(var i = 0; i < 16; i++)
639 | {
640 | ipad[i] = bkey[i] ^ 0x36363636;
641 | opad[i] = bkey[i] ^ 0x5C5C5C5C;
642 | }
643 |
644 | var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
645 | return core_sha1(opad.concat(hash), 512 + 160);
646 | }
647 |
648 | /*
649 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally
650 | * to work around bugs in some JS interpreters.
651 | */
652 | function safe_add(x, y)
653 | {
654 | var lsw = (x & 0xFFFF) + (y & 0xFFFF);
655 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
656 | return (msw << 16) | (lsw & 0xFFFF);
657 | }
658 |
659 | /*
660 | * Bitwise rotate a 32-bit number to the left.
661 | */
662 | function rol(num, cnt)
663 | {
664 | return (num << cnt) | (num >>> (32 - cnt));
665 | }
666 |
667 | /*
668 | * Convert an 8-bit or 16-bit string to an array of big-endian words
669 | * In 8-bit function, characters >255 have their hi-byte silently ignored.
670 | */
671 | function str2binb(str)
672 | {
673 | var bin = Array();
674 | var mask = (1 << chrsz) - 1;
675 | for(var i = 0; i < str.length * chrsz; i += chrsz)
676 | bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
677 | return bin;
678 | }
679 |
680 | /*
681 | * Convert an array of big-endian words to a string
682 | */
683 | function binb2str(bin)
684 | {
685 | var str = "";
686 | var mask = (1 << chrsz) - 1;
687 | for(var i = 0; i < bin.length * 32; i += chrsz)
688 | str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
689 | return str;
690 | }
691 |
692 | /*
693 | * Convert an array of big-endian words to a hex string.
694 | */
695 | function binb2hex(binarray)
696 | {
697 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
698 | var str = "";
699 | for(var i = 0; i < binarray.length * 4; i++)
700 | {
701 | str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
702 | hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
703 | }
704 | return str;
705 | }
706 |
707 | /*
708 | * Convert an array of big-endian words to a base-64 string
709 | */
710 | function binb2b64(binarray)
711 | {
712 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
713 | var str = "";
714 | for(var i = 0; i < binarray.length * 4; i += 3)
715 | {
716 | var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
717 | | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
718 | | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
719 | for(var j = 0; j < 4; j++)
720 | {
721 | if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
722 | else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
723 | }
724 | }
725 | return str;
726 | }
727 |
728 |
729 | });
730 |
731 | require.define("/node_modules/crypto-browserify/rng.js",function(require,module,exports,__dirname,__filename,process,global){// Original code adapted from Robert Kieffer.
732 | // details at https://github.com/broofa/node-uuid
733 | (function() {
734 | var _global = this;
735 |
736 | var mathRNG, whatwgRNG;
737 |
738 | // NOTE: Math.random() does not guarantee "cryptographic quality"
739 | mathRNG = function(size) {
740 | var bytes = new Array(size);
741 | var r;
742 |
743 | for (var i = 0, r; i < size; i++) {
744 | if ((i & 0x03) == 0) r = Math.random() * 0x100000000;
745 | bytes[i] = r >>> ((i & 0x03) << 3) & 0xff;
746 | }
747 |
748 | return bytes;
749 | }
750 |
751 | // currently only available in webkit-based browsers.
752 | if (_global.crypto && crypto.getRandomValues) {
753 | var _rnds = new Uint32Array(4);
754 | whatwgRNG = function(size) {
755 | var bytes = new Array(size);
756 | crypto.getRandomValues(_rnds);
757 |
758 | for (var c = 0 ; c < size; c++) {
759 | bytes[c] = _rnds[c >> 2] >>> ((c & 0x03) * 8) & 0xff;
760 | }
761 | return bytes;
762 | }
763 | }
764 |
765 | module.exports = whatwgRNG || mathRNG;
766 |
767 | }())
768 | });
769 |
770 | require.define("/node_modules/crypto-browserify/md5.js",function(require,module,exports,__dirname,__filename,process,global){/*
771 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
772 | * Digest Algorithm, as defined in RFC 1321.
773 | * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
774 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
775 | * Distributed under the BSD License
776 | * See http://pajhome.org.uk/crypt/md5 for more info.
777 | */
778 |
779 | /*
780 | * Configurable variables. You may need to tweak these to be compatible with
781 | * the server-side, but the defaults work in most cases.
782 | */
783 | var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
784 | var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
785 |
786 | /*
787 | * These are the functions you'll usually want to call
788 | * They take string arguments and return either hex or base-64 encoded strings
789 | */
790 | function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
791 | function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
792 | function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
793 | function hex_hmac_md5(k, d)
794 | { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
795 | function b64_hmac_md5(k, d)
796 | { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
797 | function any_hmac_md5(k, d, e)
798 | { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
799 |
800 | /*
801 | * Perform a simple self-test to see if the VM is working
802 | */
803 | function md5_vm_test()
804 | {
805 | return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
806 | }
807 |
808 | /*
809 | * Calculate the MD5 of a raw string
810 | */
811 | function rstr_md5(s)
812 | {
813 | return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
814 | }
815 |
816 | /*
817 | * Calculate the HMAC-MD5, of a key and some data (raw strings)
818 | */
819 | function rstr_hmac_md5(key, data)
820 | {
821 | var bkey = rstr2binl(key);
822 | if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
823 |
824 | var ipad = Array(16), opad = Array(16);
825 | for(var i = 0; i < 16; i++)
826 | {
827 | ipad[i] = bkey[i] ^ 0x36363636;
828 | opad[i] = bkey[i] ^ 0x5C5C5C5C;
829 | }
830 |
831 | var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
832 | return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
833 | }
834 |
835 | /*
836 | * Convert a raw string to a hex string
837 | */
838 | function rstr2hex(input)
839 | {
840 | try { hexcase } catch(e) { hexcase=0; }
841 | var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
842 | var output = "";
843 | var x;
844 | for(var i = 0; i < input.length; i++)
845 | {
846 | x = input.charCodeAt(i);
847 | output += hex_tab.charAt((x >>> 4) & 0x0F)
848 | + hex_tab.charAt( x & 0x0F);
849 | }
850 | return output;
851 | }
852 |
853 | /*
854 | * Convert a raw string to a base-64 string
855 | */
856 | function rstr2b64(input)
857 | {
858 | try { b64pad } catch(e) { b64pad=''; }
859 | var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
860 | var output = "";
861 | var len = input.length;
862 | for(var i = 0; i < len; i += 3)
863 | {
864 | var triplet = (input.charCodeAt(i) << 16)
865 | | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
866 | | (i + 2 < len ? input.charCodeAt(i+2) : 0);
867 | for(var j = 0; j < 4; j++)
868 | {
869 | if(i * 8 + j * 6 > input.length * 8) output += b64pad;
870 | else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
871 | }
872 | }
873 | return output;
874 | }
875 |
876 | /*
877 | * Convert a raw string to an arbitrary string encoding
878 | */
879 | function rstr2any(input, encoding)
880 | {
881 | var divisor = encoding.length;
882 | var i, j, q, x, quotient;
883 |
884 | /* Convert to an array of 16-bit big-endian values, forming the dividend */
885 | var dividend = Array(Math.ceil(input.length / 2));
886 | for(i = 0; i < dividend.length; i++)
887 | {
888 | dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
889 | }
890 |
891 | /*
892 | * Repeatedly perform a long division. The binary array forms the dividend,
893 | * the length of the encoding is the divisor. Once computed, the quotient
894 | * forms the dividend for the next step. All remainders are stored for later
895 | * use.
896 | */
897 | var full_length = Math.ceil(input.length * 8 /
898 | (Math.log(encoding.length) / Math.log(2)));
899 | var remainders = Array(full_length);
900 | for(j = 0; j < full_length; j++)
901 | {
902 | quotient = Array();
903 | x = 0;
904 | for(i = 0; i < dividend.length; i++)
905 | {
906 | x = (x << 16) + dividend[i];
907 | q = Math.floor(x / divisor);
908 | x -= q * divisor;
909 | if(quotient.length > 0 || q > 0)
910 | quotient[quotient.length] = q;
911 | }
912 | remainders[j] = x;
913 | dividend = quotient;
914 | }
915 |
916 | /* Convert the remainders to the output string */
917 | var output = "";
918 | for(i = remainders.length - 1; i >= 0; i--)
919 | output += encoding.charAt(remainders[i]);
920 |
921 | return output;
922 | }
923 |
924 | /*
925 | * Encode a string as utf-8.
926 | * For efficiency, this assumes the input is valid utf-16.
927 | */
928 | function str2rstr_utf8(input)
929 | {
930 | var output = "";
931 | var i = -1;
932 | var x, y;
933 |
934 | while(++i < input.length)
935 | {
936 | /* Decode utf-16 surrogate pairs */
937 | x = input.charCodeAt(i);
938 | y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
939 | if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
940 | {
941 | x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
942 | i++;
943 | }
944 |
945 | /* Encode output as utf-8 */
946 | if(x <= 0x7F)
947 | output += String.fromCharCode(x);
948 | else if(x <= 0x7FF)
949 | output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
950 | 0x80 | ( x & 0x3F));
951 | else if(x <= 0xFFFF)
952 | output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
953 | 0x80 | ((x >>> 6 ) & 0x3F),
954 | 0x80 | ( x & 0x3F));
955 | else if(x <= 0x1FFFFF)
956 | output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
957 | 0x80 | ((x >>> 12) & 0x3F),
958 | 0x80 | ((x >>> 6 ) & 0x3F),
959 | 0x80 | ( x & 0x3F));
960 | }
961 | return output;
962 | }
963 |
964 | /*
965 | * Encode a string as utf-16
966 | */
967 | function str2rstr_utf16le(input)
968 | {
969 | var output = "";
970 | for(var i = 0; i < input.length; i++)
971 | output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
972 | (input.charCodeAt(i) >>> 8) & 0xFF);
973 | return output;
974 | }
975 |
976 | function str2rstr_utf16be(input)
977 | {
978 | var output = "";
979 | for(var i = 0; i < input.length; i++)
980 | output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
981 | input.charCodeAt(i) & 0xFF);
982 | return output;
983 | }
984 |
985 | /*
986 | * Convert a raw string to an array of little-endian words
987 | * Characters >255 have their high-byte silently ignored.
988 | */
989 | function rstr2binl(input)
990 | {
991 | var output = Array(input.length >> 2);
992 | for(var i = 0; i < output.length; i++)
993 | output[i] = 0;
994 | for(var i = 0; i < input.length * 8; i += 8)
995 | output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
996 | return output;
997 | }
998 |
999 | /*
1000 | * Convert an array of little-endian words to a string
1001 | */
1002 | function binl2rstr(input)
1003 | {
1004 | var output = "";
1005 | for(var i = 0; i < input.length * 32; i += 8)
1006 | output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
1007 | return output;
1008 | }
1009 |
1010 | /*
1011 | * Calculate the MD5 of an array of little-endian words, and a bit length.
1012 | */
1013 | function binl_md5(x, len)
1014 | {
1015 | /* append padding */
1016 | x[len >> 5] |= 0x80 << ((len) % 32);
1017 | x[(((len + 64) >>> 9) << 4) + 14] = len;
1018 |
1019 | var a = 1732584193;
1020 | var b = -271733879;
1021 | var c = -1732584194;
1022 | var d = 271733878;
1023 |
1024 | for(var i = 0; i < x.length; i += 16)
1025 | {
1026 | var olda = a;
1027 | var oldb = b;
1028 | var oldc = c;
1029 | var oldd = d;
1030 |
1031 | a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
1032 | d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
1033 | c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
1034 | b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
1035 | a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
1036 | d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
1037 | c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
1038 | b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
1039 | a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
1040 | d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
1041 | c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
1042 | b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
1043 | a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
1044 | d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
1045 | c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
1046 | b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
1047 |
1048 | a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
1049 | d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
1050 | c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
1051 | b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
1052 | a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
1053 | d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
1054 | c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
1055 | b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
1056 | a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
1057 | d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
1058 | c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
1059 | b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
1060 | a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
1061 | d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
1062 | c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
1063 | b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
1064 |
1065 | a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
1066 | d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
1067 | c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
1068 | b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
1069 | a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
1070 | d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
1071 | c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
1072 | b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
1073 | a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
1074 | d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
1075 | c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
1076 | b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
1077 | a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
1078 | d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
1079 | c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
1080 | b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
1081 |
1082 | a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
1083 | d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
1084 | c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
1085 | b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
1086 | a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
1087 | d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
1088 | c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
1089 | b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
1090 | a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
1091 | d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
1092 | c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
1093 | b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
1094 | a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
1095 | d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
1096 | c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
1097 | b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
1098 |
1099 | a = safe_add(a, olda);
1100 | b = safe_add(b, oldb);
1101 | c = safe_add(c, oldc);
1102 | d = safe_add(d, oldd);
1103 | }
1104 | return Array(a, b, c, d);
1105 | }
1106 |
1107 | /*
1108 | * These functions implement the four basic operations the algorithm uses.
1109 | */
1110 | function md5_cmn(q, a, b, x, s, t)
1111 | {
1112 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
1113 | }
1114 | function md5_ff(a, b, c, d, x, s, t)
1115 | {
1116 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
1117 | }
1118 | function md5_gg(a, b, c, d, x, s, t)
1119 | {
1120 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
1121 | }
1122 | function md5_hh(a, b, c, d, x, s, t)
1123 | {
1124 | return md5_cmn(b ^ c ^ d, a, b, x, s, t);
1125 | }
1126 | function md5_ii(a, b, c, d, x, s, t)
1127 | {
1128 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
1129 | }
1130 |
1131 | /*
1132 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally
1133 | * to work around bugs in some JS interpreters.
1134 | */
1135 | function safe_add(x, y)
1136 | {
1137 | var lsw = (x & 0xFFFF) + (y & 0xFFFF);
1138 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
1139 | return (msw << 16) | (lsw & 0xFFFF);
1140 | }
1141 |
1142 | /*
1143 | * Bitwise rotate a 32-bit number to the left.
1144 | */
1145 | function bit_rol(num, cnt)
1146 | {
1147 | return (num << cnt) | (num >>> (32 - cnt));
1148 | }
1149 |
1150 |
1151 | exports.hex_md5 = hex_md5;
1152 | exports.b64_md5 = b64_md5;
1153 | exports.any_md5 = any_md5;
1154 |
1155 | });
1156 |
1157 | require.define("/app.coffee",function(require,module,exports,__dirname,__filename,process,global){(function() {
1158 | var crypto, index, _i;
1159 |
1160 | crypto = require('crypto');
1161 |
1162 | for (index = _i = 0; _i <= 12; index = ++_i) {
1163 | window[index] = index;
1164 | }
1165 |
1166 | }).call(this);
1167 |
1168 | });
1169 | require("/app.coffee");
1170 | })();
1171 |
--------------------------------------------------------------------------------