├── .gitignore ├── examples ├── style │ ├── link.css │ ├── import.css │ └── link.less ├── turtle.jpg ├── helloworld.jsx ├── index.html ├── less.html ├── prefixfree.html ├── processImage.html ├── processImage.css └── js │ ├── prefixfree.min.js │ └── less-1.1.5.min.js ├── test ├── proxy │ ├── ba r │ │ └── hello.txt │ ├── fo o │ │ └── hello.txt │ ├── noheadnoendhtml.html │ ├── nohead.html │ ├── subdir │ │ └── index.html │ ├── index.html │ └── earlyscript.html ├── nonProxy │ ├── ba r │ │ └── hello.txt │ ├── fo o │ │ └── hello.txt │ ├── style sheet.css │ └── index.html ├── middlewares │ ├── main.jsx │ ├── main.css │ ├── main.less │ ├── main.scss │ └── index.html ├── compilessAutoprefixer │ └── test.less ├── createLiveStyleTestServer.js ├── proxy-test.js └── nonProxy-test.js ├── .travis.yml ├── css.js ├── LICENSE ├── lib ├── middleware │ ├── bufferDataEventsUntilFirstListener.js │ └── injectLiveStyleScriptIncludeIntoHtml.js ├── createLiveStyleApp.js └── installLiveCssFileWatcherInServer.js ├── package.json ├── less.js ├── bin └── livestyle ├── README.md └── livestyle-client.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /examples/style/link.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /test/proxy/ba r/hello.txt: -------------------------------------------------------------------------------- 1 | The contents of /ba r/hello.txt 2 | -------------------------------------------------------------------------------- /test/proxy/fo o/hello.txt: -------------------------------------------------------------------------------- 1 | The contents of /foo/hello.txt 2 | -------------------------------------------------------------------------------- /test/nonProxy/ba r/hello.txt: -------------------------------------------------------------------------------- 1 | The contents of /ba r/hello.txt 2 | -------------------------------------------------------------------------------- /test/nonProxy/fo o/hello.txt: -------------------------------------------------------------------------------- 1 | The contents of /fo o/hello.txt 2 | -------------------------------------------------------------------------------- /examples/style/import.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: black; 3 | } 4 | -------------------------------------------------------------------------------- /test/nonProxy/style sheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: red; 3 | } 4 | -------------------------------------------------------------------------------- /examples/style/link.less: -------------------------------------------------------------------------------- 1 | @color: #FFF; 2 | 3 | h1 { 4 | color: @color; 5 | } 6 | -------------------------------------------------------------------------------- /test/middlewares/main.jsx: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | return 'module'; 3 | }); 4 | -------------------------------------------------------------------------------- /examples/turtle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/podio/podio-livestyle/master/examples/turtle.jpg -------------------------------------------------------------------------------- /test/middlewares/main.css: -------------------------------------------------------------------------------- 1 | .css { 2 | background: red; 3 | 4 | transform: translateZ(2); 5 | } 6 | -------------------------------------------------------------------------------- /test/proxy/noheadnoendhtml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello, upstream world! 5 | 6 | -------------------------------------------------------------------------------- /test/proxy/nohead.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello, upstream world! 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/helloworld.jsx: -------------------------------------------------------------------------------- 1 | React.renderComponent( 2 |

Hello, world!

, 3 | document.getElementById('example') 4 | ); 5 | -------------------------------------------------------------------------------- /test/middlewares/main.less: -------------------------------------------------------------------------------- 1 | @color: green; 2 | 3 | .less { 4 | background: @color; 5 | 6 | transform: translateZ(2); 7 | } 8 | -------------------------------------------------------------------------------- /test/middlewares/main.scss: -------------------------------------------------------------------------------- 1 | $color: blue; 2 | 3 | .scss { 4 | background: $color; 5 | 6 | transform: translateZ(2); 7 | } 8 | -------------------------------------------------------------------------------- /test/proxy/subdir/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is the index file of /subdir/ 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/compilessAutoprefixer/test.less: -------------------------------------------------------------------------------- 1 | .nonNested { 2 | animation-name: test; 3 | } 4 | 5 | .nested { 6 | .deep { 7 | animation-name: test; 8 | } 9 | } -------------------------------------------------------------------------------- /test/nonProxy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello, world! 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/proxy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Hello, upstream world! 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/proxy/earlyscript.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "4" 5 | - "6" 6 | - "8" 7 | 8 | env: 9 | - CXX=g++-4.8 10 | 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - g++-4.8 17 | 18 | script: "npm run-script travis" 19 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test page for Livestyle 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 |

My god, fix these colors ASAP!

17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test/middlewares/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

CSS

16 |

SCSS

17 |

LESS

18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/less.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test page for Livestyle 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

My god, fix these colors ASAP!

19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/prefixfree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test page for Livestyle 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

My god, fix these colors ASAP!

19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/processImage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | express-processimages example 5 | 6 | 7 | 8 |

Launch livestyle --processimage and play with the 9 | processing instructions in the image urls 10 | in processImage.css

11 | 12 |
13 |
14 |
15 |
16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/processImage.css: -------------------------------------------------------------------------------- 1 | .turtle { 2 | float: left; 3 | width: 481px; 4 | height: 424px; 5 | } 6 | 7 | .first { 8 | background-image: url(turtle.jpg); 9 | } 10 | 11 | .second { 12 | background-image: url(turtle.jpg?jpegtran=-flip,horizontal); 13 | } 14 | 15 | .third { 16 | background-image: url(turtle.jpg?jpegtran=-grayscale); 17 | } 18 | 19 | .fourth { 20 | background-image: url(turtle.jpg?setFormat=png&pngquant=16); 21 | } 22 | 23 | .thesvg { 24 | float: left; 25 | border: 1px solid red; 26 | width: 481px; 27 | height: 424px; 28 | background-image: url(network-error.svg?svgfilter=--runScript,--stopColor=beige&inkscape=--export-png); 29 | background-color: red; 30 | } 31 | -------------------------------------------------------------------------------- /test/createLiveStyleTestServer.js: -------------------------------------------------------------------------------- 1 | var createLiveStyleApp = require('../lib/createLiveStyleApp'); 2 | 3 | module.exports = function createLiveStyleTestServer(options) { 4 | var app = createLiveStyleApp(options); 5 | 6 | options.silentSocketIo = true; 7 | 8 | // Listen on a vacant TCP port and hand back the url + app 9 | var server = app.listen(0); 10 | 11 | require('../lib/installLiveCssFileWatcherInServer')(server, options, require('socket.io')); 12 | 13 | var address = server.address(); 14 | return { 15 | hostname: address.address, 16 | port: address.port, 17 | host: address.address + ':' + address.port, 18 | url: 'http://' + address.address + ':' + address.port, 19 | app: app 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /css.js: -------------------------------------------------------------------------------- 1 | define({ 2 | load: function (name, req, load, config, map) { 3 | var url = name, 4 | path = url.split('/'), 5 | linkElement = document.createElement('link'), 6 | headElement = document.getElementsByTagName('head')[0], 7 | parentModuleName = map && map.parentMap && map.parentMap.name, 8 | parentScriptElement, 9 | i, 10 | childNode; 11 | 12 | linkElement.setAttribute('href', url); 13 | linkElement.setAttribute('rel', 'stylesheet'); 14 | 15 | if (parentModuleName) { 16 | for (i = 0 ; i < headElement.childNodes.length ; i += 1) { 17 | childNode = headElement.childNodes[i]; 18 | if (childNode.nodeType === 1 && childNode.nodeName.toLowerCase() === 'script' && childNode.getAttribute('data-requiremodule') === parentModuleName) { 19 | parentScriptElement = childNode; 20 | break; 21 | } 22 | } 23 | } 24 | 25 | if (parentScriptElement) { 26 | headElement.insertBefore(linkElement, parentScriptElement); 27 | } else { 28 | headElement.appendChild(linkElement); 29 | } 30 | load(); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Peter Müller and Andreas Lind Petersen 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of the author nor the names of contributors may 15 | be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 19 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /lib/middleware/bufferDataEventsUntilFirstListener.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware that buffers up the request's 'data' and 'end' events 3 | * until another 'data' listener is added, then replay the events in 4 | * order. 5 | * 6 | * Intended for use with formidable in scenarios where the IncomingForm 7 | * is initialized in a route after something async has happened 8 | * (authentication via a socket, for instance). 9 | */ 10 | 11 | function createEventBufferer(eventName, bufferedEvents) { 12 | return function () { // ... 13 | bufferedEvents.push([eventName].concat(Array.prototype.slice.call(arguments))); 14 | }; 15 | } 16 | 17 | var eventNamesToBuffer = ['data', 'end']; 18 | 19 | module.exports = function () { 20 | return function bufferDataEventsUntilFirstListener(req, res, next) { 21 | var eventBufferersByEventName = {}, 22 | bufferedEvents = []; 23 | eventNamesToBuffer.forEach(function (eventName) { 24 | var eventBufferer = createEventBufferer(eventName, bufferedEvents); 25 | req.on(eventName, eventBufferer); 26 | eventBufferersByEventName[eventName] = eventBufferer; 27 | }); 28 | req.on('newListener', function (name, listener) { 29 | if (name === 'data') { 30 | eventNamesToBuffer.forEach(function (eventName) { 31 | req.removeListener(eventName, eventBufferersByEventName[eventName]); 32 | }); 33 | process.nextTick(function () { 34 | bufferedEvents.forEach(function (bufferedEvent) { 35 | req.emit.apply(req, bufferedEvent); 36 | }); 37 | }); 38 | } 39 | }); 40 | next(); 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributors": [ 3 | { 4 | "name": "Peter Müller", 5 | "email": "munter@fumle.dk" 6 | }, 7 | { 8 | "name": "Andreas Lind", 9 | "email": "andreas@one.com" 10 | }, 11 | { 12 | "name": "Gustav Nikolaj Olsen", 13 | "email": "gustavnikolaj@gmail.com" 14 | } 15 | ], 16 | "name": "livestyle", 17 | "description": "NodeJS middleware and binary for setting up a webserver that notifies the browser of CSS updates. Polling fallback for pure client side functionality.", 18 | "version": "3.0.0", 19 | "homepage": "https://github.com/One-com/livestyle", 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/One-com/livestyle.git" 23 | }, 24 | "engines": { 25 | "node": ">= 0.10.0" 26 | }, 27 | "dependencies": { 28 | "errorhandler": "1.4.2", 29 | "express": "4.13.3", 30 | "express-autoprefixer": "4.0.3", 31 | "express-compile-sass": "^4.0.0", 32 | "express-compiless": "3.0.4", 33 | "express-jsxtransform": "4.0.2", 34 | "express-processimage": "10.2.3", 35 | "hijackresponse": "1.0.1", 36 | "morgan": "1.6.1", 37 | "optimist": "0.6.1", 38 | "request": "2.9.3", 39 | "socket.io": "2.0.4", 40 | "socket.io-client": "2.0.4" 41 | }, 42 | "directories": { 43 | "bin": "./bin" 44 | }, 45 | "main": "./lib/createLiveStyleApp.js", 46 | "devDependencies": { 47 | "autoprefixer": "^6.0.0", 48 | "mocha": "5.0.0", 49 | "node-sass": "^4.7.2", 50 | "postcss": "^5.0.0", 51 | "react-tools": "0.13.3", 52 | "unexpected": "10.37.2", 53 | "unexpected-express": "10.0.0" 54 | }, 55 | "scripts": { 56 | "test": "mocha --exit", 57 | "travis": "mocha --exit -R spec" 58 | }, 59 | "files": [ 60 | "livestyle-client.js", 61 | "css.js", 62 | "less.js", 63 | "lib", 64 | "bin" 65 | ], 66 | "preferGlobal": true, 67 | "license": "BSD-3-Clause" 68 | } 69 | -------------------------------------------------------------------------------- /less.js: -------------------------------------------------------------------------------- 1 | /*global less, console*/ 2 | define([ 3 | 'css' 4 | ], function (css) { 5 | var lessLoader = { 6 | load: function (name, req, load, config) { 7 | var url = name + (name.indexOf('.less') === -1 ? '.less' : ''), 8 | path = url.split('/'); 9 | 10 | path.pop(); 11 | if (path.length !== 0) { 12 | path = '/' + path.join('/') + '/'; 13 | } 14 | 15 | $.ajax({ 16 | url: url, 17 | dataType: 'text', 18 | success: function (data) { 19 | var parser = new less.Parser({ 20 | paths: [path] 21 | }); 22 | 23 | parser.parse(data, function (e, tree) { 24 | var style = null, 25 | css = null, 26 | lessLink; 27 | 28 | if (e) { 29 | console.error(e); 30 | } else { 31 | style = document.createElement('style'); 32 | style.type = "text/css"; 33 | style.id = 'less:' + url.replace(/\//g, '-').replace('.less', ''); 34 | 35 | css = tree.toCSS(); 36 | 37 | if (style.styleSheet) { 38 | style.styleSheet.cssText = css; 39 | } else { 40 | style.innerHTML = css; 41 | } 42 | document.getElementsByTagName('head')[0].appendChild(style); 43 | 44 | lessLink = document.createElement('link'); 45 | lessLink.rel = 'stylesheet/less'; 46 | lessLink.type = 'text/css'; 47 | lessLink.href = url; 48 | document.getElementsByTagName('head')[0].appendChild(lessLink); 49 | less.sheets.push(lessLink); 50 | 51 | load(css); 52 | } 53 | }); 54 | } 55 | }); 56 | } 57 | }; 58 | 59 | if (window.liveStyle && window.liveStyle.compiless) { 60 | lessLoader = css; 61 | } 62 | 63 | return lessLoader; 64 | }); 65 | -------------------------------------------------------------------------------- /examples/js/prefixfree.min.js: -------------------------------------------------------------------------------- 1 | // StyleFix 1.0.1 & PrefixFree 1.0.4 / by Lea Verou / MIT license 2 | (function(){function h(a,b){return[].slice.call((b||document).querySelectorAll(a))}if(window.addEventListener){var b=window.StyleFix={link:function(a){try{if(!/\bstylesheet\b/i.test(a.rel)||!a.sheet.cssRules)return}catch(c){return}var d=a.href||a.getAttribute("data-href"),f=d.replace(/[^\/]+$/,""),g=a.parentNode,e=new XMLHttpRequest;e.open("GET",d);e.onreadystatechange=function(){if(4===e.readyState){var c=e.responseText;if(c&&a.parentNode){c=b.fix(c,!0,a);f&&(c=c.replace(/url\((?:'|")?(.+?)(?:'|")?\)/gi, 3 | function(a,c){return!/^([a-z]{3,10}:|\/|#)/i.test(c)?'url("'+f+c+'")':a}),c=c.replace(RegExp("\\b(behavior:\\s*?url\\('?\"?)"+f,"gi"),"$1"));var d=document.createElement("style");d.textContent=c;d.media=a.media;d.disabled=a.disabled;d.setAttribute("data-href",a.getAttribute("href"));g.insertBefore(d,a);g.removeChild(a)}}};e.send(null);a.setAttribute("data-inprogress","")},styleElement:function(a){var c=a.disabled;a.textContent=b.fix(a.textContent,!0,a);a.disabled=c},styleAttribute:function(a){var c= 4 | a.getAttribute("style"),c=b.fix(c,!1,a);a.setAttribute("style",c)},process:function(){h('link[rel~="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link);h("style").forEach(StyleFix.styleElement);h("[style]").forEach(StyleFix.styleAttribute)},register:function(a,c){(b.fixers=b.fixers||[]).splice(void 0===c?b.fixers.length:c,0,a)},fix:function(a,c){for(var d=0;d ' + req.url); 42 | } 43 | } 44 | }); 45 | next(); 46 | }); 47 | } 48 | 49 | if (options.processImage) { 50 | app.use(require('express-processimage')({root: options.root})); 51 | } 52 | 53 | if (options.autoprefixer) { 54 | var autoprefixerOptions = {}; 55 | if (typeof options.autoprefixer === 'string' || Array.isArray(options.autoprefixer)) { 56 | autoprefixerOptions = { browsers: options.autoprefixer }; 57 | } else if (typeof options.autoprefixer === 'object') { 58 | autoprefixerOptions = options.autoprefixer; 59 | } 60 | app.use(require('express-autoprefixer')(autoprefixerOptions)); 61 | } 62 | 63 | if (options.root) { 64 | if (options.compiless) { 65 | app.use(require('express-compiless')({root: options.root})); 66 | } 67 | 68 | if (options.compilesass) { 69 | app.use(require('express-compile-sass')({ 70 | root: options.root, 71 | sourceMap: true, 72 | watchFiles: !options.dead, 73 | logToConsole: options.debug 74 | })); 75 | } 76 | 77 | if (options.jsxtransform) { 78 | app.use(require('express-jsxtransform')()); 79 | } 80 | 81 | var staticProvider = express['static'](options.root); 82 | app.use(function (req, res, next) { 83 | if (!proxyUrl || req.accepts('css') || req.accepts('text/x-less')) { 84 | staticProvider(req, res, next); 85 | } else { 86 | next(); 87 | } 88 | }); 89 | } 90 | 91 | if (proxyUrl) { 92 | var selfRedirectUrlRegExp = new RegExp('^' + proxyUrl.protocol + '//' + proxyUrl.host.replace(/[\.\+\*\?\{\}\[\]\(\)]/g, '\\$&') + '/'); 93 | app.use(function (req, res) { 94 | // Pass on the host header so servers further down the line can use it 95 | req.headers.Host = proxyUrl.host; 96 | 97 | var targetUrl = proxyUrl.protocol + '//' + proxyUrl.host + req.url, 98 | upstreamRequest = request({ 99 | followRedirect: false, 100 | method: req.method, 101 | url: targetUrl, 102 | onResponse: true 103 | }, function (err, response) { 104 | if (options.debug) { 105 | console.warn('Proxying ' + req.url + ' to ' + targetUrl + ' => ' + (err ? err.toString() : response.statusCode)); 106 | } 107 | if (err) { 108 | res.writeHead(502, {}); 109 | res.end('Error connecting to upstream server: ' + err.stack); 110 | } else { 111 | if (response.headers.location && /^https?:/.test(response.headers.location)) { 112 | // HTTP redirect. If it's an absolute url pointing at the upstream server, rewrite it to a relative one 113 | // so it points at the LiveStyle proxy. Technically this is a violation of the HTTP spec, but getting the 114 | // LiveStyle server's host/port is a bit hard from here, and browsers understand it fine: 115 | var rewrittenRedirect = response.headers.location.replace(selfRedirectUrlRegExp, '/'); 116 | if (rewrittenRedirect !== response.headers.location) { 117 | if (options.debug) { 118 | console.warn('Rewriting redirect from upstream server: ' + response.headers.location + ' => ' + rewrittenRedirect); 119 | } 120 | response.headers.location = rewrittenRedirect; 121 | } 122 | } 123 | res.writeHead(response.statusCode, response.headers); 124 | response.pipe(res); 125 | } 126 | }); 127 | req.pipe(upstreamRequest); 128 | }); 129 | } 130 | 131 | app.use(errorHandler); 132 | 133 | return app; 134 | }; 135 | -------------------------------------------------------------------------------- /bin/livestyle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /*jslint nomen:false */ 4 | /*global require, __dirname */ 5 | var express = require('express'), 6 | util = require('util'), 7 | path = require('path'), 8 | mappings = {}, 9 | optimist = require('optimist'), 10 | commandLineOptions = optimist 11 | .usage('Usage: $0 [--proxy [-m ]] [-d] [-h ] [-p ] [--root directory]') 12 | .boolean('d') 13 | .options('root', { 14 | alias: 'r', 15 | describe: 'The directory to serve static files from. Defaults to the current working directory unless --proxy is specified.' 16 | }) 17 | .options('watchhtml', { 18 | type: 'boolean', 19 | describe: 'Watch the HTML file itself and reload the entire page on change', 20 | default: false 21 | }) 22 | .options('compiless', { 23 | type: 'boolean', 24 | describe: 'Whether to add the express-compiless middleware to the stack (compiles less files and serves it them as text/css).', 25 | default: false 26 | }) 27 | .options('compilesass', { 28 | type: 'boolean', 29 | describe: 'Whether to add the express-compile-sass middleware to the stack (compiles scss files and serves it them as text/css).', 30 | default: false 31 | }) 32 | .options('autoprefixer', { 33 | describe: 'If this flag is set the express-autoprefixer middleware is added to the stack (adds missing vendor prefixes to text/css files). If will use the defaults from the autoprefixer module if you provide no options. You can provide options as a string. Fx: "ie > 8, last 2 versions".', 34 | }) 35 | .options('jsxtransform', { 36 | type: 'boolean', 37 | describe: 'Compile facebook React JSX to JS on the way out.', 38 | default: false 39 | }) 40 | .options('processimage', { 41 | alias: 'processimages', 42 | type: 'boolean', 43 | describe: 'Whether to add the express-processimage middleware to the stack.', 44 | default: false 45 | }) 46 | .options('dead', { 47 | default: false, 48 | describe: 'Do not watch files on disc and do not inject the LiveStyle client' 49 | }) 50 | .options('debug', { 51 | alias: 'd', 52 | describe: 'Turn on debug mode (server logs events to stderr, client logs to console).' 53 | }) 54 | .options('proxy', { 55 | describe: 'The remote host:port to serve non-CSS files from' 56 | }) 57 | .options('map', { 58 | alias: 'm', 59 | describe: 'Directory mappings of the form: remoteDir=localDir where localDir is relative to the root directory unless prefixed with /' 60 | }) 61 | .options('host', { 62 | alias: 'h', 63 | default: '0.0.0.0', 64 | describe: 'The local hostname or IP-address to listen on' 65 | }) 66 | .options('port', { 67 | alias: 'p', 68 | default: 3000, 69 | describe: 'The local post number to listen on' 70 | }) 71 | .options('watchfile', { 72 | describe: 'force using fs.watchFile instead of fs.watch (works better on some OSes)' 73 | }) 74 | .options('mtime', { 75 | describe: 'suppress change events unless the mtime of a file has changed (experimental, fs.watch only)' 76 | }) 77 | .check(function (argv) { 78 | if (argv.map) { 79 | (typeof argv.map === 'string' ? [argv.map] : argv.map).forEach(function (urlEqualsDir) { 80 | var matchUrlEqualsDir = urlEqualsDir.match(/^([^=]+)=([^=]+)$/); 81 | if (matchUrlEqualsDir) { 82 | mappings[matchUrlEqualsDir[1]] = matchUrlEqualsDir[2]; 83 | } else { 84 | throw 'Invalid --map syntax: ' + urlEqualsDir; 85 | } 86 | }); 87 | } 88 | }) 89 | .argv, 90 | root; 91 | 92 | if (commandLineOptions.root) { 93 | root = path.resolve(process.cwd(), commandLineOptions.root); 94 | } else if (!commandLineOptions.proxy) { 95 | root = process.cwd(); 96 | } 97 | 98 | if (commandLineOptions.help) { 99 | optimist.showHelp(); 100 | process.exit(); 101 | } 102 | 103 | if (commandLineOptions.proxy) { 104 | console.log('Proxying to ' + commandLineOptions.proxy); 105 | if (root) { 106 | console.log('Serving static CSS files from ' + root); 107 | } 108 | } else if (root) { 109 | console.log('Serving static files from ' + root); 110 | } 111 | 112 | console.log('Listening to http://' + commandLineOptions.host + ':' + commandLineOptions.port + '/'); 113 | 114 | var server = require('../lib/createLiveStyleApp')({ 115 | watchHtml: commandLineOptions.watchhtml, 116 | watchCssImages: commandLineOptions.watchcssimages, 117 | dead: commandLineOptions.dead, 118 | debug: commandLineOptions.debug, 119 | root: root, 120 | compiless: commandLineOptions.compiless, 121 | compilesass: commandLineOptions.compilesass, 122 | autoprefixer: commandLineOptions.autoprefixer, 123 | jsxtransform: commandLineOptions.jsxtransform, 124 | processImage: commandLineOptions.processimage, 125 | mappings: mappings, 126 | proxy: commandLineOptions.proxy || null 127 | }).listen(commandLineOptions.port, commandLineOptions.host); 128 | 129 | if (root && !commandLineOptions.dead) { 130 | require('../lib/installLiveCssFileWatcherInServer')(server, { 131 | debug: commandLineOptions.debug, 132 | root: root, 133 | mtime: commandLineOptions.mtime, 134 | watchfile: commandLineOptions.watchfile, 135 | mappings: mappings, 136 | watchCssImages: commandLineOptions.watchcssimages 137 | }, require('socket.io')); 138 | } 139 | -------------------------------------------------------------------------------- /test/proxy-test.js: -------------------------------------------------------------------------------- 1 | var createLiveStyleTestServer = require('./createLiveStyleTestServer'), 2 | expect = require('unexpected'), 3 | path = require('path'), 4 | express = require('express'), 5 | request = require('request'); 6 | 7 | describe('livestyle server in proxy mode', function () { 8 | // create a livestyle server in pure proxy mode and an upstream 9 | // server, then request an HTML file 10 | // the HTML file should be patched with the bootstrapper right 11 | // before 12 | it('should inject the livestyle client in a document', function (done) { 13 | var root = path.resolve(__dirname, 'proxy'), 14 | upstreamApp = express().use(express['static'](root)), 15 | upstreamServer = upstreamApp.listen(0), 16 | upstreamServerUrl = 'http://127.0.0.1:' + upstreamServer.address().port + '/', 17 | appInfo = createLiveStyleTestServer({ 18 | proxy: upstreamServerUrl 19 | }); 20 | 21 | request({method: 'GET', url: 'http://127.0.0.1:' + appInfo.port + '/'}, function (err, res, body) { 22 | expect(err, 'to be null'); 23 | expect(body, 'to match', /<\/script><\/head>/); 24 | done(); 25 | }); 26 | }); 27 | // create a livestyle server in pure proxy mode and an upstream 28 | // server, then request an HTML file 29 | // the HTML file should be patched with the bootstrapper right 30 | // before 31 | it('should inject the livestyle client in a document before first script tag', function (done) { 32 | var root = path.resolve(__dirname, 'proxy'), 33 | upstreamApp = express().use(express['static'](root)), 34 | upstreamServer = upstreamApp.listen(0), 35 | upstreamServerUrl = 'http://127.0.0.1:' + upstreamServer.address().port + '/', 36 | appInfo = createLiveStyleTestServer({ 37 | proxy: upstreamServerUrl 38 | }); 39 | 40 | request({method: 'GET', url: 'http://127.0.0.1:' + appInfo.port + '/earlyscript.html'}, function (err, res, body) { 41 | expect(err, 'to be null'); 42 | expect(body, 'to match', /<\/script>' + 76 | ''); 77 | if (chunk.length > i) { 78 | res.write(chunk.slice(i)); 79 | } 80 | injected = true; 81 | } 82 | 83 | res.on('end', function () { 84 | if (!injected) { 85 | injectScriptAtIndex(new Buffer([]), 0); 86 | } 87 | res.end(); 88 | }).on('data', function (chunk, encoding) { 89 | if (injected) { 90 | res.write(chunk, encoding); 91 | } else { 92 | for (var i = 0 ; i < chunk.length ; i += 1) { 93 | var ch; 94 | if (Buffer.isBuffer(chunk)) { 95 | ch = String.fromCharCode(chunk[i]); 96 | } else { 97 | // string 98 | ch = chunk[i]; 99 | } 100 | switch (state) { 101 | case 0: 102 | if (ch === '<') { 103 | state = 1; 104 | } 105 | break; 106 | case 1: // < 107 | if (ch === '/') { 108 | state = 2; 109 | } else if (ch === 's' || ch === 'S') { 110 | state = 10; 111 | } else { 112 | state = 0; 113 | } 114 | break; 115 | case 2: // ' || ch === ' ') { 147 | injectScriptAtIndex(chunk, i + 1 - ''.length); 148 | return; 149 | } else { 150 | state = 0; 151 | } 152 | break; 153 | case 7: // ' || ch === ' ') { 169 | injectScriptAtIndex(chunk, i + 1 - ''.length); 170 | return; 171 | } else { 172 | state = 0; 173 | } 174 | break; 175 | case 10: // 0?d.firstChild.nodeValue!==a.nodeValue&&d.replaceChild(a,d.firstChild):d.appendChild(a)})(document.createTextNode(a));c&&g&&(t("saving "+e+" to cache."),g.setItem(e,a),g.setItem(e+":timestamp",c))}function q(a,b,c,e){function i(b,c,d){b.status>=200&&b.status<300?c(b.responseText,b.getResponseHeader("Last-Modified")):typeof d=="function"&&d(b.status,a)}var g=r(),h=f?!1:d.async;typeof g.overrideMimeType=="function"&&g.overrideMimeType("text/css"),g.open("GET",a,h),g.setRequestHeader("Accept",b||"text/x-less, text/css; q=0.9, */*; q=0.5"),g.send(null),f?g.status===0?c(g.responseText):e(g.status,a):h?g.onreadystatechange=function(){g.readyState==4&&i(g,c,e)}:i(g,c,e)}function r(){if(a.XMLHttpRequest)return new XMLHttpRequest;try{return new ActiveXObject("MSXML2.XMLHTTP.3.0")}catch(b){return t("browser doesn't support AJAX."),null}}function s(a){return a&&a.parentNode.removeChild(a)}function t(a){d.env=="development"&&typeof console!="undefined"&&console.log("less: "+a)}function u(a,b){var c="less-error-message:"+o(b),e=["
    ",'
  • {0}
  • ',"
  • {current}
  • ",'
  • {2}
  • ',"
"].join("\n"),f=document.createElement("div"),g,h;f.id=c,f.className="less-error-message",h="

"+(a.message||"There is an error in your .less file")+"

"+'

'+b+" ",a.extract&&(h+="on line "+a.line+", column "+(a.column+1)+":

"+e.replace(/\[(-?\d)\]/g,function(b,c){return parseInt(a.line)+parseInt(c)||""}).replace(/\{(\d)\}/g,function(b,c){return a.extract[parseInt(c)]||""}).replace(/\{current\}/,a.extract[1].slice(0,a.column)+''+a.extract[1].slice(a.column)+"")),f.innerHTML=h,p([".less-error-message ul, .less-error-message li {","list-style-type: none;","margin-right: 15px;","padding: 4px 0;","margin: 0;","}",".less-error-message label {","font-size: 12px;","margin-right: 15px;","padding: 4px 0;","color: #cc7777;","}",".less-error-message pre {","color: #ee4444;","padding: 4px 0;","margin: 0;","display: inline-block;","}",".less-error-message pre.ctx {","color: #dd4444;","}",".less-error-message h3 {","font-size: 20px;","font-weight: bold;","padding: 15px 0 5px 0;","margin: 0;","}",".less-error-message a {","color: #10a","}",".less-error-message .error {","color: red;","font-weight: bold;","padding-bottom: 2px;","border-bottom: 1px dashed red;","}"].join("\n"),{title:"error-message"}),f.style.cssText=["font-family: Arial, sans-serif","border: 1px solid #e00","background-color: #eee","border-radius: 5px","-webkit-border-radius: 5px","-moz-border-radius: 5px","color: #e00","padding: 15px","margin-bottom: 15px"].join(";"),d.env=="development"&&(g=setInterval(function(){document.body&&(document.getElementById(c)?document.body.replaceChild(f,document.getElementById(c)):document.body.insertBefore(f,document.body.firstChild),clearInterval(g))},10))}Array.isArray||(Array.isArray=function(a){return Object.prototype.toString.call(a)==="[object Array]"||a instanceof Array}),Array.prototype.forEach||(Array.prototype.forEach=function(a,b){var c=this.length>>>0;for(var d=0;d>>0,c=new Array(b),d=arguments[1];for(var e=0;e>>0,c=0;if(b===0&&arguments.length===1)throw new TypeError;if(arguments.length>=2)var d=arguments[1];else do{if(c in this){d=this[c++];break}if(++c>=b)throw new TypeError}while(!0);for(;c=b)return-1;c<0&&(c+=b);for(;ck&&(j[f]=j[f].slice(c-k),k=c)}function s(a){var d,e,g,h,i,m,n,o;if(a instanceof Function)return a.call(l.parsers);if(typeof a=="string")d=b.charAt(c)===a?a:null,g=1,r();else{r();if(d=a.exec(j[f]))g=d[0].length;else return null}if(d){o=c+=g,m=c+j[f].length-g;while(c0)throw{type:"Syntax",message:"Missing closing `}`",filename:a.filename};return c.map(function(a){return a.join("")})}([[]]),h=new e.Ruleset([],s(this.parsers.primary)),h.root=!0,h.toCSS=function(c){var d,f,g;return function(g,h){function n(a){return a?(b.slice(0,a).match(/\n/g)||"").length:null}var i=[];g=g||{},typeof h=="object"&&!Array.isArray(h)&&(h=Object.keys(h).map(function(a){var b=h[a];return b instanceof e.Value||(b instanceof e.Expression||(b=new e.Expression([b])),b=new e.Value([b])),new e.Rule("@"+a,b,!1,0)}),i=[new e.Ruleset(null,h)]);try{var j=c.call(this,{frames:i}).toCSS([],{compress:g.compress||!1})}catch(k){f=b.split("\n"),d=n(k.index);for(var l=k.index,m=-1;l>=0&&b.charAt(l)!=="\n";l--)m++;throw{type:k.type,message:k.message,filename:a.filename,index:k.index,line:typeof d=="number"?d+1:null,callLine:k.call&&n(k.call)+1,callExtract:f[n(k.call)],stack:k.stack,column:m,extract:[f[d-1],f[d],f[d+1]]}}return g.compress?j.replace(/(\s)+/g,"$1"):j}}(h.eval);if(c=0&&b.charAt(v)!=="\n";v--)w++;u={name:"ParseError",message:"Syntax Error on line "+p,index:c,filename:a.filename,line:p,column:w,extract:[q[p-2],q[p-1],q[p]]}}this.imports.queue.length>0?n=function(){g(u,h)}:g(u,h)},parsers:{primary:function(){var a,b=[];while((a=s(this.mixin.definition)||s(this.rule)||s(this.ruleset)||s(this.mixin.call)||s(this.comment)||s(this.directive))||s(/^[\s\n]+/))a&&b.push(a);return b},comment:function(){var a;if(b.charAt(c)!=="/")return;if(b.charAt(c+1)==="/")return new e.Comment(s(/^\/\/.*/),!0);if(a=s(/^\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/))return new e.Comment(a)},entities:{quoted:function(){var a,d=c,f;b.charAt(d)==="~"&&(d++,f=!0);if(b.charAt(d)!=='"'&&b.charAt(d)!=="'")return;f&&s("~");if(a=s(/^"((?:[^"\\\r\n]|\\.)*)"|'((?:[^'\\\r\n]|\\.)*)'/))return new e.Quoted(a[0],a[1]||a[2],f)},keyword:function(){var a;if(a=s(/^[_A-Za-z-][_A-Za-z0-9-]*/))return new e.Keyword(a)},call:function(){var a,b,d=c;if(!(a=/^([\w-]+|%)\(/.exec(j[f])))return;a=a[1].toLowerCase();if(a==="url")return null;c+=a.length;if(a==="alpha")return s(this.alpha);s("("),b=s(this.entities.arguments);if(!s(")"))return;if(a)return new e.Call(a,b,d)},arguments:function(){var a=[],b;while(b=s(this.expression)){a.push(b);if(!s(","))break}return a},literal:function(){return s(this.entities.dimension)||s(this.entities.color)||s(this.entities.quoted)},url:function(){var a;if(b.charAt(c)!=="u"||!s(/^url\(/))return;a=s(this.entities.quoted)||s(this.entities.variable)||s(this.entities.dataURI)||s(/^[-\w%@$\/.&=:;#+?~]+/)||"";if(!s(")"))throw new Error("missing closing ) for url()");return new e.URL(a.value||a.data||a instanceof e.Variable?a:new e.Anonymous(a),o.paths)},dataURI:function(){var a;if(s(/^data:/)){a={},a.mime=s(/^[^\/]+\/[^,;)]+/)||"",a.charset=s(/^;\s*charset=[^,;)]+/)||"",a.base64=s(/^;\s*base64/)||"",a.data=s(/^,\s*[^)]+/);if(a.data)return a}},variable:function(){var a,d=c;if(b.charAt(c)==="@"&&(a=s(/^@@?[\w-]+/)))return new e.Variable(a,d)},color:function(){var a;if(b.charAt(c)==="#"&&(a=s(/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})/)))return new e.Color(a[1])},dimension:function(){var a,d=b.charCodeAt(c);if(d>57||d<45||d===47)return;if(a=s(/^(-?\d*\.?\d+)(px|%|em|pc|ex|in|deg|s|ms|pt|cm|mm|rad|grad|turn)?/))return new e.Dimension(a[1],a[2])},javascript:function(){var a,d=c,f;b.charAt(d)==="~"&&(d++,f=!0);if(b.charAt(d)!=="`")return;f&&s("~");if(a=s(/^`([^`]*)`/))return new e.JavaScript(a[1],c,f)}},variable:function(){var a;if(b.charAt(c)==="@"&&(a=s(/^(@[\w-]+)\s*:/)))return a[1]},shorthand:function(){var a,b;if(!t(/^[@\w.%-]+\/[@\w.-]+/))return;if((a=s(this.entity))&&s("/")&&(b=s(this.entity)))return new e.Shorthand(a,b)},mixin:{call:function(){var a=[],d,f,g,h=c,i=b.charAt(c);if(i!=="."&&i!=="#")return;while(d=s(/^[#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/))a.push(new e.Element(f,d,c)),f=s(">");s("(")&&(g=s(this.entities.arguments))&&s(")");if(a.length>0&&(s(";")||t("}")))return new e.mixin.Call(a,g,h)},definition:function(){var a,d=[],f,g,h,i;if(b.charAt(c)!=="."&&b.charAt(c)!=="#"||t(/^[^{]*(;|})/))return;if(f=s(/^([#.](?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+)\s*\(/)){a=f[1];while(h=s(this.entities.variable)||s(this.entities.literal)||s(this.entities.keyword)){if(h instanceof e.Variable)if(s(":"))if(i=s(this.expression))d.push({name:h.name,value:i});else throw new Error("Expected value");else d.push({name:h.name});else d.push({value:h});if(!s(","))break}if(!s(")"))throw new Error("Expected )");g=s(this.block);if(g)return new e.mixin.Definition(a,d,g)}}},entity:function(){return s(this.entities.literal)||s(this.entities.variable)||s(this.entities.url)||s(this.entities.call)||s(this.entities.keyword)||s(this.entities.javascript)||s(this.comment)},end:function(){return s(";")||t("}")},alpha:function(){var a;if(!s(/^\(opacity=/i))return;if(a=s(/^\d+/)||s(this.entities.variable)){if(!s(")"))throw new Error("missing closing ) for alpha()");return new e.Alpha(a)}},element:function(){var a,b,d;d=s(this.combinator),a=s(/^(?:\d+\.\d+|\d+)%/)||s(/^(?:[.#]?|:*)(?:[\w-]|\\(?:[a-fA-F0-9]{1,6} ?|[^a-fA-F0-9]))+/)||s("*")||s(this.attribute)||s(/^\([^)@]+\)/);if(a)return new e.Element(d,a,c);if(d.value&&d.value.charAt(0)==="&")return new e.Element(d,null,c)},combinator:function(){var a,d=b.charAt(c);if(d===">"||d==="+"||d==="~"){c++;while(b.charAt(c)===" ")c++;return new e.Combinator(d)}if(d==="&"){a="&",c++,b.charAt(c)===" "&&(a="& ");while(b.charAt(c)===" ")c++;return new e.Combinator(a)}if(d===":"&&b.charAt(c+1)===":"){c+=2;while(b.charAt(c)===" ")c++;return new e.Combinator("::")}return b.charAt(c-1)===" "?new e.Combinator(" "):new e.Combinator(null)},selector:function(){var a,d,f=[],g,h;while(d=s(this.element)){g=b.charAt(c),f.push(d);if(g==="{"||g==="}"||g===";"||g===",")break}if(f.length>0)return new e.Selector(f)},tag:function(){return s(/^[a-zA-Z][a-zA-Z-]*[0-9]?/)||s("*")},attribute:function(){var a="",b,c,d;if(!s("["))return;if(b=s(/^[a-zA-Z-]+/)||s(this.entities.quoted))(d=s(/^[|~*$^]?=/))&&(c=s(this.entities.quoted)||s(/^[\w-]+/))?a=[b,d,c.toCSS?c.toCSS():c].join(""):a=b;if(!s("]"))return;if(a)return"["+a+"]"},block:function(){var a;if(s("{")&&(a=s(this.primary))&&s("}"))return a},ruleset:function(){var a=[],b,d,f;p();while(b=s(this.selector)){a.push(b),s(this.comment);if(!s(","))break;s(this.comment)}if(a.length>0&&(d=s(this.block)))return new e.Ruleset(a,d);i=c,q()},rule:function(){var a,d,g=b.charAt(c),k,l;p();if(g==="."||g==="#"||g==="&")return;if(a=s(this.variable)||s(this.property)){a.charAt(0)!="@"&&(l=/^([^@+\/'"*`(;{}-]*);/.exec(j[f]))?(c+=l[0].length-1,d=new e.Anonymous(l[1])):a==="font"?d=s(this.font):d=s(this.value),k=s(this.important);if(d&&s(this.end))return new e.Rule(a,d,k,h);i=c,q()}},"import":function(){var a;if(s(/^@import\s+/)&&(a=s(this.entities.quoted)||s(this.entities.url))&&s(";"))return new e.Import(a,o)},directive:function(){var a,d,f,g;if(b.charAt(c)!=="@")return;if(d=s(this["import"]))return d;if(a=s(/^@media|@page/)||s(/^@(?:-webkit-|-moz-)?keyframes/)){g=(s(/^[^{]+/)||"").trim();if(f=s(this.block))return new e.Directive(a+" "+g,f)}else if(a=s(/^@[-a-z]+/))if(a==="@font-face"){if(f=s(this.block))return new e.Directive(a,f)}else if((d=s(this.entity))&&s(";"))return new e.Directive(a,d)},font:function(){var a=[],b=[],c,d,f,g;while(g=s(this.shorthand)||s(this.entity))b.push(g);a.push(new e.Expression(b));if(s(","))while(g=s(this.expression)){a.push(g);if(!s(","))break}return new e.Value(a)},value:function(){var a,b=[],c;while(a=s(this.expression)){b.push(a);if(!s(","))break}if(b.length>0)return new e.Value(b)},important:function(){if(b.charAt(c)==="!")return s(/^! *important/)},sub:function(){var a;if(s("(")&&(a=s(this.expression))&&s(")"))return a},multiplication:function(){var a,b,c,d;if(a=s(this.operand)){while((c=s("/")||s("*"))&&(b=s(this.operand)))d=new e.Operation(c,[d||a,b]);return d||a}},addition:function(){var a,d,f,g;if(a=s(this.multiplication)){while((f=s(/^[-+]\s+/)||b.charAt(c-1)!=" "&&(s("+")||s("-")))&&(d=s(this.multiplication)))g=new e.Operation(f,[g||a,d]);return g||a}},operand:function(){var a,d=b.charAt(c+1);b.charAt(c)==="-"&&(d==="@"||d==="(")&&(a=s("-"));var f=s(this.sub)||s(this.entities.dimension)||s(this.entities.color)||s(this.entities.variable)||s(this.entities.call);return a?new e.Operation("*",[new e.Dimension(-1),f]):f},expression:function(){var a,b,c=[],d;while(a=s(this.addition)||s(this.entity))c.push(a);if(c.length>0)return new e.Expression(c)},property:function(){var a;if(a=s(/^(\*?-?[-a-z_0-9]+)\s*:/))return a[1]}}}};if(d.mode==="browser"||d.mode==="rhino")d.Parser.importer=function(a,b,c,d){a.charAt(0)!=="/"&&b.length>0&&(a=b[0]+a),n({href:a,title:a,type:d.mime},c,!0)};(function(a){function b(b){return a.functions.hsla(b.h,b.s,b.l,b.a)}function c(b){if(b instanceof a.Dimension)return parseFloat(b.unit=="%"?b.value/100:b.value);if(typeof b=="number")return b;throw{error:"RuntimeError",message:"color functions take numbers as parameters"}}function d(a){return Math.min(1,Math.max(0,a))}a.functions={rgb:function(a,b,c){return this.rgba(a,b,c,1)},rgba:function(b,d,e,f){var g=[b,d,e].map(function(a){return c(a)}),f=c(f);return new a.Color(g,f)},hsl:function(a,b,c){return this.hsla(a,b,c,1)},hsla:function(a,b,d,e){function h(a){return a=a<0?a+1:a>1?a-1:a,a*6<1?g+(f-g)*a*6:a*2<1?f:a*3<2?g+(f-g)*(2/3-a)*6:g}a=c(a)%360/360,b=c(b),d=c(d),e=c(e);var f=d<=.5?d*(b+1):d+b-d*b,g=d*2-f;return this.rgba(h(a+1/3)*255,h(a)*255,h(a-1/3)*255,e)},hue:function(b){return new a.Dimension(Math.round(b.toHSL().h))},saturation:function(b){return new a.Dimension(Math.round(b.toHSL().s*100),"%")},lightness:function(b){return new a.Dimension(Math.round(b.toHSL().l*100),"%")},alpha:function(b){return new a.Dimension(b.toHSL().a)},saturate:function(a,c){var e=a.toHSL();return e.s+=c.value/100,e.s=d(e.s),b(e)},desaturate:function(a,c){var e=a.toHSL();return e.s-=c.value/100,e.s=d(e.s),b(e)},lighten:function(a,c){var e=a.toHSL();return e.l+=c.value/100,e.l=d(e.l),b(e)},darken:function(a,c){var e=a.toHSL();return e.l-=c.value/100,e.l=d(e.l),b(e)},fadein:function(a,c){var e=a.toHSL();return e.a+=c.value/100,e.a=d(e.a),b(e)},fadeout:function(a,c){var e=a.toHSL();return e.a-=c.value/100,e.a=d(e.a),b(e)},fade:function(a,c){var e=a.toHSL();return e.a=c.value/100,e.a=d(e.a),b(e)},spin:function(a,c){var d=a.toHSL(),e=(d.h+c.value)%360;return d.h=e<0?360+e:e,b(d)},mix:function(b,c,d){var e=d.value/100,f=e*2-1,g=b.toHSL().a-c.toHSL().a,h=((f*g==-1?f:(f+g)/(1+f*g))+1)/2,i=1-h,j=[b.rgb[0]*h+c.rgb[0]*i,b.rgb[1]*h+c.rgb[1]*i,b.rgb[2]*h+c.rgb[2]*i],k=b.alpha*e+c.alpha*(1-e);return new a.Color(j,k)},greyscale:function(b){return this.desaturate(b,new a.Dimension(100))},e:function(b){return new a.Anonymous(b instanceof a.JavaScript?b.evaluated:b)},escape:function(b){return new a.Anonymous(encodeURI(b.value).replace(/=/g,"%3D").replace(/:/g,"%3A").replace(/#/g,"%23").replace(/;/g,"%3B").replace(/\(/g,"%28").replace(/\)/g,"%29"))},"%":function(b){var c=Array.prototype.slice.call(arguments,1),d=b.value;for(var e=0;e255?255:a<0?0:a).toString(16),a.length===1?"0"+a:a}).join("")},operate:function(b,c){var d=[];c instanceof a.Color||(c=c.toColor());for(var e=0;e<3;e++)d[e]=a.operate(b,this.rgb[e],c.rgb[e]);return new a.Color(d,this.alpha+c.alpha)},toHSL:function(){var a=this.rgb[0]/255,b=this.rgb[1]/255,c=this.rgb[2]/255,d=this.alpha,e=Math.max(a,b,c),f=Math.min(a,b,c),g,h,i=(e+f)/2,j=e-f;if(e===f)g=h=0;else{h=i>.5?j/(2-e-f):j/(e+f);switch(e){case a:g=(b-c)/j+(b255?255:a<0?0:a).toString(16),a.length===1?"0"+a:a}).join("")}}}(c("../tree")),function(a){a.Comment=function(a,b){this.value=a,this.silent=!!b},a.Comment.prototype={toCSS:function(a){return a.compress?"":this.value},eval:function(){return this}}}(c("../tree")),function(a){a.Dimension=function(a,b){this.value=parseFloat(a),this.unit=b||null},a.Dimension.prototype={eval:function(){return this},toColor:function(){return new a.Color([this.value,this.value,this.value])},toCSS:function(){var a=this.value+this.unit;return a},operate:function(b,c){return new a.Dimension(a.operate(b,this.value,c.value),this.unit||c.unit)}}}(c("../tree")),function(a){a.Directive=function(b,c){this.name=b,Array.isArray(c)?this.ruleset=new a.Ruleset([],c):this.value=c},a.Directive.prototype={toCSS:function(a,b){return this.ruleset?(this.ruleset.root=!0,this.name+(b.compress?"{":" {\n ")+this.ruleset.toCSS(a,b).trim().replace(/\n/g,"\n ")+(b.compress?"}":"\n}\n")):this.name+" "+this.value.toCSS()+";\n"},eval:function(a){return a.frames.unshift(this),this.ruleset=this.ruleset&&this.ruleset.eval(a),a.frames.shift(),this},variable:function(b){return a.Ruleset.prototype.variable.call(this.ruleset,b)},find:function(){return a.Ruleset.prototype.find.apply(this.ruleset,arguments)},rulesets:function(){return a.Ruleset.prototype.rulesets.apply(this.ruleset)}}}(c("../tree")),function(a){a.Element=function(b,c,d){this.combinator=b instanceof a.Combinator?b:new a.Combinator(b),this.value=c?c.trim():"",this.index=d},a.Element.prototype.toCSS=function(a){return this.combinator.toCSS(a||{})+this.value},a.Combinator=function(a){a===" "?this.value=" ":a==="& "?this.value="& ":this.value=a?a.trim():""},a.Combinator.prototype.toCSS=function(a){return{"":""," ":" ","&":"","& ":" ",":":" :","::":"::","+":a.compress?"+":" + ","~":a.compress?"~":" ~ ",">":a.compress?">":" > "}[this.value]}}(c("../tree")),function(a){a.Expression=function(a){this.value=a},a.Expression.prototype={eval:function(b){return this.value.length>1?new a.Expression(this.value.map(function(a){return a.eval(b)})):this.value.length===1?this.value[0].eval(b):this},toCSS:function(a){return this.value.map(function(b){return b.toCSS(a)}).join(" ")}}}(c("../tree")),function(a){a.Import=function(b,c){var d=this;this._path=b,b instanceof a.Quoted?this.path=/\.(le?|c)ss(\?.*)?$/.test(b.value)?b.value:b.value+".less":this.path=b.value.value||b.value,this.css=/css(\?.*)?$/.test(this.path),this.css||c.push(this.path,function(a){if(!a)throw new Error("Error parsing "+d.path);d.root=a})},a.Import.prototype={toCSS:function(){return this.css?"@import "+this._path.toCSS()+";\n":""},eval:function(b){var c;if(this.css)return this;c=new a.Ruleset(null,this.root.rules.slice(0));for(var d=0;d0){c=this.arguments&&this.arguments.map(function(b){return b.eval(a)});for(var g=0;g0&&c>this.params.length)return!1;d=Math.min(c,this.arity);for(var e=0;ee.selectors[g].elements.length?Array.prototype.push.apply(d,e.find(new a.Selector(b.elements.slice(1)),c)):d.push(e);break}}),this._lookups[g]=d)},toCSS:function(b,c){var d=[],e=[],f=[],g=[],h,i;this.root||(b.length===0?g=this.selectors.map(function(a){return[a]}):this.joinSelectors(g,b,this.selectors));for(var j=0;j0&&(h=g.map(function(a){return a.map(function(a){return a.toCSS(c)}).join("").trim()}).join(c.compress?",":g.length>3?",\n":", "),d.push(h,(c.compress?"{":" {\n ")+e.join(c.compress?"":"\n ")+(c.compress?"}":"\n}\n"))),d.push(f),d.join("")+(c.compress?"\n":"")},joinSelectors:function(a,b,c){for(var d=0;d0&&e.push(new a.Selector(g)),h.length>0&&f.push(new a.Selector(h));for(var l=0;l0&&typeof a!="undefined"&&(b.value=c[0]+(b.value.charAt(0)==="/"?b.value.slice(1):b.value)),this.value=b,this.paths=c)},b.URL.prototype={toCSS:function(){return"url("+(this.attrs?"data:"+this.attrs.mime+this.attrs.charset+this.attrs.base64+this.attrs.data:this.value.toCSS())+")"},eval:function(a){return this.attrs?this:new b.URL(this.value.eval(a),this.paths)}}}(c("../tree")),function(a){a.Value=function(a){this.value=a,this.is="value"},a.Value.prototype={eval:function(b){return this.value.length===1?this.value[0].eval(b):new a.Value(this.value.map(function(a){return a.eval(b)}))},toCSS:function(a){return this.value.map(function(b){return b.toCSS(a)}).join(a.compress?",":", ")}}}(c("../tree")),function(a){a.Variable=function(a,b){this.name=a,this.index=b},a.Variable.prototype={eval:function(b){var c,d,e=this.name;e.indexOf("@@")==0&&(e="@"+(new a.Variable(e.slice(1))).eval(b).value);if(c=a.find(b.frames,function(a){if(d=a.variable(e))return d.value.eval(b)}))return c;throw{message:"variable "+e+" is undefined",index:this.index}}}}(c("../tree")),c("./tree").find=function(a,b){for(var c=0,d;c1?"["+a.value.map(function(a){return a.toCSS(!1)}).join(", ")+"]":a.toCSS(!1)};var f=location.protocol==="file:"||location.protocol==="chrome:"||location.protocol==="chrome-extension:"||location.protocol==="resource:";d.env=d.env||(location.hostname=="127.0.0.1"||location.hostname=="0.0.0.0"||location.hostname=="localhost"||location.port.length>0||f?"development":"production"),d.async=!1,d.poll=d.poll||(f?1e3:1500),d.watch=function(){return this.watchMode=!0},d.unwatch=function(){return this.watchMode=!1},d.env==="development"?(d.optimization=0,/!watch/.test(location.hash)&&d.watch(),d.watchTimer=setInterval(function(){d.watchMode&&m(function(a,b,c){a&&p(a.toCSS(),b,c.lastModified)})},d.poll)):d.optimization=3;var g;try{g=typeof a.localStorage=="undefined"?null:a.localStorage}catch(h){g=null}var i=document.getElementsByTagName("link"),j=/^text\/(x-)?less$/;d.sheets=[];for(var k=0;k]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?\u00ab\u00bb\u201c\u201d\u2018\u2019]))/ig;d.defaultPorts={http:"80",https:"443",ftp:"21"};d.invalid_hostname_characters=/[^a-zA-Z0-9\.-]/;d.encode=q;d.decode=decodeURIComponent;d.iso8859=function(){d.encode=escape;d.decode=unescape};d.unicode=function(){d.encode=q;d.decode=decodeURIComponent};d.characters={pathname:{encode:{expression:/%(24|26|2B|2C|3B|3D|3A|40)/ig,map:{"%24":"$","%26":"&","%2B":"+","%2C":",","%3B":";","%3D":"=","%3A":":","%40":"@"}},decode:{expression:/[\/\?#]/g,map:{"/":"%2F","?":"%3F","#":"%23"}}},reserved:{encode:{expression:/%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,map:{"%3A":":","%2F":"/","%3F":"?","%23":"#","%5B":"[","%5D":"]","%40":"@","%21":"!","%24":"$","%26":"&","%27":"'","%28":"(","%29":")","%2A":"*","%2B":"+","%2C":",","%3B":";","%3D":"="}}}};d.encodeQuery=function(a){return d.encode(a+"").replace(/%20/g,"+")};d.decodeQuery=function(a){return d.decode((a+"").replace(/\+/g,"%20"))};d.recodePath=function(a){for(var a=(a+"").split("/"),b=0,c=a.length;be)return a[0]===b[0]&&"/"===a[0]?"/":"";"/"!==a[e]&&(e=a.substring(0,e).lastIndexOf("/"));return a.substring(0,e+1)};d.withinString=function(a,b){return a.replace(d.find_uri_expression,b)};d.ensureValidHostname=function(a){if(a.match(d.invalid_hostname_characters)){if(!p)throw new TypeError("Hostname '"+a+"' contains characters other than [A-Z0-9.-] and Punycode.js is not available");if(p.toASCII(a).match(d.invalid_hostname_characters))throw new TypeError("Hostname '"+a+"' contains characters other than [A-Z0-9.-]");}};f.build=function(a){if(!0===a)this._deferred_build=!0;else if(a===g||this._deferred_build)this._string=d.build(this._parts),this._deferred_build=!1;return this};f.clone=function(){return new d(this)};f.toString=function(){return this.build(!1)._string};f.valueOf=function(){return this.toString()};i={protocol:"protocol",username:"username",password:"password",hostname:"hostname",port:"port"};m=function(a){return function(b,c){if(b===g)return this._parts[a]||"";this._parts[a]=b;this.build(!c);return this}};for(h in i)f[h]=m(i[h]);i={query:"?",fragment:"#"};m=function(a,b){return function(c,e){if(c===g)return this._parts[a]||"";null!==c&&(c+="",c[0]===b&&(c=c.substring(1)));this._parts[a]=c;this.build(!e);return this}};for(h in i)f[h]=m(h,i[h]);i={search:["?","query"],hash:["#","fragment"]};m=function(a,b){return function(c,e){var d=this[a](c,e);return"string"===typeof d&&d.length?b+d:d}};for(h in i)f[h]=m(i[h][1],i[h][0]);f.pathname=function(a,b){if(a===g||!0===a){var c=this._parts.path||(this._parts.urn?"":"/");return a?d.decodePath(c):c}this._parts.path=a?d.recodePath(a):"/";this.build(!b);return this};f.path=f.pathname;f.href=function(a,b){if(a===g)return this.toString();this._string="";this._parts={protocol:null,username:null,password:null,hostname:null,urn:null,port:null,path:null,query:null,fragment:null};var c=a instanceof d,e="object"===typeof a&&(a.hostname||a.path),f;if("string"===typeof a)this._parts=d.parse(a);else if(c||e)for(f in c=c?a._parts:a,c)Object.hasOwnProperty.call(this._parts,f)&&(this._parts[f]=c[f]);else throw new TypeError("invalid input");this.build(!b);return this};f.is=function(a){var b=!1,c=!1,e=!1,f=!1,g=!1,h=!1,i=!1,j=!this._parts.urn;this._parts.hostname&&(j=!1,c=d.ip4_expression.test(this._parts.hostname),e=d.ip6_expression.test(this._parts.hostname),b=c||e,g=(f=!b)&&n&&n.has(this._parts.hostname),h=f&&d.idn_expression.test(this._parts.hostname),i=f&&d.punycode_expression.test(this._parts.hostname));switch(a.toLowerCase()){case "relative":return j;case "absolute":return!j;case "domain":case "name":return f;case "sld":return g;case "ip":return b;case "ip4":case "ipv4":case "inet4":return c;case "ip6":case "ipv6":case "inet6":return e;case "idn":return h;case "url":return!this._parts.urn;case "urn":return!!this._parts.urn;case "punycode":return i}return null};var s=f.protocol,t=f.port,u=f.hostname;f.protocol=function(a,b){if(a!==g&&a&&(a=a.replace(/:(\/\/)?$/,""),a.match(/[^a-zA-z0-9\.+-]/)))throw new TypeError("Protocol '"+a+"' contains characters other than [A-Z0-9.+-]");return s.call(this,a,b)};f.scheme=f.protocol;f.port=function(a,b){if(this._parts.urn)return a===g?"":this;if(a!==g&&(0===a&&(a=null),a&&(a+="",":"===a[0]&&(a=a.substring(1)),a.match(/[^0-9]/))))throw new TypeError("Port '"+a+"' contains characters other than [0-9]");return t.call(this,a,b)};f.hostname=function(a,b){if(this._parts.urn)return a===g?"":this;if(a!==g){var c={};d.parseHost(a,c);a=c.hostname}return u.call(this,a,b)};f.host=function(a,b){if(this._parts.urn)return a===g?"":this;if(a===g)return this._parts.hostname?d.buildHost(this._parts):"";d.parseHost(a,this._parts);this.build(!b);return this};f.authority=function(a,b){if(this._parts.urn)return a===g?"":this;if(a===g)return this._parts.hostname?d.buildAuthority(this._parts):"";d.parseAuthority(a,this._parts);this.build(!b);return this};f.userinfo=function(a,b){if(this._parts.urn)return a===g?"":this;if(a===g){if(!this._parts.username)return"";var c=d.buildUserinfo(this._parts);return c.substring(0,c.length-1)}"@"!==a[a.length-1]&&(a+="@");d.parseUserinfo(a,this._parts);this.build(!b);return this};f.subdomain=function(a,b){if(this._parts.urn)return a===g?"":this;if(a===g){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.length-this.domain().length-1;return this._parts.hostname.substring(0,c)||""}c=this._parts.hostname.length-this.domain().length;c=this._parts.hostname.substring(0,c);c=RegExp("^"+j(c));a&&"."!==a[a.length-1]&&(a+=".");a&&d.ensureValidHostname(a);this._parts.hostname=this._parts.hostname.replace(c,a);this.build(!b);return this};f.domain=function(a,b){if(this._parts.urn)return a===g?"":this;"boolean"===typeof a&&(b=a,a=g);if(a===g){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.match(/\./g);if(c&&2>c.length)return this._parts.hostname;c=this._parts.hostname.length-this.tld(b).length-1;c=this._parts.hostname.lastIndexOf(".",c-1)+1;return this._parts.hostname.substring(c)||""}if(!a)throw new TypeError("cannot set domain empty");d.ensureValidHostname(a);!this._parts.hostname||this.is("IP")?this._parts.hostname=a:(c=RegExp(j(this.domain())+"$"),this._parts.hostname=this._parts.hostname.replace(c,a));this.build(!b);return this};f.tld=function(a,b){if(this._parts.urn)return a===g?"":this;"boolean"===typeof a&&(b=a,a=g);if(a===g){if(!this._parts.hostname||this.is("IP"))return"";var c=this._parts.hostname.lastIndexOf("."),c=this._parts.hostname.substring(c+1);return!0!==b&&n&&n.list[c.toLowerCase()]?n.get(this._parts.hostname)||c:c}if(a)if(a.match(/[^a-zA-Z0-9-]/))if(n&&n.is(a))c=RegExp(j(this.tld())+"$"),this._parts.hostname=this._parts.hostname.replace(c,a);else throw new TypeError("TLD '"+a+"' contains characters other than [A-Z0-9]");else{if(!this._parts.hostname||this.is("IP"))throw new ReferenceError("cannot set TLD on non-domain host");c=RegExp(j(this.tld())+"$");this._parts.hostname=this._parts.hostname.replace(c,a)}else throw new TypeError("cannot set TLD empty");this.build(!b);return this};f.directory=function(a,b){if(this._parts.urn)return a===g?"":this;if(a===g||!0===a){if(!this._parts.path&&!this._parts.hostname)return"";if("/"===this._parts.path)return"/";var c=this._parts.path.length-this.filename().length-1,c=this._parts.path.substring(0,c)||(this._parts.hostname?"/":"");return a?d.decodePath(c):c}c=this._parts.path.length-this.filename().length;c=this._parts.path.substring(0,c);c=RegExp("^"+j(c));this.is("relative")||(a||(a="/"),"/"!==a[0]&&(a="/"+a));a&&"/"!==a[a.length-1]&&(a+="/");a=d.recodePath(a);this._parts.path=this._parts.path.replace(c,a);this.build(!b);return this};f.filename=function(a,b){if(this._parts.urn)return a===g?"":this;if(a===g||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var c=this._parts.path.lastIndexOf("/"),c=this._parts.path.substring(c+1);return a?d.decodePathSegment(c):c}c=!1;"/"===a[0]&&(a=a.substring(1));a.match(/\.?\//)&&(c=!0);var e=RegExp(j(this.filename())+"$"),a=d.recodePath(a);this._parts.path=this._parts.path.replace(e,a);c?this.normalizePath(b):this.build(!b);return this};f.suffix=function(a,b){if(this._parts.urn)return a===g?"":this;if(a===g||!0===a){if(!this._parts.path||"/"===this._parts.path)return"";var c=this.filename(),e=c.lastIndexOf(".");if(-1===e)return"";c=c.substring(e+1);c=/^[a-z0-9%]+$/i.test(c)?c:"";return a?d.decodePathSegment(c):c}"."===a[0]&&(a=a.substring(1));if(c=this.suffix())e=a?RegExp(j(c)+"$"):RegExp(j("."+c)+"$");else{if(!a)return this;this._parts.path+="."+d.recodePath(a)}e&&(a=d.recodePath(a),this._parts.path=this._parts.path.replace(e,a));this.build(!b);return this};f.segment=function(a,b,c){var e=this._parts.urn?":":"/",d=this.path(),f="/"===d.substring(0,1),d=d.split(e);"number"!==typeof a&&(c=b,b=a,a=g);if(a!==g&&"number"!==typeof a)throw Error("Bad segment '"+a+"', must be 0-based integer");f&&d.shift();0>a&&(a=Math.max(d.length+a,0));if(b===g)return a===g?d:d[a];if(null===a||d[a]===g)if(l(b))d=b;else{if(b||"string"===typeof b&&b.length)""===d[d.length-1]?d[d.length-1]=b:d.push(b)}else b||"string"===typeof b&&b.length?d[a]=b:d.splice(a,1);f&&d.unshift("");return this.path(d.join(e),c)};var v=f.query;f.query=function(a,b){return!0===a?d.parseQuery(this._parts.query):a!==g&&"string"!==typeof a?(this._parts.query=d.buildQuery(a),this.build(!b),this):v.call(this,a,b)};f.addQuery=function(a,b,c){var e=d.parseQuery(this._parts.query);d.addQuery(e,a,b);this._parts.query=d.buildQuery(e);"string"!==typeof a&&(c=b);this.build(!c);return this};f.removeQuery=function(a,b,c){var e=d.parseQuery(this._parts.query);d.removeQuery(e,a,b);this._parts.query=d.buildQuery(e);"string"!==typeof a&&(c=b);this.build(!c);return this};f.addSearch=f.addQuery;f.removeSearch=f.removeQuery;f.normalize=function(){return this._parts.urn?this.normalizeProtocol(!1).normalizeQuery(!1).normalizeFragment(!1).build():this.normalizeProtocol(!1).normalizeHostname(!1).normalizePort(!1).normalizePath(!1).normalizeQuery(!1).normalizeFragment(!1).build()};f.normalizeProtocol=function(a){"string"===typeof this._parts.protocol&&(this._parts.protocol=this._parts.protocol.toLowerCase(),this.build(!a));return this};f.normalizeHostname=function(a){this._parts.hostname&&(this.is("IDN")&&p?this._parts.hostname=p.toASCII(this._parts.hostname):this.is("IPv6")&&r&&(this._parts.hostname=r.best(this._parts.hostname)),this._parts.hostname=this._parts.hostname.toLowerCase(),this.build(!a));return this};f.normalizePort=function(a){"string"===typeof this._parts.protocol&&this._parts.port===d.defaultPorts[this._parts.protocol]&&(this._parts.port=null,this.build(!a));return this};f.normalizePath=function(a){if(this._parts.urn||!this._parts.path||"/"===this._parts.path)return this;var b,c,e=this._parts.path,f,g;"/"!==e[0]&&("."===e[0]&&(c=e.substring(0,e.indexOf("/"))),b=!0,e="/"+e);for(e=e.replace(/(\/(\.\/)+)|\/{2,}/g,"/");;){f=e.indexOf("/../");if(-1===f)break;else if(0===f){e=e.substring(3);break}g=e.substring(0,f).lastIndexOf("/");-1===g&&(g=f);e=e.substring(0,g)+e.substring(f+3)}b&&this.is("relative")&&(e=c?c+e:e.substring(1));e=d.recodePath(e);this._parts.path=e;this.build(!a);return this};f.normalizePathname=f.normalizePath;f.normalizeQuery=function(a){"string"===typeof this._parts.query&&(this._parts.query.length?this.query(d.parseQuery(this._parts.query)):this._parts.query=null,this.build(!a));return this};f.normalizeFragment=function(a){this._parts.fragment||(this._parts.fragment=null,this.build(!a));return this};f.normalizeSearch=f.normalizeQuery;f.normalizeHash=f.normalizeFragment;f.iso8859=function(){var a=d.encode,b=d.decode;d.encode=escape;d.decode=decodeURIComponent;this.normalize();d.encode=a;d.decode=b;return this};f.unicode=function(){var a=d.encode,b=d.decode;d.encode=q;d.decode=unescape;this.normalize();d.encode=a;d.decode=b;return this};f.readable=function(){var a=this.clone();a.username("").password("").normalize();var b="";a._parts.protocol&&(b+=a._parts.protocol+"://");a._parts.hostname&&(a.is("punycode")&&p?(b+=p.toUnicode(a._parts.hostname),a._parts.port&&(b+=":"+a._parts.port)):b+=a.host());a._parts.hostname&&(a._parts.path&&"/"!==a._parts.path[0])&&(b+="/");b+=a.path(!0);if(a._parts.query){for(var c="",e=0,f=a._parts.query.split("&"),h=f.length;e /bar 76 | while (/\/[^\/\.][^\/]*\/\.\.\//.test(href)) { 77 | href = href.replace(/\/[^\/\.][^\/]*\/\.\.\//, '/'); 78 | } 79 | 80 | return href; 81 | }, 82 | findCssIncludes = function () { 83 | var cssIncludes = [], 84 | links = document.getElementsByTagName('link'), 85 | styles = document.getElementsByTagName('style'), 86 | style, 87 | cssRule, 88 | i, 89 | href; 90 | 91 | function eachCssRule(stylesheet, callback) { 92 | var cssRules = styleSheet.rules || styleSheet.cssRules; // IE8 and below use .rules 93 | 94 | if (cssRules) { 95 | for (var j = 0 ; j < cssRules.length ; j += 1) { 96 | callback(cssRules[j]); 97 | } 98 | } 99 | } 100 | 101 | function eachStyleDeclaration(cssRule, callback) { 102 | // Style rule 103 | if (cssRule.type === 1) { 104 | callback(cssRule.style); 105 | } 106 | 107 | // Media blocks 108 | if (cssRule.type === 4) { 109 | eachCssRule(cssRule, function (cssRule) { 110 | eachStyleDeclaration(cssRule, callback); 111 | }); 112 | } 113 | } 114 | 115 | // Link tags with rel="stylesheet" 116 | for (i = 0; i < links.length; i += 1) { 117 | if (/\bstylesheet\b/i.test(links[i].getAttribute('rel'))) { 118 | href = cleanHref(links[i].getAttribute('href')); 119 | if (href) { 120 | cssIncludes.push({type: 'link', href: href, node: links[i]}); 121 | } 122 | } 123 | } 124 | 125 | for (i = 0 ; i < document.styleSheets.length ; i += 1) { 126 | var styleSheet = document.styleSheets[i], 127 | ownerNode = styleSheet.owningElement || styleSheet.ownerNode; 128 | 129 | if (styleSheet.href) { 130 | eachCssRule(styleSheet, function (cssRule) { 131 | // Look for .compilessinclude {src: url(...);} in non-inline stylesheets: 132 | if (/^\.compilessinclude$/.test(cssRule.selectorText)) { 133 | var backgroundImage = cssRule.style.backgroundImage || (cssRule.style.getPropertyValue && cssRule.style.getPropertyValue('background-image')) || cssRule.style.cssText, 134 | matchBackgroundImage = backgroundImage && backgroundImage.match(/url\((['"]|)(.*?)\1\)/); 135 | if (matchBackgroundImage) { 136 | href = cleanHref(styleSheet.href); 137 | var backgroundImageUrl = URI(matchBackgroundImage[2]).absoluteTo(ownerNode.getAttribute('href')).absoluteTo(location.href).toString(), 138 | watchHref = cleanHref(backgroundImageUrl); 139 | if (href && watchHref) { 140 | cssIncludes.push({type: 'link', href: href, watchHref: watchHref, node: ownerNode}); 141 | } 142 | } 143 | } 144 | }); 145 | } 146 | 147 | if (liveStyleOptions.watchCssImages) { 148 | var baseUrl = styleSheet.href || location.href; 149 | 150 | eachCssRule(styleSheet, function (cssRule) { 151 | eachStyleDeclaration(cssRule, function (style) { 152 | for (var k = 0 ; k < style.length ; k += 1) { 153 | var propertyName = style[k], 154 | value = style[propertyName], 155 | matchUrl = value.match(/url\((['"]|)(.*?)\1\)/), 156 | url = matchUrl && matchUrl[2]; 157 | if (url && !/^data:/.test(url)) { 158 | var cssImageUrl = URI(url).absoluteTo(baseUrl).absoluteTo(location.href).toString(), 159 | watchHref = cleanHref(cssImageUrl); 160 | if (href && watchHref) { 161 | cssIncludes.push({type: 'cssImage', href: href, watchHref: watchHref, cssRule: cssRule, propertyName: propertyName}); 162 | } 163 | } 164 | } 165 | }); 166 | }); 167 | } 168 | } 169 | 170 | // Style tags: @includes and inline prefixfree blocks 171 | for (i = 0 ; i < styles.length ; i += 1) { 172 | style = styles[i]; 173 | 174 | if (typeof StyleFix !== 'undefined') { 175 | // Prefixfree support 176 | href = cleanHref(style.href || style.getAttribute('data-href')); 177 | if (href) { 178 | cssIncludes.push({type: 'prefixfree', href: href, node: style}); 179 | } 180 | } 181 | 182 | // @import 183 | style.innerHTML.replace(/@import\s+(?:'([^']+)'|"([^"]+)"|url\(([^\)]+)\))/g, function ($0, singleQuotedHref, doubleQuotedHref, urlParenthesesHref) { 184 | if (urlParenthesesHref) { 185 | urlParenthesesHref = urlParenthesesHref.replace(/^(['"])(.*)\1$/, '$2'); 186 | } 187 | var verbatimHref = singleQuotedHref || doubleQuotedHref || urlParenthesesHref, 188 | href = cleanHref(singleQuotedHref || doubleQuotedHref || urlParenthesesHref); 189 | if (href) { 190 | cssIncludes.push({type: 'import', href: href, verbatimHref: verbatimHref, styleElement: style}); 191 | } 192 | }); 193 | } 194 | 195 | return cssIncludes; 196 | }, 197 | removeCacheBuster = function (href) { 198 | return href.replace(/[?&]livestyle=\d+/, ''); 199 | }, 200 | addCacheBuster = function (href) { 201 | return href + (href.indexOf('?') === -1 ? '?' : '&') + 'livestyle=' + new Date().getTime(); 202 | }, 203 | 204 | // Replaces a link tag with an updated version. Just replacing the href would cause FOUC. 205 | // We insert the new node before the old one and remove the old one after the new one has loaded. 206 | replaceLinkTag = function (node, href, watchHref) { 207 | isBusyByHref[href] = (isBusyByHref[href] || 0) + 1; 208 | if (watchHref) { 209 | isBusyByHref[watchHref] = (isBusyByHref[watchHref] || 0) + 1; 210 | } 211 | var parent = node.parentNode, 212 | newNode = node.cloneNode(true), 213 | monitor; 214 | 215 | newNode.removeAttribute('href'); 216 | 217 | // The node must be added to the document before the href is set, otherwise IE9 won't 218 | // apply the styles. 219 | if (node.nextSibling) { 220 | parent.insertBefore(newNode, node.nextSibling); 221 | } else { 222 | parent.appendChild(newNode); 223 | } 224 | 225 | newNode.setAttribute('href', href); 226 | newNode.onload = function () { 227 | newNode.onload = null; 228 | isBusyByHref[href] -= 1; 229 | if (watchHref) { 230 | isBusyByHref[watchHref] -= 1; 231 | } 232 | if (node.parentNode) { 233 | parent.removeChild(node); 234 | } 235 | if (monitor) { 236 | clearInterval(monitor); 237 | monitor = null; 238 | } 239 | // There may be additional occurrences of this href in changedHrefsQueue that can be processed 240 | // now that the busy counter was decremented: 241 | processNextChangedHref(); 242 | }; 243 | monitor = setInterval(function () { 244 | var isReady; 245 | try { 246 | isReady = newNode.sheet && newNode.sheet.cssRules.length > 0; // Throws an error if the stylesheet hasn't loaded 247 | } catch (err) {} 248 | if (isReady) { 249 | newNode.onload(); 250 | } 251 | }, 20); 252 | }, 253 | replaceStyleTag = function (node, verbatimOldHref, href) { 254 | var parent = node.parentNode, 255 | newNode = node.cloneNode(), 256 | replacerRegExp = new RegExp("@import\\s+url\\([\"']?" + verbatimOldHref.replace(/[\.\?\[\]\(\)\{\}]/g, "\\$&") + "[\"']?\\);?"); 257 | newNode.textContent = node.textContent.replace(replacerRegExp, '@import url(\'' + addCacheBuster(removeCacheBuster(href)) + '\');').replace(/^\s*|\s*$/g, ''); 258 | parent.insertBefore(newNode, node); 259 | parent.removeChild(node); 260 | }, 261 | processNextChangedHref = function () { 262 | var i, 263 | href; 264 | // Find the first non-busy href in changedHrefsQueue: 265 | for (i = 0 ; i < changedHrefsQueue.length ; i += 1) { 266 | if (isBusyByHref[changedHrefsQueue[i]]) { 267 | if (liveStyleOptions.debug) { 268 | log("Postponing 'change' notification on stylesheet that's already being refreshed: " + changedHrefsQueue[i]); 269 | } 270 | } else { 271 | href = changedHrefsQueue.splice(i, 1)[0]; 272 | break; 273 | } 274 | } 275 | if (!href) { 276 | return; 277 | } 278 | 279 | var cssIncludes = findCssIncludes(), 280 | newHref, 281 | replacerRegExp; 282 | 283 | if (href === location.pathname) { 284 | location.reload(true); 285 | } 286 | 287 | for (i = 0; i < cssIncludes.length; i += 1) { 288 | var cssInclude = cssIncludes[i], 289 | matchAgainstHref = removeCacheBuster(cssInclude.watchHref || cssInclude.href); 290 | 291 | if (matchAgainstHref === href) { 292 | log('Refreshing ' + cssInclude.href); 293 | if (cssInclude.type === 'link') { 294 | // Less.js support (https://github.com/cloudhead/less.js) 295 | if (/\bstylesheet\/less\b/i.test(cssInclude.node.getAttribute('rel')) && typeof less !== 'undefined') { 296 | // Sadly this method isn't accessible 297 | // less.loadStyleSheet(cssInclude.node, function () {}, false, 0); 298 | // So instead we'll just have to brutally refresh ALL less includes 299 | less.refresh(); 300 | } else { 301 | replaceLinkTag(cssInclude.node, cssInclude.href, cssInclude.watchHref); 302 | } 303 | } else if (cssInclude.type === 'import') { 304 | replaceStyleTag(cssInclude.styleElement, cssInclude.verbatimHref, href); 305 | } else if (cssInclude.type === 'cssImage') { 306 | var value = cssInclude.cssRule.style[cssInclude.propertyName]; 307 | value = value.replace(/url\((['"]|)(.*?)\1\)/g, function ($0, quoteChar, url) { 308 | return "url(" + quoteChar + addCacheBuster(url) + quoteChar + ")"; 309 | }); 310 | cssInclude.cssRule.style.setProperty(cssInclude.propertyName, value, cssInclude.cssRule.style.getPropertyPriority(cssInclude.propertyName)); 311 | 312 | } else if (cssInclude.type === 'prefixfree') { 313 | // The next two lines are hacks to make Prefixfree think this is a link and not a style block 314 | cssInclude.node.setAttribute('href', href); // No cache buster needed 315 | cssInclude.node.rel = 'stylesheet'; 316 | StyleFix.link(cssInclude.node); 317 | } 318 | 319 | // Replacing the first occurrence should be good enough. Besides, the @import replacement code invalidates 320 | // the rest of the cssIncludes in the same stylesheet. 321 | break; 322 | } 323 | } 324 | processNextChangedHref(); // We consumed an item from changedHrefsQueue, keep going 325 | }, 326 | startListening = function () { 327 | var socket = io.connect(); 328 | 329 | socket.on('connect', function () { 330 | var hrefs = [], 331 | watchNewStylesheets = function () { 332 | var cssIncludes = findCssIncludes(), 333 | toWatch = [], 334 | href, 335 | i; 336 | 337 | if (liveStyleOptions.watchHtml) { 338 | cssIncludes.unshift({type: 'html', href: location.pathname}); // See https://github.com/One-com/livestyle/issues/11 339 | } 340 | for (i = 0; i < cssIncludes.length; i += 1) { 341 | href = removeCacheBuster(cssIncludes[i].watchHref || cssIncludes[i].href); 342 | if (hrefs.indexOf(href) === -1) { 343 | hrefs.push(href); 344 | toWatch.push(href); 345 | } 346 | } 347 | 348 | if (toWatch.length !== 0) { 349 | log('Subscribing to ' + toWatch.length + ' files:\n ' + toWatch.join('\n ')); 350 | socket.emit('watch', toWatch); 351 | } 352 | }; 353 | 354 | watchNewStylesheets(); 355 | setInterval(watchNewStylesheets, pollTimeout); 356 | }).on('change', function (href) { 357 | if (changedHrefsQueue.indexOf(href) === -1) { 358 | changedHrefsQueue.push(href); 359 | log('Received change notification for ' + href + ', queued'); 360 | processNextChangedHref(); 361 | } else { 362 | log('Received change notification for ' + href + ', skipped (already in queue)'); 363 | } 364 | }); 365 | }, 366 | state = {}, 367 | startPolling = function () { 368 | var getXHR = function () { 369 | try { 370 | return new XMLHttpRequest(); 371 | } catch (e1) { 372 | try { 373 | return new ActiveXObject("Msxml2.XMLHTTP.6.0"); 374 | } catch (e2) { 375 | try { 376 | return new ActiveXObject("Msxml2.XMLHTTP.3.0"); 377 | } catch (e3) { 378 | try { 379 | return new ActiveXObject("Msxml2.XMLHTTP"); 380 | } catch (e4) { 381 | try { 382 | return new ActiveXObject("Microsoft.XMLHTTP"); 383 | } catch (e5) {} 384 | } 385 | } 386 | } 387 | } 388 | 389 | return null; // no XHR support 390 | }, 391 | cssIncludes = findCssIncludes(), 392 | proceed = function () { 393 | var cssInclude, 394 | url, 395 | xhr; 396 | 397 | if (cssIncludes.length > 0) { 398 | cssInclude = cssIncludes.shift(); 399 | url = removeCacheBuster(cssInclude.href); 400 | xhr = getXHR(); 401 | 402 | xhr.onreadystatechange = function () { 403 | var lastModified, 404 | etag; 405 | 406 | if (xhr.readyState === 4) { 407 | if (xhr.status === 200) { 408 | lastModified = Date.parse(xhr.getResponseHeader("Last-Modified")); 409 | etag = xhr.getResponseHeader("ETag"); 410 | 411 | if (!state[url] || state[url].lastModified < lastModified || state[url].etag !== etag) { 412 | state[url] = { 413 | lastModified: lastModified, 414 | etag: etag 415 | }; 416 | refresh(url); 417 | } 418 | } 419 | 420 | proceed(); 421 | } 422 | }; 423 | xhr.open('HEAD', addCacheBuster(url), true); 424 | xhr.send(null); 425 | } else { 426 | setTimeout(startPolling, pollTimeout); 427 | } 428 | }; 429 | if (liveStyleOptions.watchHtml) { 430 | cssIncludes.unshift({type: 'html', href: location.pathname}); // See https://github.com/One-com/livestyle/issues/11 431 | } 432 | proceed(); 433 | }; 434 | 435 | if (typeof io !== 'undefined') { 436 | log('socket.io present, connecting'); 437 | startListening(); 438 | } else { 439 | log('socket.io not present, falling back to polling mode'); 440 | startPolling(); 441 | } 442 | }()); 443 | --------------------------------------------------------------------------------