Skip to content
Browse files

Initial commit

  • Loading branch information...
1 parent 280019d commit 70a6389970d3f2f2696392ef37a49605f3d73e46 @serby committed
Showing with 483 additions and 0 deletions.
  1. +32 −0 Cakefile
  2. +75 −0 README.md
  3. +150 −0 lib/compact.js
  4. +29 −0 package.json
  5. +3 −0 test/assets/a.js
  6. +1 −0 test/assets/b.js
  7. +1 −0 test/assets/c.js
  8. +192 −0 test/compact.test.js
View
32 Cakefile
@@ -0,0 +1,32 @@
+fs = require 'fs'
+exec = (command, callback) ->
+ console.log 'Excecuting \'' + command + '\''
+ require('child_process').exec command, callback
+
+option '-f', '--file [FILE]', 'Use file in task'
+
+task 'delintAll', 'Runs jshint on all js code', (options) ->
+
+ paths = 'lib test'
+ if options.file
+ paths = options.file
+
+ exec 'jshint ' + paths, execOutput
+
+task 'delint', 'Runs all modified or added files through jshint', (options) ->
+ exec 'jshint `git status --porcelain | sed -e "s/^...//g"`', execOutput
+
+task 'npm-publish', 'Creates a tag based on version number in package.json then pushes and publishes to NPM', (options) ->
+ version = JSON.parse(fs.readFileSync('./package.json')).version
+ log 'Publishing ' + version
+ exec 'git tag ' + version, execOutput
+ exec 'git push --tags', execOutput
+ exec 'npm publish', execOutput
+
+execOutput = (error, stout, sterr) ->
+ if sterr
+ console.warn sterr
+ if stout
+ console.log stout
+
+log = console.log
View
75 README.md
@@ -0,0 +1,75 @@
+# compact.js - A simple JavaScript and CSS compacting middleware for express
+
+## Installation
+
+ npm install compact
+
+## Usage
+
+```js
+
+var compact = require('compact').createCompact(
+__dirname + '/public/src/',
+__dirname + '/public/compact/',
+ '/js/compact/'
+);
+
+compact.addNamespace('global');
+
+compact.ns.global
+ .addJs('/js/main.js')
+ .addJs('/js/widget-a.js')
+ .addJs('/js/widget-b.js');
+
+compact.addNamespace('home')
+ .addJs('/js/banner.js')
+ .addJs('/js/ads.js');
+
+compact.addNamespace('profile')
+ .addJs('/js/profile.js');
+
+compact.addNamespace('comments')
+ .addJs('/js/paging.js');
+ .addJs('/js/comments.js');
+
+// All routes will have global
+app.use(compact.js(['global']))
+
+// Add some compacted JavaScript for just this route. Having the namespaces
+// in separate arrays will produce a javascript file per array.
+app.get('/', compact.js(['home'], ['profile']));
+
+// Having different namespaces joined together will combine and output as one
+// javascript file.
+app.get('/blog', compact.js(['comments', 'profile']));
+
+```
+
+Then in the view use the **compactJsHtml()** view helper
+
+```html
+#{compactJsHtml()}
+```
+On / you'd get the following
+
+```html
+<script src="/js/compact/global.js"></script>
+<script src="/js/compact/home.js"></script>
+<script src="/js/compact/profile.js"></script>
+```
+
+On /blog you'd get this
+
+```html
+<script src="/js/compact/global.js"></script>
+<script src="/js/compact/comment-profile.js"></script>
+```
+
+You also have access to the **compactJs()** helper which will return an array
+of files for you to include on the page.
+
+## Credits
+[Paul Serby](https://github.com/serby/)
+
+## Licence
+Licenced under the [New BSD License](http://opensource.org/licenses/bsd-license.php)
View
150 lib/compact.js
@@ -0,0 +1,150 @@
+var path = require('path')
+ , fs = require('fs')
+ , async = require('async')
+ , _ = require('underscore')
+ , parser = require("uglify-js").parser
+ , uglifyer = require("uglify-js").uglify;
+
+module.exports.createCompact = function(sourcePath, destinationPath, webPath) {
+
+
+ if (!path.existsSync(sourcePath)) {
+ throw new Error('Invalid source path \'' + sourcePath + '\'');
+ }
+
+ if (!path.existsSync(destinationPath)) {
+ throw new Error('Invalid destination path \'' + destinationPath + '\'');
+ }
+
+ var namespaces = Object.create(null)
+ , compactedJs = []
+ , namespaceGroupsCache = {}
+ , compressOperationCache = {};
+
+ webPath = webPath || '';
+
+ function addNamespace(name) {
+
+ if (!name) {
+ throw new Error('Invalid namespace');
+ }
+
+ if (!namespaces[name]) {
+ var newNamespace = {};
+ Object.defineProperty(namespaces, name, {
+ get: function() { return newNamespace; },
+ configurable: false,
+ enumerable: true,
+ set: function(value) {
+ throw new Error('You can not alter a registered namespace \'' + name + '\''); }
+ });
+ }
+ var namespace = namespaces[name];
+
+ function addJs(filePath) {
+ if (!namespace.javascriptFiles) {
+ namespace.javascriptFiles = [];
+ }
+ namespace.javascriptFiles.push(path.normalize(sourcePath + '/' + filePath));
+
+ return namespace;
+ }
+
+ namespace.addJs = addJs;
+
+ return namespace;
+ }
+
+ function compressAndWriteJavascript(targetNamespaces, callback) {
+ var compressedData = ''
+ , javascriptFiles = [];
+
+ var compactFilename = targetNamespaces.map(function(namespace) {
+ return namespace;
+ }).join('-') + '.js'
+ , outputFilename = destinationPath + '/' + compactFilename;
+
+ // Only compress and write 'compactFilename' once
+ if (compressOperationCache[compactFilename]) {
+ callback(undefined, outputFilename);
+ }
+
+ targetNamespaces.forEach(function(namespace) {
+ javascriptFiles = javascriptFiles.concat(namespaces[namespace].javascriptFiles);
+ });
+
+ javascriptFiles = _.uniq(javascriptFiles);
+
+ async.concat(javascriptFiles, fs.readFile, function(error, contents) {
+
+ if (error) {
+ return callback(error);
+ }
+
+ fs.writeFile(outputFilename, compress(contents.join('')), 'utf-8', function(error) {
+ if (error) {
+ return callback(error);
+ }
+ compactedJs.push(path.normalize(webPath + '/' + compactFilename));
+ compressOperationCache[compactFilename] = true;
+ callback(undefined, outputFilename);
+ });
+ });
+ }
+
+ function compress(data) {
+ var ast = parser.parse(data);
+ ast = uglifyer.ast_mangle(ast);
+ ast = uglifyer.ast_squeeze(ast);
+ return uglifyer.gen_code(ast);
+ }
+
+ function processNamespace(namespace) {
+ if (!namespaces[namespace]) {
+ throw new RangeError('Unknown namespace \'' + namespace + '\'');
+ }
+ }
+
+ function compactJavascript() {
+ if (arguments.length === 0) {
+ throw new Error('You must pass one or more arrays containing valid namespace names');
+ }
+ var namespaceGroups = Array.prototype.slice.call(arguments);
+
+ compactedJs = [];
+
+ return function(req, res, next) {
+ // Only do the work once for each namespaceGroups
+ var hash = namespaceGroups.join('|');
+ if (namespaceGroupsCache[hash]) {
+ next();
+ }
+
+
+ async.forEach(namespaceGroups, compressAndWriteJavascript, function(error) {
+ if (error) {
+ return next(error);
+ }
+
+ req.app.helpers({
+ compactJs: function() {
+ return compactedJs;
+ },
+ compactJsHtml: function() {
+ return compactedJs.map(function(filename) {
+ return '<script src="/' + filename + '"></script>';
+ }).join('');
+ },
+ });
+ namespaceGroupsCache[hash] = true;
+ next();
+ });
+ };
+ }
+
+ return {
+ addNamespace: addNamespace,
+ js: compactJavascript,
+ ns: namespaces
+ };
+};
View
29 package.json
@@ -0,0 +1,29 @@
+{
+ "author": "Paul Serby <paul@serby.net>",
+ "name": "compact",
+ "description": "A JavaScript and CSS compacting middleware for express",
+ "version": "0.0.1",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/serby/compact.git"
+ },
+ "main": "./lib/compact.js",
+ "scripts": {
+ "test": "mocha -r should -R spec"
+ },
+ "engines": {
+ "node": ">=0.4 <0.7"
+ },
+ "dependencies": {
+ "underscore": "~1.3",
+ "async": "~0.1",
+ "uglify-js": "~1.2"
+ },
+ "devDependencies": {
+ "mocha": "*",
+ "should": "*",
+ "mkdirp": "~0.3",
+ "async": "~0.1",
+ "asyncjs": "~0.0"
+ }
+}
View
3 test/assets/a.js
@@ -0,0 +1,3 @@
+var a = 1;
+
+a = 10;
View
1 test/assets/b.js
@@ -0,0 +1 @@
+var b = 3;
View
1 test/assets/c.js
@@ -0,0 +1 @@
+var c = 5;
View
192 test/compact.test.js
@@ -0,0 +1,192 @@
+var fs = require('fs')
+ , mkdirp = require('mkdirp')
+ , async = require('asyncjs');
+
+
+var srcPath = __dirname + '/assets'
+ , destPath = __dirname + '/tmp';
+
+function createFiles(done) {
+ mkdirp(destPath, done);
+}
+
+function removeFiles(done) {
+ async.rmtree(destPath, done);
+}
+
+describe('compact.js', function() {
+
+ beforeEach(createFiles);
+ afterEach(removeFiles);
+
+ describe('#createCompact()', function() {
+
+ it('should error with invalid source path', function() {
+ (function() {
+ var compact = require('../../compact').createCompact('invalid src path');
+ }).should.throw('Invalid source path \'invalid src path\'');
+ });
+
+ it('should error with invalid destination path', function() {
+ (function() {
+ var compact = require('../../compact').createCompact(srcPath, 'invalid dest path');
+ }).should.throw('Invalid destination path \'invalid dest path\'');
+ });
+
+ it('should succeed with valid paths', function() {
+ require('../../compact')
+ .createCompact(srcPath, destPath).should.be.a('object');
+ });
+ });
+
+ describe('#addNamespace()', function() {
+ var compact;
+
+ beforeEach(function() {
+ compact = require('../../compact').createCompact(srcPath, destPath);
+ });
+
+ it('should fail on null', function() {
+ (function() {
+ compact.addNamespace(null);
+ }).should.throw('Invalid namespace');
+ });
+
+ it('should succeed with valid namespace', function() {
+ compact.addNamespace('global').addJs.should.be.a('function');
+ });
+ });
+
+ describe('Namespace', function() {
+
+ var namespace
+ , compact;
+
+ beforeEach(function() {
+ compact = require('../../compact').createCompact(srcPath, destPath);
+ namespace = compact.addNamespace('global');
+ });
+
+ describe('access via .ns', function() {
+ it('should be accessible via ', function() {
+ compact.ns.global.should = Object.prototype.should;
+ compact.ns.global.should.be.a('object');
+ });
+
+ it('should not be able to mess with a namespace', function() {
+ compact.ns.global.should = Object.prototype.should;
+ (function() {
+ compact.ns.global = {};
+ }).should.throw('You can not alter a registered namespace \'global\'');
+
+ });
+ });
+
+ describe('#addJs()', function() {
+
+ it('should succeed with valid file', function() {
+ namespace.addJs('a.js');
+ });
+
+ it('should be chainable', function() {
+ namespace
+ .addJs('a.js')
+ .addJs('b.js');
+ });
+
+ it('should be able to add via .ns', function() {
+ compact.ns.global
+ .addJs('a.js')
+ .addJs('b.js');
+ });
+ });
+ });
+
+ describe('#js()', function() {
+
+ var namespace
+ , compact;
+
+ beforeEach(function() {
+ compact = require('../../compact').createCompact(srcPath, destPath);
+ });
+
+ it('should error without parameter', function() {
+ (function() {
+ compact.js();
+ }).should.throw('You must pass one or more arrays containing valid namespace names');
+
+ });
+
+ it('should succeed with empty array as first parameter', function() {
+ compact.js([]).should.be.a('function');
+ });
+
+ it('should create a helper when given valid input for a single namespace', function(done) {
+ compact.addNamespace('global')
+ .addJs('/a.js')
+ .addJs('/b.js');
+
+ var
+ req = {
+ app: {
+ helpers: function(helper) {
+ helper.compactJs().should.eql(['/global.js']);
+ done();
+ }
+ }
+ }
+ , res;
+
+ compact.js(['global'])(req, res, function() {});
+ });
+
+ it('should have a correct helper when given valid input for multiple namespaces', function(done) {
+
+ compact.addNamespace('global')
+ .addJs('/a.js')
+ .addJs('/b.js');
+
+ compact.addNamespace('profile')
+ .addJs('/c.js');
+
+ var
+ req = {
+ app: {
+ helpers: function(helper) {
+ helper.compactJs().should.eql(['/global-profile.js']);
+ done();
+ }
+ }
+ }
+ , res;
+
+ compact.js(['global', 'profile'])(req, res, function() {});
+ });
+
+ it('should have a correct helper when given valid input for multiple groups', function(done) {
+
+ compact.addNamespace('global')
+ .addJs('/a.js');
+
+ compact.addNamespace('blog')
+ .addJs('/b.js');
+
+ compact.addNamespace('profile')
+ .addJs('/c.js');
+
+ var
+ req = {
+ app: {
+ helpers: function(helper) {
+ helper.compactJs().should.eql(['/global-profile.js', '/blog.js']);
+ done();
+ }
+ }
+ }
+ , res;
+
+ compact.js(['global', 'profile'], ['blog'])(req, res, function() {});
+ });
+ });
+});

0 comments on commit 70a6389

Please sign in to comment.
Something went wrong with that request. Please try again.