From 10ebf8abf2db5a44287601142d6e01bd54795379 Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Mon, 20 Feb 2012 16:21:50 -0800 Subject: [PATCH 01/31] fixing src checking when inline scripts exist on the page --- src/client/vogue-client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/vogue-client.js b/src/client/vogue-client.js index 28a480a..be3e8e3 100644 --- a/src/client/vogue-client.js +++ b/src/client/vogue-client.js @@ -230,7 +230,7 @@ scripts = document.getElementsByTagName("script"); for (var i=0; i < scripts.length; i++) { src = scripts[i].getAttribute("src"); - if (src.slice(-15) === 'vogue-client.js') break; + if (src && src.slice(-15) === 'vogue-client.js') break; } rootUrl = src.match(/^https?\:\/\/(.*?)\//)[0]; // There is an optional base argument, that can be used. From 3cd5a88eaffa7757aeb48cecf25b8f220d40a149 Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Mon, 20 Feb 2012 19:26:03 -0800 Subject: [PATCH 02/31] doing everything the ultra-dumb, low-performance way. Now, any time any file changes, every CSS file gets reloaded. So if there are server-side includes, we don't have to track their internal dependencies --- src/VogueClient.js | 58 -------------------------- src/Watcher.js | 83 -------------------------------------- src/client/vogue-client.js | 52 ++---------------------- src/vogue.js | 44 +++++++++++++++++--- 4 files changed, 42 insertions(+), 195 deletions(-) delete mode 100644 src/VogueClient.js delete mode 100644 src/Watcher.js diff --git a/src/VogueClient.js b/src/VogueClient.js deleted file mode 100644 index 02dc0d0..0000000 --- a/src/VogueClient.js +++ /dev/null @@ -1,58 +0,0 @@ -var fs = require('fs'); - -exports.VogueClient = VogueClient; - -// Encapsulates a web socket client connection from a web browser. -function VogueClient(clientSocket, watcher) { - this.socket = clientSocket; - this.watcher = watcher; - this.watchedFiles = {}; - clientSocket.on('watch', this.handleMessage.bind(this)); - clientSocket.on('disconnect', this.disconnect.bind(this)); -} - -// Parse an incoming message from the client and dispatch accordingly. -VogueClient.prototype.handleMessage = function(data) { - this.watchFile(data.href); -}; - -VogueClient.prototype.watchFile = function(href) { - var filename = this.watcher.getFilenameForHref(href); - fs.stat(filename, function(err, stats) { - if (err) { - console.log('Could not read stats for ' + filename); - return; - } - - this.watchedFiles[filename] = { - href: href, - mtime: stats.mtime - }; - this.watcher.startWatching(filename); - }.bind(this)); -}; - -VogueClient.prototype.updateFile = function(filename) { - var fileInfo = this.watchedFiles[filename]; - if (fileInfo) { - fs.stat(filename, function(err, stats) { - if (err) { - console.error('Could not read stats for file: ' + filename); - return; - } - // Only send message to client if the file was modified - // since we last saw it. - if (fileInfo.mtime < stats.mtime) { - this.socket.emit('update', { href: fileInfo.href }); - fileInfo.mtime = stats.mtime; - } - }.bind(this)); - } -}; - -VogueClient.prototype.disconnect = function() { - for (var filename in this.watchedFiles) { - this.watcher.stopWatching(filename); - } - this.watcher.removeClient(this); -}; diff --git a/src/Watcher.js b/src/Watcher.js deleted file mode 100644 index 75453f2..0000000 --- a/src/Watcher.js +++ /dev/null @@ -1,83 +0,0 @@ -var fs = require('fs') - , path = require('path'); - -exports.Watcher = Watcher; - -function Watcher(webDirectory, rewrite) { - this.webDirectory = webDirectory; - // array of VogueClient objects - this.clients = []; - - // filename -> number_of_clients_watching - this.fileWatcherCount = {}; - - if (rewrite) { - this.rewriteUrlToPath = createRewriter(rewrite); - } - - function createRewriter(rewrite) { - var parts = rewrite.split(':'); - if (parts.length === 2) { - var regex = new RegExp(parts[0]); - var replacement = parts[1]; - return function (str) { - return str.replace(regex, replacement); - } - } else { - throw new Error('Rewrite must be of the form "regex:replacement".'); - } - } -} - -Watcher.prototype.addClient = function(client) { - this.clients.push(client); -}; - -Watcher.prototype.removeClient = function(client) { - this.clients.splice(this.clients.indexOf(client), 1); -}; - -Watcher.prototype.getFilenameForHref = function(href) { - if (this.rewriteUrlToPath) { - href = this.rewriteUrlToPath(href); - } - // Remove any querystring junk. - // e.g. "foo/bar.css?abc=123" --> "foo/bar.css" - href = href.split('?')[0]; - var filename = path.join(this.webDirectory, href); - return filename; -}; - -Watcher.prototype.startWatching = function(filename) { - console.log('Watching file: ' + filename); - if (filename in this.fileWatcherCount) { - // already watching this file, so just increment the client count. - this.fileWatcherCount[filename]++; - } else { - fs.watchFile( - filename, - { persistent: true, interval: 50 }, - fileChanged.bind(this) - ); - this.fileWatcherCount[filename] = 1; - } - - function fileChanged() { - console.log('File changed: ' + filename); - this.clients.forEach(function(client) { - client.updateFile(filename); - }); - } -}; - -Watcher.prototype.stopWatching = function(filename) { - if (!(filename in this.fileWatcherCount)) return; - - var watcherCount = --this.fileWatcherCount[filename]; - if (watcherCount == 0) { - delete this.fileWatcherCount[filename]; - fs.unwatchFile(filename); - console.log('Stopped watching file: ' + filename); - } -} - diff --git a/src/client/vogue-client.js b/src/client/vogue-client.js index be3e8e3..7ca8208 100644 --- a/src/client/vogue-client.js +++ b/src/client/vogue-client.js @@ -9,56 +9,13 @@ var stylesheets, socket = io.connect(script.rootUrl); - /** - * Watch for all available stylesheets. - */ - function watchAllStylesheets() { - var href; + function updateAllStylesheets() { for (href in stylesheets) { if (hop.call(stylesheets, href)) { - socket.emit("watch", { - href: href - }); + stylesheets[href].href += (stylesheets[href].href.indexOf('?') === -1 ? "?" : "&")+"_vogue_rand="+Math.random(); } } - } - - /** - * Reload a stylesheet. - * - * @param {String} href The URL of the stylesheet to be reloaded. - */ - function reloadStylesheet(href) { - var newHref = stylesheets[href].href + - (href.indexOf("?") >= 0 ? "&" : "?") + - "_vogue_nocache=" + (new Date).getTime(), - stylesheet; - // Check if the appropriate DOM Node is there. - if (!stylesheets[href].setAttribute) { - // Create the link. - stylesheet = document.createElement("link"); - stylesheet.setAttribute("rel", "stylesheet"); - stylesheet.setAttribute("href", newHref); - head.appendChild(stylesheet); - - // Update the reference to the newly created link. - stylesheets[href] = stylesheet; - } else { - // Update the href to the new URL. - stylesheets[href].href = newHref; - } - } - - - /** - * Handle messages from socket.io, and load the appropriate stylesheet. - * - * @param message Socket.io message object. - * @param message.href The url of the stylesheet to be loaded. - */ - function handleMessage(message) { - reloadStylesheet(message.href); - } + } /** * Fetch all the local stylesheets from the page. @@ -162,8 +119,7 @@ } stylesheets = getLocalStylesheets(); - socket.on("connect", watchAllStylesheets); - socket.on("update", handleMessage); + socket.on("update", updateAllStylesheets); } /** diff --git a/src/vogue.js b/src/vogue.js index c511056..01ace95 100755 --- a/src/vogue.js +++ b/src/vogue.js @@ -16,22 +16,54 @@ var http = require('http') , opt = require('parseopt') , io = require('socket.io'); -var VogueClient = require('./VogueClient').VogueClient - , Watcher = require('./Watcher').Watcher; +// var VogueClient = require('./VogueClient').VogueClient + // , Watcher = require('./Watcher').Watcher; var options = getOptions() , server = http.createServer(handleHttpRequest) , socket = io.listen(server) - , watcher = new Watcher(options.webDirectory,options.rewrite); + // , watcher = new Watcher(options.webDirectory,options.rewrite); server.listen(options.port); -socket.sockets.on('connection', function(clientSocket) { - watcher.addClient(new VogueClient(clientSocket, watcher)); -}); console.log('Watching directory: ' + options.webDirectory); console.log('Listening for clients: http://localhost:' + options.port + '/'); +var walk = function(dir, done) { + var results = []; + fs.readdir(dir, function(err, list) { + if (err) return done(err); + var i = 0; + (function next() { + var file = list[i++]; + if (!file) return done(null, results); + file = dir + '/' + file; + fs.stat(file, function(err, stat) { + if (stat && stat.isDirectory()) { + walk(file, function(err, res) { + results = results.concat(res); + next(); + }); + } else { + results.push(file); + next(); + } + }); + })(); + }); +}; + +// watch every file in the whole directory we're put to +walk(options.webDirectory, function(err, list) { + list.forEach(function(file) { + fs.watchFile(file, {interval: 300}, function(cur, prev) { + if (cur.mtime.toString() != prev.mtime.toString()) { + socket.sockets.emit('update'); + } + }); + }) + console.log('Now watching '+list.length+' files'); +}) function handleHttpRequest(request, response) { var pathname = url.parse(request.url).pathname; From b15e194acf7ba193ffb351e77d7e45f371f6664f Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Mon, 20 Feb 2012 23:30:29 -0800 Subject: [PATCH 03/31] use ajax to get new page --- src/client/vogue-client.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/client/vogue-client.js b/src/client/vogue-client.js index 7ca8208..de9cda3 100644 --- a/src/client/vogue-client.js +++ b/src/client/vogue-client.js @@ -12,7 +12,24 @@ function updateAllStylesheets() { for (href in stylesheets) { if (hop.call(stylesheets, href)) { - stylesheets[href].href += (stylesheets[href].href.indexOf('?') === -1 ? "?" : "&")+"_vogue_rand="+Math.random(); + // use ajax to download contents of CSS + // and create a "; + stylesheets[this.base_href].parentNode.removeChild(stylesheets[this.base_href]); + stylesheets[this.base_href] = div; + // make it look like a tag + div.href = this.base_href; + document.body.appendChild(div); + } + } + ajax.open("GET", new_url, true); + ajax.send(null); } } } From d1ced8fa53466458d7bf5b7305715c1f144c5433 Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Wed, 22 Feb 2012 02:05:33 -0800 Subject: [PATCH 04/31] supporting ie8 dynamic stylesheets --- src/client/vogue-client.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/client/vogue-client.js b/src/client/vogue-client.js index de9cda3..2281a51 100644 --- a/src/client/vogue-client.js +++ b/src/client/vogue-client.js @@ -19,13 +19,21 @@ ajax.base_href = href; ajax.onreadystatechange = function(){ if (this.readyState == 4){ - var div = document.createElement('div'); - div.innerHTML = ""; stylesheets[this.base_href].parentNode.removeChild(stylesheets[this.base_href]); - stylesheets[this.base_href] = div; + + // http://www.phpied.com/dynamic-script-and-style-elements-in-ie/ + var sheet = document.createElement('style'); + + sheet.setAttribute("type", "text/css"); + document.getElementsByTagName('head')[0].appendChild(sheet); + if (sheet.styleSheet) { // IE + sheet.styleSheet.cssText = this.responseText; + } else { // the world + sheet.appendChild(document.createTextNode(this.responseText)); + } + stylesheets[this.base_href] = sheet; // make it look like a tag - div.href = this.base_href; - document.body.appendChild(div); + sheet.href = this.base_href; } } ajax.open("GET", new_url, true); From b5eb282f7ced17592b42b53e645290676b173570 Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Tue, 6 Mar 2012 09:11:59 -0800 Subject: [PATCH 05/31] removing about page --- src/client/about.htm | 36 ------------------------------------ src/vogue.js | 10 ---------- 2 files changed, 46 deletions(-) delete mode 100644 src/client/about.htm diff --git a/src/client/about.htm b/src/client/about.htm deleted file mode 100644 index c8f0c9c..0000000 --- a/src/client/about.htm +++ /dev/null @@ -1,36 +0,0 @@ - - - - Vogue - - -

Vogue

-

- Add this bookmarklet to your bookmarks. It will inject Vogue's scripts - into the currently running page. -

-

- Inject Vogue -

-

- Or, add the following script into your HTML. -

-
<script src="http://localhost:{port}/vogue-client.js" type="text/javascript"></script>
-

Note: This should only appear in development-time code. Remove before going into production.

- - - diff --git a/src/vogue.js b/src/vogue.js index 01ace95..5f74323 100755 --- a/src/vogue.js +++ b/src/vogue.js @@ -74,16 +74,6 @@ function handleHttpRequest(request, response) { } } -function sendAboutPage(response) { - fs.readFile(__dirname + '/client/about.htm', function(e, fileData) { - var html = fileData.toString(); - html = html.replace(/\{port\}/g, options.port.toString()); - response.writeHead(200, { 'Content-Type': 'text/html' }); - response.write(html); - response.end(); - }); -} - function sendVogueClient(response) { fs.readFile(__dirname + '/client/vogue-client.js', function(e, fileData) { var script = fileData.toString(); From 47d2376cbc5b073f95866ae4d1a9a62b2c8721bf Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Tue, 6 Mar 2012 10:34:03 -0800 Subject: [PATCH 06/31] upgrading socketio to 0.9 --- src/package.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/package.json b/src/package.json index d962859..6eb90b4 100644 --- a/src/package.json +++ b/src/package.json @@ -1,10 +1,13 @@ { "name": "vogue", "description": "Auto-reload stylesheets in web browser whenever the CSS files are saved.", - "version": "0.4.3", - "homepage": "http://github.com/andrewdavey/vogue", - "repository": "http://github.com/andrewdavey/vogue.git", + "version": "0.5", + "homepage": "http://github.com/quizlet/vogue", + "repository": "http://github.com/quizlet/vogue.git", "author": "Andrew Davey (http://aboutcode.net/)", + "contributors": [ + { "name": "Andrew Sutherland", "email": "andrew@quizlet.com"}, + ], "directories": { "lib": "" }, @@ -15,7 +18,7 @@ "vogue": "./vogue.js" }, "dependencies": { - "socket.io": "0.7.x", + "socket.io": "0.9.x", "parseopt": ">=1.0.0" } } From 51085c60dbeb4cf007f9a23bb306fce73c246b65 Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Tue, 6 Mar 2012 11:12:09 -0800 Subject: [PATCH 07/31] supporting SSL pages (supply a cert via command line), making interval watching smarter by having a slow default refresh rate but upping it when you first change a file --- src/client/vogue-client.js | 60 +++++++++---------- src/vogue.js | 120 +++++++++++++++++++++++++++---------- 2 files changed, 117 insertions(+), 63 deletions(-) diff --git a/src/client/vogue-client.js b/src/client/vogue-client.js index 2281a51..5f85c73 100644 --- a/src/client/vogue-client.js +++ b/src/client/vogue-client.js @@ -10,36 +10,36 @@ socket = io.connect(script.rootUrl); function updateAllStylesheets() { - for (href in stylesheets) { - if (hop.call(stylesheets, href)) { - // use ajax to download contents of CSS - // and create a