var Wrench = require('wrench');
var UglifyJS = require('uglify-js');
var Stylus = require('stylus');
var Nib = require('nib')();
var CleanCSS = require('clean-css');
var Jade = require('jade');
var display = config.minify ? true : false;
var minificationTimes = {};
var resources = config.resources = module.exports = {};
var pub = resources.public = {};
var main = pub.main = {};
var vendor = pub.vendor = {};
var ngModules = resources.ngModules = {};
var views = resources.views = {};
var minified = resources.minified = {};
ngModules.getModules = function() {
var modules = [];
for (let key in this)
if (this[key].js)
modules.push(key)
return modules;
}
console.verbose('Loading resources...');
// Read and concat all app+module's client-side files
var files = readFiles(config.moddir).concat(readFiles(config.appdir));
// console.debug(files);
function readFiles(basepath) {
basepath = basepath + '/client/'
return Wrench
// read all files and directories recursively
.readdirSyncRecursive(basepath)
// keep only files (with a dot.)
.reduce((files, file) => files.concat(~file.indexOf('.') ? [file] : []), [])
// prepend basepath
.map(rel => basepath + rel)
// normalize path
.map(file => Path.join(file).replace(/\\/g, '/'))
}
// Categorize by folder
files.forEach(function(fullpath) {
var pkg = {};
var filename = pkg.filename = Path.basename(fullpath);
var ext = Path.extname(filename).substr(1);
if (ext == 'es6') return;
var basename = pkg.basename = Path.basename(filename, '.' + ext);
var baseName = _.camelCase(basename);
var match, cat;
switch (true) {
case !!~fullpath.indexOf('/client/views/'):
views[filename] = views[filename] || fullpath;
return;
case !!~fullpath.indexOf('/client/ng-modules/'):
ngModules[baseName] = ngModules[baseName] || {};
ngModules[baseName][ext] = ngModules[baseName][ext] || fullpath;
ngModules[baseName][ext] = fullpath || ngModules[baseName][ext];
return;
case !!(match = fullpath.match('/client/public/(main|vendor)/(.*)/' + filename)):
cat = pub[match[1]][match[2]] = pub[match[1]][match[2]] || [];
cat.push(fullpath);
// cat[filename] = cat[filename] || fullpath;
return;
};
});
// console.debug('\n\n%s\n\n', JSON.stringify(resources, null, 1));
// Sort
main.css = main.css.sort(sorter);
vendor.css = vendor.css.sort(sorter);
main.js = main.js.sort(sorter);
vendor.js = vendor.js.sort(sorter);
// console.debug('\n'+main.css.join('\n'));
// console.debug('\n'+vendor.css.join('\n'));
// console.debug('\n'+main.js.join('\n'));
// console.debug('\n'+vendor.js.join('\n'));
function sorter(a, b) {
var order = 0;
order = Path.basename(a) > Path.basename(b) ? 1 : -1;
[
'polyfill',
'jquery.js',
'angular.js',
'create-ng-directive.js',
'lodash',
'modernizr',
'snap.svg',
'jquery.flot.js',
'bootstrap.css',
'bootstrap',
'reset',
'jquery',
'angular',
'common',
'uniform',
'basic',
'modern',
'modern-theme',
'vendor',
'socket',
'ng-modules',
'root.js',
'app.js',
'node_modules|simple-app',
].reverse().map(str => order = match(str));
return order;
function match(str) {
return a.match(str) ? -1 : b.match(str) ? 1 : order;
}
}
// Consolidate ng-templates in Script templates.js
// Consolidate ng-templates
resources.getConsolidatedNgTemplatesJS = function() {
var ngTemplatesJade = `ngTemplates = angular.module('ngTemplates', []);\n`;
for (let key in ngModules)(function(key, module, ngModules) {
if (!module.jade) return;
var fullpath = module.jade;
var relpath = fullpath.split('/client').pop().replace('.jade', '');
var str = fs.readFileSync(fullpath, 'utf8');
str = Jade.render(str, locals);
str = `ngTemplates.run(($templateCache)=>$templateCache.put('${relpath}',\`${str}\`))`;
ngTemplatesJade += '\n' + str + '\n';
})(key, ngModules[key], ngModules);
// console.debug(ngTemplatesJade);
ngTemplatesJade += '\n//\n// ng-templates --END--';
return ngTemplatesJade;
};
// console.debug('\n' + JSON.stringify(resources, null, 1));
// Consolidate ng-templates
resources.getConsolidatedNgTemplatesJadeFn = function() {
var str = getConsolidatedNgTemplatesJade();
// console.debug(str);
var fn = Jade.compile(str, locals);
return fn;
function getConsolidatedNgTemplatesJade() {
var ngTemplatesJade = '// ng-templates --begin-- \n//';
for (let key in ngModules)(function(key, module, ngModules) {
if (!module.jade) return;
var fullpath = module.jade;
var str = fs.readFileSync(fullpath, 'utf8');
var relpath = fullpath.split('/client').pop().replace('.jade', '');
str = '\n\n';
ngTemplatesJade += str;
})(key, ngModules[key], ngModules);
// console.debug(ngTemplatesJade);
ngTemplatesJade += '\n//\n// ng-templates --END--';
return ngTemplatesJade;
}
}
resources.getConsolidatedNgTemplates = function() {
return jades().map(o => {
o.fn = Jade.compile(o.str, locals);
return o;
});
function jades() {
var jades = [];
for (let key in ngModules) {
let module = ngModules[key];
if (!module.jade) continue;
let o = {};
o.fullpath = module.jade;
o.str = fs.readFileSync(o.fullpath, 'utf8');
o.relpath = o.fullpath.split('/client').pop().replace('.jade', '');
o.str = '';
jades.push(o);
}
return jades;
}
}
// Minify
// console.debug('\n' + JSON.stringify(resources, null, 1));
if (!dev && config.minify) {
resources.getMinified = {};
resources.getMinified.js = new Promise(function(resolve) {
return resolve(minifyJS());
});
resources.getMinified.css = new Promise(function(resolve) {
return resolve(minifyCSS());
});
}
function minifyJS() {
console.verbose('Minifying JS...');
// consolidate
var str = '';
vendor.js.forEach(function(fullpath) {
str += '\n' + fs.readFileSync(fullpath, 'utf8') + '\n';
});
main.js.forEach(function(fullpath) {
str += '\n' + fs.readFileSync(fullpath, 'utf8') + '\n';
});
for (let key in ngModules)
if (ngModules[key].js)
str += '\n' + fs.readFileSync(ngModules[key].js, 'utf8') + '\n';
// minify
var minifiedJS = UglifyJS.minify(str, {
// outSourceMap: mapPath,
// mangle: (topdir != 'client'),
mangle: false,
fromString: true,
}).code;
console.verbose('JS minified! %s => %s (%s)', calcSize(str), calcSize(minifiedJS), calcDiff(minifiedJS, str));
return minifiedJS;
}
function calcSize(str) {
var len = str.length;
var bytes = len;
var kb = parseInt(bytes / 1024);
if (kb < 1000)
return kb + 'kB';
var mb = parseInt(kb / 1024)
return mb + 'mb';
}
function calcDiff(small, big) {
small = small.length;
big = big.length;
var ratio = small / big;
var percent = ratio * 100;
if (percent < 1)
return percent.toFixed(2) + '%';
return parseInt(percent) + '%';
}
function minifyCSS() {
var vendor = minifyVendorCSS();
var main = minifyStylus();
return vendor + main;
}
function minifyVendorCSS() {
return vendor.css.reduce(function(combined, fullpath) {
// console.log('fullpath:', fullpath);
var css = fs.readFileSync(fullpath, 'utf8');
var minified = css;
// var minified = new CleanCSS({
// keepSpecialComments: 0
// }).minify(css).styles;
minified = '\n/* fullpath: ' + fullpath + ' */\n' + minified;
combined += minified;
return combined;
}, '');
// // Consolidate
// var css = '';
// vendor.css.forEach(function(fullpath) {
// console.log('fullpath:', fullpath);
// css += '\n' +
// ' /* fullpath: ' + fullpath + ' */' + '\n' +
// fs.readFileSync(fullpath, 'utf8') +
// '\n';
// });
// // minify
// return new CleanCSS({
// // keepSpecialComments: 1
// }).minify(css).styles;
}
function minifyStylus() {
// return main.css.reduce(function(combined, fullpath){
// console.log('fullpath:', fullpath);
// var styl = fs.readFileSync(fullpath, 'utf8');
// },'');
var cssMiniStr = '';
var stylStr = '';
// Consolidate
main.css.forEach(function(fullpath) {
stylStr += '\n' + fs.readFileSync(fullpath, 'utf8') + '\n';
});
for (let key in ngModules)
if (ngModules[key].styl)
stylStr += '\n' + fs.readFileSync(ngModules[key].styl, 'utf8') + '\n';
// Compile & minify
Stylus(stylStr).set('compress', true).render(function(err, css) {
if (err) console.err(err);
cssMiniStr = css;
});
return cssMiniStr;
}