/*------------------------------------*\
TOOLS
\*------------------------------------*/
/* jslint node: true */
/**
* Engine for compile preprocessor CSS files and minify CSS output.
* @private
* @function cssCompilation
* @memberOf NA#
* @this NA
* @param {NA~callback} next Next step after preprocessor compilation.
*/
exports.cssCompilation = function (next) {
var NA = this,
async = NA.modules.async;
if (NA.configuration.generate || NA.webconfig.cssBundlingBeforeResponse) {
async.parallel([
NA.lessCompilation.bind(NA),
NA.stylusCompilation.bind(NA)
], function () {
NA.cssMinification(next);
});
} else {
next();
}
};
/**
* Engine for compile Less files.
* @private
* @function lessCompilation
* @memberOf NA#
* @this NA
* @param {NA~callback} next Next step after Less compilation.
*/
exports.lessCompilation = function (next) {
var NA = this,
enableLess = NA.webconfig.less,
async = NA.modules.async,
path = NA.modules.path,
less = NA.modules.less,
fs = NA.modules.fs,
allLessCompiled,
data = {},
paths = [];
if (enableLess && enableLess.files) {
allLessCompiled = enableLess.files;
if (enableLess.paths && paths.length === 0) {
for (var i = 0; i < enableLess.paths.length; i++) {
paths[i] = path.join(NA.webconfig.assetsRelativePath, enableLess.paths[i]);
}
} else if (paths.length === 0) {
paths = [
NA.webconfig.assetsRelativePath,
path.join(NA.webconfig.assetsRelativePath, 'stylesheets'),
path.join(NA.webconfig.assetsRelativePath, 'styles'),
path.join(NA.webconfig.assetsRelativePath, 'css')
];
}
async.each(allLessCompiled, function (compiledFile, next) {
var currentFile = fs.readFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compiledFile), 'utf-8'),
prefixLess = new NA.modules.prefixLess(),
options = {
paths: paths
};
if (enableLess.autoprefix) {
options.plugins = [prefixLess];
}
less.render(currentFile, options, function (e, output) {
if (e) {
NA.log(e);
}
data.pathName = path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compiledFile.replace(/\.less$/g,'.css'));
fs.writeFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compiledFile.replace(/\.less$/g,'.css')), output.css);
NA.log(NA.cliLabels.lessGenerate.replace(/%([\-a-zA-Z0-9_]+)%/g, function (regex, matches) { return data[matches]; }));
next();
});
}, function () {
if (next) {
next();
}
});
} else {
if (next) {
next();
}
}
};
/**
* Engine for compile Stylus files.
* @private
* @function stylusCompilation
* @memberOf NA#
* @this NA
* @param {NA~callback} next Next step after Stylus compilation.
*/
exports.stylusCompilation = function (next) {
var NA = this,
enableStylus = NA.webconfig.stylus,
async = NA.modules.async,
path = NA.modules.path,
stylus = NA.modules.stylus,
fs = NA.modules.fs,
allStylusCompiled,
data = {},
paths = [];
if (enableStylus && enableStylus.files) {
allStylusCompiled = enableStylus.files;
if (enableStylus.paths && paths.length === 0) {
for (var i = 0; i < enableStylus.paths.length; i++) {
paths[i] = path.join(NA.webconfig.assetsRelativePath, enableStylus.paths[i]);
}
} else if (paths.length === 0) {
paths = [
NA.webconfig.assetsRelativePath,
path.join(NA.webconfig.assetsRelativePath, 'stylesheets'),
path.join(NA.webconfig.assetsRelativePath, 'styles'),
path.join(NA.webconfig.assetsRelativePath, 'css')
];
}
async.each(allStylusCompiled, function (compiledFile, next) {
var currentFile = fs.readFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compiledFile), 'utf-8'),
stylusFn = stylus(currentFile);
if (enableStylus.autoprefix) {
stylusFn = stylusFn.use(NA.modules.prefixStylus());
}
stylusFn
.set('paths', paths)
.render(function (e, output) {
if (e) {
NA.log(e);
}
data.pathName = path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compiledFile.replace(/\.styl$/g,'.css'));
fs.writeFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compiledFile.replace(/\.styl$/g,'.css')), output);
NA.log(NA.cliLabels.stylusGenerate.replace(/%([\-a-zA-Z0-9_]+)%/g, function (regex, matches) { return data[matches]; }));
next();
});
}, function () {
if (next) {
next();
}
});
} else {
if (next) {
next();
}
}
};
/**
* Engine for minification and concatenation of all files with a Bundle configuration.
* @private
* @function cssMinification
* @memberOf NA#
* @this NA
* @param {NA~callback} next Next step after minification and concatenation of all CSS.
*/
exports.cssMinification = function (next) {
var NA = this,
bundles = NA.webconfig.bundles,
cleanCss = NA.modules.cleanCss,
async = NA.modules.async,
path = NA.modules.path,
fs = NA.modules.fs,
enable,
output = "",
data = {},
allCssMinified = [];
/* Verify if bundle is okay and if engine must start. */
enable = (NA.configuration.generate ||
/**
* CSS minification before each HTML response.
* @public
* @alias cssBundlingBeforeResponse
* @type {boolean}
* @memberOf NA#webconfig
* @default false
*/
NA.webconfig.cssBundlingBeforeResponse);
if (typeof NA.webconfig.cssBundlingEnable === "boolean") {
/**
* No CSS minification if set to false.
* @public
* @alias cssBundlingEnable
* @type {boolean}
* @memberOf NA#webconfig
* @default true
*/
enable = NA.webconfig.cssBundlingEnable;
}
/* Star engine. */
if (bundles && bundles.stylesheets && enable) {
NA.forEach(bundles.stylesheets, function (compressedFile) {
allCssMinified.push(compressedFile);
});
async.each(allCssMinified, function (compressedFile, firstCallback) {
async.map(bundles.stylesheets[compressedFile], function (sourceFile, secondCallback) {
try {
secondCallback(null, fs.readFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, sourceFile), 'utf-8'));
} catch (e) {
secondCallback(e, "");
}
}, function(error, results) {
if (error) {
throw error;
}
for (var i = 0; i < results.length; i++) {
output += results[i];
}
output = (new cleanCss().minify(output)).styles;
compressedFile = compressedFile.replace(/{version}/g, NA.webconfig.version);
fs.writeFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compressedFile), output);
data.pathName = path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compressedFile);
NA.log(NA.cliLabels.cssGenerate.replace(/%([\-a-zA-Z0-9_]+)%/g, function (regex, matches) { return data[matches]; }));
output = "";
firstCallback();
});
}, function () {
if (next) {
next();
}
});
} else {
if (next) {
next();
}
}
};
/**
* Engine for obfuscation and concatenation of all files with a Bundle configuration.
* @private
* @function jsObfuscation
* @memberOf NA#
* @this NA
* @param {NA~callback} next Next step after obfuscation and concatenation of all CSS.
*/
exports.jsObfuscation = function (next) {
var NA = this,
bundles = NA.webconfig.bundles,
uglifyEs = NA.modules.uglifyEs,
async = NA.modules.async,
path = NA.modules.path,
fs = NA.modules.fs,
enable,
output = "",
data = {},
allJsMinified = [];
/* Verify if bundle is okay and if engine must start. */
enable = (NA.configuration.generate ||
/**
* JavaScript obfuscation before each HTML response.
* @public
* @alias jsBundlingBeforeResponse
* @type {boolean}
* @memberOf NA#webconfig
* @default false
*/
NA.webconfig.jsBundlingBeforeResponse);
if (typeof NA.webconfig.jsBundlingEnable === "boolean") {
/**
* No JavaScript obfuscation if set to false.
* @public
* @alias jsBundlingEnable
* @type {boolean}
* @memberOf NA#webconfig
* @default true
*/
enable = NA.webconfig.jsBundlingEnable;
}
/* Star engine. */
if (bundles && bundles.javascripts && enable) {
NA.forEach(bundles.javascripts, function (compressedFile) {
allJsMinified.push(compressedFile);
});
async.each(allJsMinified, function (compressedFile, firstCallback) {
async.map(bundles.javascripts[compressedFile], function (sourceFile, secondCallback) {
var code,
result,
file;
/* For the NA.socket and NA.io auto configuration. */
if (path.join("/", NA.webconfig.socketClientFile) === path.join("/", sourceFile)) {
file = fs.readFileSync(path.join(NA.nodeatlasPath, "src", "socket.io.js"), "utf-8")
.replace(/%urlRelativeSubPath%/g, NA.webconfig.urlRelativeSubPath.slice(1))
.replace(/%urlRoot%/g, NA.webconfig.urlRoot);
result = uglifyEs.minify(file);
/* And for others real files. */
} else {
code = fs.readFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, sourceFile), "utf-8");
result = uglifyEs.minify(code);
}
secondCallback(null, result.code);
}, function(error, results) {
if (error) {
throw error;
}
for (var i = 0; i < results.length; i++) {
output += results[i];
}
compressedFile = compressedFile.replace(/{version}/g, NA.webconfig.version);
fs.writeFileSync(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compressedFile), output);
data.pathName = path.join(NA.serverPath, NA.webconfig.assetsRelativePath, compressedFile);
NA.log(NA.cliLabels.jsGenerate.replace(/%([\-a-zA-Z0-9_]+)%/g, function (regex, matches) { return data[matches]; }));
output = "";
firstCallback();
});
}, function () {
if (next) {
next();
}
});
} else {
if (next) {
next();
}
}
};
/**
* Check if a file have been already parsed.
* @private
* @function cssAlreadyParse
* @memberOf NA#
* @param {string} newPath Current file tested.
* @param {Array.<string>} allCssFiles Files already tested.
* @param {string} inject State for know if injection will be authorized.
*/
exports.cssAlreadyParse = function (newPath, allCssFiles, inject) {
var NA = this,
path = NA.modules.path;
for (var i = 0; i < allCssFiles.length; i++) {
if (path.join(NA.serverPath, NA.webconfig.assetsRelativePath, newPath) === allCssFiles[i]) {
inject = false;
}
}
return inject;
};
/**
* Inject Css if not already injected.
* @private
* @function injectCssAuth
* @memberOf NA#
* @param {string} pathFile Current file for injection.
* @param {Array.<string>} allCssFiles Files already tested.
* @param {string} inject State for know if injection will be authorized.
*/
exports.injectCssAuth = function (pathFile, allCssFiles, inject) {
var NA = this,
path = NA.modules.path;
if (inject) {
allCssFiles.push(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, pathFile));
}
};
/**
* Verify if common or specif file without double are ready for injection CSS.
* @private
* @function prepareCssInjection
* @memberOf NA#
* @param {Array.<string>} allCssFiles Files already tested.
* @param {string|Array.<string>} injection Represent the injectCss property injection to the template.
*/
exports.prepareCssInjection = function (allCssFiles, injection) {
var NA = this,
path = NA.modules.path,
inject = true,
/**
* CSS files for specific injection of CSS.
* @public
* @alias injectCss
* @type {string|Array.<string>}
* @memberOf NA#locals.routeParameters
*/
specificInjection = injection,
/**
* CSS files for common injection of CSS.
* @public
* @alias injectCss
* @type {string|Array.<string>}
* @memberOf NA#webconfig
*/
commonInjection = NA.webconfig.injectCss;
/* Add common injections. */
if (typeof commonInjection === 'string') {
allCssFiles.push(path.join(NA.serverPath, NA.webconfig.assetsRelativePath, commonInjection));
} else if (commonInjection) {
for (var i = 0; i < commonInjection.length; i++) {
/* Inject Css. */
NA.injectCssAuth(NA.webconfig.injectCss[i], allCssFiles, inject);
}
}
/* Add specific injections. */
if (specificInjection) {
if (typeof specificInjection === 'string') {
/* Check if a file have been already parsed. */
inject = NA.cssAlreadyParse(specificInjection, allCssFiles, inject);
/* Inject Css. */
NA.injectCssAuth(specificInjection, allCssFiles, inject);
} else {
for (var j = 0; j < specificInjection.length; j++) {
/* Check if a file have been already parsed. */
inject = NA.cssAlreadyParse(specificInjection[j], allCssFiles, inject);
/* Inject Css. */
NA.injectCssAuth(specificInjection[j], allCssFiles, inject);
inject = true;
}
}
}
return allCssFiles;
};
/**
* Inject the content of a stylesheets file into a DOM.
* @private
* @function injectCss
* @memberOf NA#
* @param {string} dom The ouptput HTML.
* @param {string|Array.<string>} injection Represent the injectCss property injection to the template.
* @param {injectCss~mainCallback} mainCallback The next steps after injection.
*/
exports.injectCss = function (data, injection, mainCallback) {
var NA = this,
jsdom = NA.modules.jsdom;
cssParse = NA.modules.cssParse,
async = NA.modules.async,
fs = NA.modules.fs,
allCssFiles = [],
dom = new jsdom.JSDOM(data);
/* Prepare Injection */
allCssFiles = NA.prepareCssInjection(allCssFiles, injection);
/* Injection */
async.map(allCssFiles, function (sourceFile, callback) {
/* Concatain all CSS. */
callback(null, fs.readFileSync(sourceFile, 'utf-8'));
}, function(error, results) {
var output = "",
css;
for (var i = 0; i < results.length; i++) {
output += results[i];
}
/* Parse CSS in JavaScript. */
css = cssParse(output);
/* Parse all rules Css. */
function parseCssRules(callback) {
for (var i = 0; i < css.stylesheet.rules.length; i++) {
if (typeof css.stylesheet.rules[i].selectors !== 'undefined') {
callback(i);
}
}
}
/* Parse all selector Css. */
function parseCssSelector(i, callback) {
for (var j = 0; j < css.stylesheet.rules[i].selectors.length; j++) {
callback(j);
}
}
/* Parse all declaration Css. */
function parseCssDeclaration(i, j) {
for (var k = 0; k < css.stylesheet.rules[i].declarations.length; k++) {
Array.prototype.forEach.call(dom.window.document.querySelectorAll(css.stylesheet.rules[i].selectors[j]), function (node) {
node.style[css.stylesheet.rules[i].declarations[k].property] = css.stylesheet.rules[i].declarations[k].value
});
}
}
/* Apply property on the DOM. */
parseCssRules(function (i) {
parseCssSelector(i, function (j) {
parseCssDeclaration(i, j);
});
});
/**
* Next steps after injection of CSS.
* @callback injectCss~mainCallback
* @param {string} dom DOM with modifications.
*/
mainCallback(dom.serialize());
});
};