PageRenderTime 61ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/build/jslib/optimize.js

https://github.com/shiki/r.js
JavaScript | 293 lines | 166 code | 38 blank | 89 comment | 38 complexity | b05a14e46f4f653803b3b3d3bae87b6b MD5 | raw file
  1. /**
  2. * @license Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
  3. * Available via the MIT or new BSD license.
  4. * see: http://github.com/jrburke/requirejs for details
  5. */
  6. /*jslint plusplus: false, nomen: false, regexp: false, strict: false */
  7. /*global define: false */
  8. define([ 'lang', 'logger', 'env!env/optimize', 'env!env/file', 'parse',
  9. 'pragma', 'uglifyjs/index'],
  10. function (lang, logger, envOptimize, file, parse,
  11. pragma, uglify) {
  12. var optimize,
  13. cssImportRegExp = /\@import\s+(url\()?\s*([^);]+)\s*(\))?([\w, ]*)(;)?/g,
  14. cssUrlRegExp = /\url\(\s*([^\)]+)\s*\)?/g;
  15. /**
  16. * If an URL from a CSS url value contains start/end quotes, remove them.
  17. * This is not done in the regexp, since my regexp fu is not that strong,
  18. * and the CSS spec allows for ' and " in the URL if they are backslash escaped.
  19. * @param {String} url
  20. */
  21. function cleanCssUrlQuotes(url) {
  22. //Make sure we are not ending in whitespace.
  23. //Not very confident of the css regexps above that there will not be ending
  24. //whitespace.
  25. url = url.replace(/\s+$/, "");
  26. if (url.charAt(0) === "'" || url.charAt(0) === "\"") {
  27. url = url.substring(1, url.length - 1);
  28. }
  29. return url;
  30. }
  31. /**
  32. * Inlines nested stylesheets that have @import calls in them.
  33. * @param {String} fileName
  34. * @param {String} fileContents
  35. * @param {String} [cssImportIgnore]
  36. */
  37. function flattenCss(fileName, fileContents, cssImportIgnore) {
  38. //Find the last slash in the name.
  39. fileName = fileName.replace(lang.backSlashRegExp, "/");
  40. var endIndex = fileName.lastIndexOf("/"),
  41. //Make a file path based on the last slash.
  42. //If no slash, so must be just a file name. Use empty string then.
  43. filePath = (endIndex !== -1) ? fileName.substring(0, endIndex + 1) : "";
  44. //Make sure we have a delimited ignore list to make matching faster
  45. if (cssImportIgnore && cssImportIgnore.charAt(cssImportIgnore.length - 1) !== ",") {
  46. cssImportIgnore += ",";
  47. }
  48. return fileContents.replace(cssImportRegExp, function (fullMatch, urlStart, importFileName, urlEnd, mediaTypes) {
  49. //Only process media type "all" or empty media type rules.
  50. if (mediaTypes && ((mediaTypes.replace(/^\s\s*/, '').replace(/\s\s*$/, '')) !== "all")) {
  51. return fullMatch;
  52. }
  53. importFileName = cleanCssUrlQuotes(importFileName);
  54. //Ignore the file import if it is part of an ignore list.
  55. if (cssImportIgnore && cssImportIgnore.indexOf(importFileName + ",") !== -1) {
  56. return fullMatch;
  57. }
  58. //Make sure we have a unix path for the rest of the operation.
  59. importFileName = importFileName.replace(lang.backSlashRegExp, "/");
  60. try {
  61. //if a relative path, then tack on the filePath.
  62. //If it is not a relative path, then the readFile below will fail,
  63. //and we will just skip that import.
  64. var fullImportFileName = importFileName.charAt(0) === "/" ? importFileName : filePath + importFileName,
  65. importContents = file.readFile(fullImportFileName), i,
  66. importEndIndex, importPath, fixedUrlMatch, colonIndex, parts;
  67. //Make sure to flatten any nested imports.
  68. importContents = flattenCss(fullImportFileName, importContents);
  69. //Make the full import path
  70. importEndIndex = importFileName.lastIndexOf("/");
  71. //Make a file path based on the last slash.
  72. //If no slash, so must be just a file name. Use empty string then.
  73. importPath = (importEndIndex !== -1) ? importFileName.substring(0, importEndIndex + 1) : "";
  74. //Modify URL paths to match the path represented by this file.
  75. importContents = importContents.replace(cssUrlRegExp, function (fullMatch, urlMatch) {
  76. fixedUrlMatch = cleanCssUrlQuotes(urlMatch);
  77. fixedUrlMatch = fixedUrlMatch.replace(lang.backSlashRegExp, "/");
  78. //Only do the work for relative URLs. Skip things that start with / or have
  79. //a protocol.
  80. colonIndex = fixedUrlMatch.indexOf(":");
  81. if (fixedUrlMatch.charAt(0) !== "/" && (colonIndex === -1 || colonIndex > fixedUrlMatch.indexOf("/"))) {
  82. //It is a relative URL, tack on the path prefix
  83. urlMatch = importPath + fixedUrlMatch;
  84. } else {
  85. logger.trace(importFileName + "\n URL not a relative URL, skipping: " + urlMatch);
  86. }
  87. //Collapse .. and .
  88. parts = urlMatch.split("/");
  89. for (i = parts.length - 1; i > 0; i--) {
  90. if (parts[i] === ".") {
  91. parts.splice(i, 1);
  92. } else if (parts[i] === "..") {
  93. if (i !== 0 && parts[i - 1] !== "..") {
  94. parts.splice(i - 1, 2);
  95. i -= 1;
  96. }
  97. }
  98. }
  99. return "url(" + parts.join("/") + ")";
  100. });
  101. return importContents;
  102. } catch (e) {
  103. logger.trace(fileName + "\n Cannot inline css import, skipping: " + importFileName);
  104. return fullMatch;
  105. }
  106. });
  107. }
  108. optimize = {
  109. /**
  110. * Optimizes a file that contains JavaScript content. Optionally collects
  111. * plugin resources mentioned in a file, and then passes the content
  112. * through an minifier if one is specified via config.optimize.
  113. *
  114. * @param {String} fileName the name of the file to optimize
  115. * @param {String} outFileName the name of the file to use for the
  116. * saved optimized content.
  117. * @param {Object} config the build config object.
  118. * @param {String} [moduleName] the module name to use for the file.
  119. * Used for plugin resource collection.
  120. * @param {Array} [pluginCollector] storage for any plugin resources
  121. * found.
  122. */
  123. jsFile: function (fileName, outFileName, config, moduleName, pluginCollector) {
  124. var parts = (config.optimize + "").split('.'),
  125. optimizerName = parts[0],
  126. keepLines = parts[1] === 'keepLines',
  127. fileContents, optFunc, deps, i, dep;
  128. fileContents = file.readFile(fileName);
  129. //Apply pragmas/namespace renaming
  130. fileContents = pragma.process(fileName, fileContents, config, 'OnSave');
  131. //If there is a plugin collector, scan the file for plugin resources.
  132. if (config.optimizeAllPluginResources && pluginCollector) {
  133. try {
  134. deps = parse.findDependencies(fileName, fileContents);
  135. if (deps.length) {
  136. for (i = 0; (dep = deps[i]); i++) {
  137. if (dep.indexOf('!') !== -1) {
  138. (pluginCollector[moduleName] ||
  139. (pluginCollector[moduleName] = [])).push(dep);
  140. }
  141. }
  142. }
  143. } catch (e) {
  144. logger.error('Parse error looking for plugin resources in ' +
  145. fileName + ', skipping.');
  146. }
  147. }
  148. //Optimize the JS files if asked.
  149. if (optimizerName && optimizerName !== 'none') {
  150. optFunc = envOptimize[optimizerName] || optimize.optimizers[optimizerName];
  151. if (!optFunc) {
  152. throw new Error('optimizer with name of "' +
  153. optimizerName +
  154. '" not found for this environment');
  155. }
  156. fileContents = optFunc(fileName, fileContents, keepLines,
  157. config[optimizerName]);
  158. }
  159. // A hacky solution to add semicolons at the end of every optimized script.
  160. // This should prevent erratic errors like this:
  161. //
  162. // define(..)(function()...).call(this) is not a function
  163. //
  164. // This is somehow due to the generated code that was merged from
  165. // pure JS files and CoffeeScript files:
  166. //
  167. // define([..], function() {...})
  168. // (function() { ...}).call(this)
  169. //
  170. fileContents = fileContents + ';';
  171. file.saveUtf8File(outFileName, fileContents);
  172. },
  173. /**
  174. * Optimizes one CSS file, inlining @import calls, stripping comments, and
  175. * optionally removes line returns.
  176. * @param {String} fileName the path to the CSS file to optimize
  177. * @param {String} outFileName the path to save the optimized file.
  178. * @param {Object} config the config object with the optimizeCss and
  179. * cssImportIgnore options.
  180. */
  181. cssFile: function (fileName, outFileName, config) {
  182. //Read in the file. Make sure we have a JS string.
  183. var originalFileContents = file.readFile(fileName),
  184. fileContents = flattenCss(fileName, originalFileContents, config.cssImportIgnore),
  185. startIndex, endIndex;
  186. //Do comment removal.
  187. try {
  188. startIndex = -1;
  189. //Get rid of comments.
  190. while ((startIndex = fileContents.indexOf("/*")) !== -1) {
  191. endIndex = fileContents.indexOf("*/", startIndex + 2);
  192. if (endIndex === -1) {
  193. throw "Improper comment in CSS file: " + fileName;
  194. }
  195. fileContents = fileContents.substring(0, startIndex) + fileContents.substring(endIndex + 2, fileContents.length);
  196. }
  197. //Get rid of newlines.
  198. if (config.optimizeCss.indexOf(".keepLines") === -1) {
  199. fileContents = fileContents.replace(/[\r\n]/g, "");
  200. fileContents = fileContents.replace(/\s+/g, " ");
  201. fileContents = fileContents.replace(/\{\s/g, "{");
  202. fileContents = fileContents.replace(/\s\}/g, "}");
  203. } else {
  204. //Remove multiple empty lines.
  205. fileContents = fileContents.replace(/(\r\n)+/g, "\r\n");
  206. fileContents = fileContents.replace(/(\n)+/g, "\n");
  207. }
  208. } catch (e) {
  209. fileContents = originalFileContents;
  210. logger.error("Could not optimized CSS file: " + fileName + ", error: " + e);
  211. }
  212. file.saveUtf8File(outFileName, fileContents);
  213. },
  214. /**
  215. * Optimizes CSS files, inlining @import calls, stripping comments, and
  216. * optionally removes line returns.
  217. * @param {String} startDir the path to the top level directory
  218. * @param {Object} config the config object with the optimizeCss and
  219. * cssImportIgnore options.
  220. */
  221. css: function (startDir, config) {
  222. if (config.optimizeCss.indexOf("standard") !== -1) {
  223. var i, fileName,
  224. fileList = file.getFilteredFileList(startDir, /\.css$/, true);
  225. if (fileList) {
  226. for (i = 0; i < fileList.length; i++) {
  227. fileName = fileList[i];
  228. logger.trace("Optimizing (" + config.optimizeCss + ") CSS file: " + fileName);
  229. optimize.cssFile(fileName, fileName, config);
  230. }
  231. }
  232. }
  233. },
  234. optimizers: {
  235. uglify: function (fileName, fileContents, keepLines, config) {
  236. var parser = uglify.parser,
  237. processor = uglify.uglify,
  238. ast, genCodeConfig;
  239. config = config || {};
  240. genCodeConfig = config.gen_codeOptions || keepLines;
  241. logger.trace("Uglifying file: " + fileName);
  242. try {
  243. ast = parser.parse(fileContents, config.strict_semicolons);
  244. ast = processor.ast_mangle(ast, config.do_toplevel);
  245. ast = processor.ast_squeeze(ast, config.ast_squeezeOptions);
  246. fileContents = processor.gen_code(ast, genCodeConfig);
  247. } catch (e) {
  248. logger.error('Cannot uglify file: ' + fileName + '. Skipping it. Error is:\n' + e.toString());
  249. }
  250. return fileContents;
  251. }
  252. }
  253. };
  254. return optimize;
  255. });