Stackedit/js/libs/css/css-builder.js
2013-08-04 12:28:17 +01:00

252 lines
8.3 KiB
JavaScript

define(['require', './normalize'], function(req, normalize) {
var nodePrint = function() {};
if (requirejs.tools)
requirejs.tools.useLib(function(req) {
req(['node/print'], function(_nodePrint) {
nodePrint = _nodePrint;
}, function(){});
});
var cssAPI = {};
function compress(css) {
if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
try {
var csso = require.nodeRequire('csso');
var csslen = css.length;
css = csso.justDoIt(css);
nodePrint('Compressed CSS output to ' + Math.round(css.length / csslen * 100) + '%.');
return css;
}
catch(e) {
nodePrint('Compression module not installed. Use "npm install csso -g" to enable.');
return css;
}
}
nodePrint('Compression not supported outside of nodejs environments.');
return css;
}
//load file code - stolen from text plugin
function loadFile(path) {
if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
var fs = require.nodeRequire('fs');
var file = fs.readFileSync(path, 'utf8');
if (file.indexOf('\uFEFF') === 0)
return file.substring(1);
return file;
}
else {
var file = new java.io.File(path),
lineSeparator = java.lang.System.getProperty("line.separator"),
input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), 'utf-8')),
stringBuffer, line;
try {
stringBuffer = new java.lang.StringBuffer();
line = input.readLine();
if (line && line.length() && line.charAt(0) === 0xfeff)
line = line.substring(1);
stringBuffer.append(line);
while ((line = input.readLine()) !== null) {
stringBuffer.append(lineSeparator).append(line);
}
return String(stringBuffer.toString());
}
finally {
input.close();
}
}
}
function saveFile(path, data) {
if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
var fs = require.nodeRequire('fs');
fs.writeFileSync(path, data, 'utf8');
}
else {
var content = new java.lang.String(data);
var output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(path), 'utf-8'));
try {
output.write(content, 0, content.length());
output.flush();
}
finally {
output.close();
}
}
}
//when adding to the link buffer, paths are normalised to the baseUrl
//when removing from the link buffer, paths are normalised to the output file path
function escape(content) {
return content.replace(/(["'\\])/g, '\\$1')
.replace(/[\f]/g, "\\f")
.replace(/[\b]/g, "\\b")
.replace(/[\n]/g, "\\n")
.replace(/[\t]/g, "\\t")
.replace(/[\r]/g, "\\r");
}
// NB add @media query support for media imports
var importRegEx = /@import\s*(url)?\s*(('([^']*)'|"([^"]*)")|\(('([^']*)'|"([^"]*)"|([^\)]*))\))\s*;?/g;
var loadCSSFile = function(fileUrl) {
var css = loadFile(fileUrl);
// normalize the css (except import statements)
css = normalize(css, fileUrl, baseUrl, cssBase);
// detect all import statements in the css and normalize
var importUrls = [];
var importIndex = [];
var importLength = [];
var match;
while (match = importRegEx.exec(css)) {
var importUrl = match[4] || match[5] || match[7] || match[8] || match[9];
// normalize import url
if (importUrl.substr(importUrl.length - 5, 5) != '.less' && importUrl.substr(importUrl.length - 4, 4) != '.css')
importUrl += '.css';
// contains a protocol
if (importUrl.match(/:\/\//))
continue;
// relative to css base
if (importUrl.substr(0, 1) == '/' && cssBase)
importUrl = cssBase + importUrl;
else
importUrl = baseUrl + importUrl;
importUrls.push(importUrl);
importIndex.push(importRegEx.lastIndex - match[0].length);
importLength.push(match[0].length);
}
// load the import stylesheets and substitute into the css
for (var i = 0; i < importUrls.length; i++)
(function(i) {
var importCSS = loadCSSFile(importUrls[i]);
css = css.substr(0, importIndex[i]) + importCSS + css.substr(importIndex[i] + importLength[i]);
var lenDiff = importCSS.length - importLength[i];
for (var j = i + 1; j < importUrls.length; j++)
importIndex[j] += lenDiff;
})(i);
return css;
}
var baseUrl;
var cssBase;
var curModule;
cssAPI.load = function(name, req, load, config, parse) {
if (!baseUrl)
baseUrl = config.baseUrl;
if (!cssBase)
cssBase = config.cssBase;
if (config.modules) {
//run through the module list - the first one without a layer set is the current layer we are in
//allows to track the current layer number for layer-specific config
for (var i = 0; i < config.modules.length; i++)
if (config.modules[i].layer === undefined) {
curModule = i;
break;
}
}
//store config
cssAPI.config = cssAPI.config || config;
name += !parse ? '.css' : '.less';
var fileUrl = req.toUrl(name);
//external URLS don't get added (just like JS requires)
if (fileUrl.substr(0, 7) == 'http://' || fileUrl.substr(0, 8) == 'https://')
return load();
//add to the buffer
_cssBuffer[name] = loadCSSFile(fileUrl);
// parse if necessary
if (parse)
_cssBuffer[name] = parse(_cssBuffer[name]);
load();
}
cssAPI.normalize = function(name, normalize) {
if (name.substr(name.length - 4, 4) == '.css')
name = name.substr(0, name.length - 4);
return normalize(name);
}
//list of cssIds included in this layer
var _layerBuffer = [];
var _cssBuffer = [];
cssAPI.write = function(pluginName, moduleName, write, parse) {
//external URLS don't get added (just like JS requires)
if (moduleName.substr(0, 7) == 'http://' || moduleName.substr(0, 8) == 'https://' || moduleName.substr(0, 2) == '//')
return;
var resourceName = moduleName + (!parse ? '.css' : '.less');
_layerBuffer.push(_cssBuffer[resourceName]);
var separateCSS = false;
if (cssAPI.config.separateCSS)
separateCSS = true;
if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
separateCSS = cssAPI.config.modules[curModule].separateCSS;
if (separateCSS)
write.asModule(pluginName + '!' + moduleName, 'define(function(){})');
else
write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.addBuffer('" + resourceName + "'); }); requirejs.s.contexts._.nextTick = requirejs.nextTick;");
}
cssAPI.onLayerEnd = function(write, data, parser) {
firstWrite = true;
//separateCSS parameter set either globally or as a layer setting
var separateCSS = false;
if (cssAPI.config.separateCSS)
separateCSS = true;
if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
separateCSS = cssAPI.config.modules[curModule].separateCSS;
curModule = null;
//calculate layer css
var css = _layerBuffer.join('');
if (separateCSS) {
nodePrint('Writing CSS! file: ' + data.name + '\n');
//calculate the css output path for this layer
var path = this.config.appDir ? this.config.baseUrl + data.name + '.css' : cssAPI.config.out.replace(/\.js$/, '.css');
//renormalize the css to the output path
var output = compress(normalize(css, baseUrl, path));
saveFile(path, output);
}
else {
if (css == '')
return;
//write the injection and layer index into the layer
//prepare the css
css = escape(compress(css));
//the code below overrides async require functionality to ensure instant buffer injection
write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.setBuffer('" + css + (parser ? "', true" : "'") + "); }); requirejs.s.contexts._.nextTick = requirejs.nextTick; ");
}
//clear layer buffer for next layer
_layerBuffer = [];
}
return cssAPI;
});