css-builder.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. define(['require', './normalize'], function(req, normalize) {
  2. var nodePrint = function() {};
  3. if (requirejs.tools)
  4. requirejs.tools.useLib(function(req) {
  5. req(['node/print'], function(_nodePrint) {
  6. nodePrint = _nodePrint;
  7. }, function(){});
  8. });
  9. var cssAPI = {};
  10. function compress(css) {
  11. if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
  12. try {
  13. var csso = require.nodeRequire('csso');
  14. var csslen = css.length;
  15. css = csso.justDoIt(css);
  16. nodePrint('Compressed CSS output to ' + Math.round(css.length / csslen * 100) + '%.');
  17. return css;
  18. }
  19. catch(e) {
  20. nodePrint('Compression module not installed. Use "npm install csso -g" to enable.');
  21. return css;
  22. }
  23. }
  24. nodePrint('Compression not supported outside of nodejs environments.');
  25. return css;
  26. }
  27. //load file code - stolen from text plugin
  28. function loadFile(path) {
  29. if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
  30. var fs = require.nodeRequire('fs');
  31. var file = fs.readFileSync(path, 'utf8');
  32. if (file.indexOf('\uFEFF') === 0)
  33. return file.substring(1);
  34. return file;
  35. }
  36. else {
  37. var file = new java.io.File(path),
  38. lineSeparator = java.lang.System.getProperty("line.separator"),
  39. input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), 'utf-8')),
  40. stringBuffer, line;
  41. try {
  42. stringBuffer = new java.lang.StringBuffer();
  43. line = input.readLine();
  44. if (line && line.length() && line.charAt(0) === 0xfeff)
  45. line = line.substring(1);
  46. stringBuffer.append(line);
  47. while ((line = input.readLine()) !== null) {
  48. stringBuffer.append(lineSeparator).append(line);
  49. }
  50. return String(stringBuffer.toString());
  51. }
  52. finally {
  53. input.close();
  54. }
  55. }
  56. }
  57. function saveFile(path, data) {
  58. if (typeof process !== "undefined" && process.versions && !!process.versions.node && require.nodeRequire) {
  59. var fs = require.nodeRequire('fs');
  60. fs.writeFileSync(path, data, 'utf8');
  61. }
  62. else {
  63. var content = new java.lang.String(data);
  64. var output = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(path), 'utf-8'));
  65. try {
  66. output.write(content, 0, content.length());
  67. output.flush();
  68. }
  69. finally {
  70. output.close();
  71. }
  72. }
  73. }
  74. //when adding to the link buffer, paths are normalised to the baseUrl
  75. //when removing from the link buffer, paths are normalised to the output file path
  76. function escape(content) {
  77. return content.replace(/(["'\\])/g, '\\$1')
  78. .replace(/[\f]/g, "\\f")
  79. .replace(/[\b]/g, "\\b")
  80. .replace(/[\n]/g, "\\n")
  81. .replace(/[\t]/g, "\\t")
  82. .replace(/[\r]/g, "\\r");
  83. }
  84. // NB add @media query support for media imports
  85. var importRegEx = /@import\s*(url)?\s*(('([^']*)'|"([^"]*)")|\(('([^']*)'|"([^"]*)"|([^\)]*))\))\s*;?/g;
  86. var loadCSSFile = function(fileUrl) {
  87. var css = loadFile(fileUrl);
  88. // normalize the css (except import statements)
  89. css = normalize(css, fileUrl, baseUrl, cssBase);
  90. // detect all import statements in the css and normalize
  91. var importUrls = [];
  92. var importIndex = [];
  93. var importLength = [];
  94. var match;
  95. while (match = importRegEx.exec(css)) {
  96. var importUrl = match[4] || match[5] || match[7] || match[8] || match[9];
  97. // normalize import url
  98. if (importUrl.substr(importUrl.length - 5, 5) != '.less' && importUrl.substr(importUrl.length - 4, 4) != '.css')
  99. importUrl += '.css';
  100. // contains a protocol
  101. if (importUrl.match(/:\/\//))
  102. continue;
  103. // relative to css base
  104. if (importUrl.substr(0, 1) == '/' && cssBase)
  105. importUrl = cssBase + importUrl;
  106. else
  107. importUrl = baseUrl + importUrl;
  108. importUrls.push(importUrl);
  109. importIndex.push(importRegEx.lastIndex - match[0].length);
  110. importLength.push(match[0].length);
  111. }
  112. // load the import stylesheets and substitute into the css
  113. for (var i = 0; i < importUrls.length; i++)
  114. (function(i) {
  115. var importCSS = loadCSSFile(importUrls[i]);
  116. css = css.substr(0, importIndex[i]) + importCSS + css.substr(importIndex[i] + importLength[i]);
  117. var lenDiff = importCSS.length - importLength[i];
  118. for (var j = i + 1; j < importUrls.length; j++)
  119. importIndex[j] += lenDiff;
  120. })(i);
  121. return css;
  122. }
  123. var baseUrl;
  124. var cssBase;
  125. var curModule;
  126. cssAPI.load = function(name, req, load, config, parse) {
  127. if (!baseUrl)
  128. baseUrl = config.baseUrl;
  129. if (!cssBase)
  130. cssBase = config.cssBase;
  131. if (config.modules) {
  132. //run through the module list - the first one without a layer set is the current layer we are in
  133. //allows to track the current layer number for layer-specific config
  134. for (var i = 0; i < config.modules.length; i++)
  135. if (config.modules[i].layer === undefined) {
  136. curModule = i;
  137. break;
  138. }
  139. }
  140. //store config
  141. cssAPI.config = cssAPI.config || config;
  142. name += !parse ? '.css' : '.less';
  143. var fileUrl = req.toUrl(name);
  144. //external URLS don't get added (just like JS requires)
  145. if (fileUrl.substr(0, 7) == 'http://' || fileUrl.substr(0, 8) == 'https://')
  146. return;
  147. //add to the buffer
  148. _cssBuffer[name] = loadCSSFile(fileUrl);
  149. // parse if necessary
  150. if (parse)
  151. _cssBuffer[name] = parse(_cssBuffer[name]);
  152. load();
  153. }
  154. cssAPI.normalize = function(name, normalize) {
  155. if (name.substr(name.length - 4, 4) == '.css')
  156. name = name.substr(0, name.length - 4);
  157. return normalize(name);
  158. }
  159. //list of cssIds included in this layer
  160. var _layerBuffer = [];
  161. var _cssBuffer = [];
  162. cssAPI.write = function(pluginName, moduleName, write, parse) {
  163. //external URLS don't get added (just like JS requires)
  164. if (moduleName.substr(0, 7) == 'http://' || moduleName.substr(0, 8) == 'https://')
  165. return;
  166. var resourceName = moduleName + (!parse ? '.css' : '.less');
  167. _layerBuffer.push(_cssBuffer[resourceName]);
  168. var separateCSS = false;
  169. if (cssAPI.config.separateCSS)
  170. separateCSS = true;
  171. if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
  172. separateCSS = cssAPI.config.modules[curModule].separateCSS;
  173. if (separateCSS)
  174. write.asModule(pluginName + '!' + moduleName, 'define(function(){})');
  175. else
  176. write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.addBuffer('" + resourceName + "'); }); requirejs.s.contexts._.nextTick = requirejs.nextTick;");
  177. }
  178. cssAPI.onLayerEnd = function(write, data, parser) {
  179. firstWrite = true;
  180. //separateCSS parameter set either globally or as a layer setting
  181. var separateCSS = false;
  182. if (cssAPI.config.separateCSS)
  183. separateCSS = true;
  184. if (typeof curModule == 'number' && cssAPI.config.modules[curModule].separateCSS !== undefined)
  185. separateCSS = cssAPI.config.modules[curModule].separateCSS;
  186. curModule = null;
  187. //calculate layer css
  188. var css = _layerBuffer.join('');
  189. if (separateCSS) {
  190. nodePrint('Writing CSS! file: ' + data.name + '\n');
  191. //calculate the css output path for this layer
  192. var path = this.config.appDir ? this.config.baseUrl + data.name + '.css' : cssAPI.config.out.replace(/\.js$/, '.css');
  193. //renormalize the css to the output path
  194. var output = compress(normalize(css, baseUrl, path));
  195. saveFile(path, output);
  196. }
  197. else {
  198. if (css == '')
  199. return;
  200. //write the injection and layer index into the layer
  201. //prepare the css
  202. css = escape(compress(css));
  203. //the code below overrides async require functionality to ensure instant buffer injection
  204. write("requirejs.s.contexts._.nextTick = function(f){f()}; require(['css'], function(css) { css.setBuffer('" + css + (parser ? "', true" : "'") + "); }); requirejs.s.contexts._.nextTick = requirejs.nextTick; ");
  205. }
  206. //clear layer buffer for next layer
  207. _layerBuffer = [];
  208. }
  209. return cssAPI;
  210. });