pre-compile.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. #!/usr/bin/env node
  2. ;(function() {
  3. 'use strict';
  4. /** The Node filesystem module */
  5. var fs = require('fs');
  6. /** Used to minify variables embedded in compiled strings */
  7. var compiledVars = [
  8. 'argsIndex',
  9. 'argsLength',
  10. 'callback',
  11. 'collection',
  12. 'concat',
  13. 'ctor',
  14. 'hasOwnProperty',
  15. 'identity',
  16. 'index',
  17. 'iteratee',
  18. 'iteratorBind',
  19. 'length',
  20. 'nativeKeys',
  21. 'object',
  22. 'ownIndex',
  23. 'ownProps',
  24. 'prop',
  25. 'propertyIsEnumerable',
  26. 'propIndex',
  27. 'props',
  28. 'result',
  29. 'skipProto',
  30. 'slice',
  31. 'stringClass',
  32. 'thisArg',
  33. 'toString',
  34. 'value',
  35. // lesser used variables
  36. 'accumulator',
  37. 'args',
  38. 'arrayLikeClasses',
  39. 'ArrayProto',
  40. 'bind',
  41. 'callee',
  42. 'className',
  43. 'compareAscending',
  44. 'data',
  45. 'forIn',
  46. 'found',
  47. 'funcs',
  48. 'indexOf',
  49. 'indicator',
  50. 'isArguments',
  51. 'isArr',
  52. 'isArray',
  53. 'isFunc',
  54. 'isFunction',
  55. 'isPlainObject',
  56. 'methodName',
  57. 'noaccum',
  58. 'objectClass',
  59. 'objectTypes',
  60. 'pass',
  61. 'properties',
  62. 'property',
  63. 'propsLength',
  64. 'recursive',
  65. 'source',
  66. 'sources',
  67. 'stackLength',
  68. 'target',
  69. 'valueProp',
  70. 'values'
  71. ];
  72. /** Used to minify `compileIterator` option properties */
  73. var iteratorOptions = [
  74. 'args',
  75. 'array',
  76. 'arrayBranch',
  77. 'beforeLoop',
  78. 'bottom',
  79. 'exit',
  80. 'firstArg',
  81. 'hasDontEnumBug',
  82. 'inLoop',
  83. 'init',
  84. 'isKeysFast',
  85. 'object',
  86. 'objectBranch',
  87. 'noArgsEnum',
  88. 'noCharByIndex',
  89. 'shadowed',
  90. 'top',
  91. 'useHas',
  92. 'useStrict'
  93. ];
  94. /** Used to minify variables and string values to a single character */
  95. var minNames = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
  96. minNames.push.apply(minNames, minNames.map(function(value) {
  97. return value + value;
  98. }));
  99. /** Used to protect the specified properties from getting minified */
  100. var propWhitelist = [
  101. '_',
  102. '__chain__',
  103. '__proto__',
  104. '__wrapped__',
  105. 'after',
  106. 'all',
  107. 'amd',
  108. 'any',
  109. 'attachEvent',
  110. 'bind',
  111. 'bindAll',
  112. 'chain',
  113. 'clearTimeout',
  114. 'clone',
  115. 'clones',
  116. 'collect',
  117. 'compact',
  118. 'compose',
  119. 'contains',
  120. 'countBy',
  121. 'criteria',
  122. 'debounce',
  123. 'defaults',
  124. 'defer',
  125. 'delay',
  126. 'detect',
  127. 'difference',
  128. 'drop',
  129. 'each',
  130. 'environment',
  131. 'escape',
  132. 'evaluate',
  133. 'every',
  134. 'exports',
  135. 'extend',
  136. 'filter',
  137. 'find',
  138. 'first',
  139. 'flatten',
  140. 'foldl',
  141. 'foldr',
  142. 'forEach',
  143. 'forIn',
  144. 'forOwn',
  145. 'functions',
  146. 'groupBy',
  147. 'has',
  148. 'head',
  149. 'identity',
  150. 'include',
  151. 'index',
  152. 'indexOf',
  153. 'initial',
  154. 'inject',
  155. 'interpolate',
  156. 'intersection',
  157. 'invert',
  158. 'invoke',
  159. 'isArguments',
  160. 'isArray',
  161. 'isBoolean',
  162. 'isDate',
  163. 'isElement',
  164. 'isEmpty',
  165. 'isEqual',
  166. 'isEqual',
  167. 'isFinite',
  168. 'isFinite',
  169. 'isFunction',
  170. 'isNaN',
  171. 'isNull',
  172. 'isNumber',
  173. 'isObject',
  174. 'isRegExp',
  175. 'isString',
  176. 'isUndefined',
  177. 'keys',
  178. 'last',
  179. 'lastIndexOf',
  180. 'map',
  181. 'max',
  182. 'memoize',
  183. 'merge',
  184. 'methods',
  185. 'min',
  186. 'mixin',
  187. 'noConflict',
  188. 'object',
  189. 'omit',
  190. 'once',
  191. 'opera',
  192. 'pairs',
  193. 'partial',
  194. 'pick',
  195. 'pluck',
  196. 'random',
  197. 'range',
  198. 'reduce',
  199. 'reduceRight',
  200. 'reject',
  201. 'rest',
  202. 'result',
  203. 'select',
  204. 'setTimeout',
  205. 'shuffle',
  206. 'size',
  207. 'some',
  208. 'sortBy',
  209. 'sortedIndex',
  210. 'source',
  211. 'sources',
  212. 'stackA',
  213. 'stackB',
  214. 'tail',
  215. 'take',
  216. 'tap',
  217. 'template',
  218. 'templateSettings',
  219. 'throttle',
  220. 'times',
  221. 'toArray',
  222. 'unescape',
  223. 'union',
  224. 'uniq',
  225. 'unique',
  226. 'uniqueId',
  227. 'value',
  228. 'values',
  229. 'variable',
  230. 'VERSION',
  231. 'where',
  232. 'without',
  233. 'wrap',
  234. 'zip',
  235. // properties used by underscore.js
  236. '_chain',
  237. '_wrapped'
  238. ];
  239. /*--------------------------------------------------------------------------*/
  240. /**
  241. * Pre-process a given Lo-Dash `source`, preparing it for minification.
  242. *
  243. * @param {String} source The source to process.
  244. * @returns {String} Returns the processed source.
  245. */
  246. function preprocess(source) {
  247. // remove copyright to add later in post-compile.js
  248. source = source.replace(/\/\*![\s\S]+?\*\//, '');
  249. // remove unrecognized JSDoc tags so Closure Compiler won't complain
  250. source = source.replace(/@(?:alias|category)\b.*/g, '');
  251. // add brackets to whitelisted properties so Closure Compiler won't mung them
  252. // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
  253. source = source.replace(RegExp('\\.(' + propWhitelist.join('|') + ')\\b', 'g'), "['$1']");
  254. // remove brackets from `_.escape()` in `_.template`
  255. source = source.replace(/__e *= *_\['escape']/g, '__e=_.escape');
  256. // remove brackets from `_.escape()` in underscore.js `_.template`
  257. source = source.replace(/_\['escape'\]\(__t'/g, '_.escape(__t');
  258. // remove brackets from `collection.indexOf` in `_.contains`
  259. source = source.replace("collection['indexOf'](target)", 'collection.indexOf(target)');
  260. // remove brackets from `result[length].value` in `_.sortBy`
  261. source = source.replace("result[length]['value']", 'result[length].value');
  262. // remove whitespace from string literals
  263. source = source.replace(/'(?:(?=(\\?))\1.)*?'/g, function(string) {
  264. // avoids removing the '\n' of the `stringEscapes` object
  265. return string.replace(/\[object |delete |else if|function | in |return\s+[\w']|throw |typeof |use strict|var |@ |'\\n'|\\\\n|\\n|\s+/g, function(match) {
  266. return match == false || match == '\\n' ? '' : match;
  267. });
  268. });
  269. // add newline to `+"__p+='"` in underscore.js `_.template`
  270. source = source.replace(/\+"__p\+='"/g, '+"\\n__p+=\'"');
  271. // remove whitespace from `_.template` related regexes
  272. source = source.replace(/(?:reDelimiterCode\w+|reEmptyString\w+|reInsertVariable) *=.+/g, function(match) {
  273. return match.replace(/ |\\n/g, '');
  274. });
  275. // remove newline from double-quoted strings in `_.template`
  276. source = source
  277. .replace('"\';\\n__with ("', '"\';__with("')
  278. .replace('"\\n}__\\n__p += \'"', '"}____p+=\'"')
  279. .replace('"__p = \'"', '"__p=\'"')
  280. .replace('"\';\\n"', '"\';"')
  281. .replace("') {\\n'", "'){'")
  282. // remove `useSourceURL` variable
  283. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var useSourceURL[\s\S]+?catch[^}]+}\n/, '');
  284. // remove debug sourceURL use in `_.template`
  285. source = source.replace(/(?:\s*\/\/.*\n)* *if *\(useSourceURL[^}]+}/, '');
  286. // minify internal properties used by 'compareAscending', `_.clone`, `_.isEqual`, `_.merge`, and `_.sortBy`
  287. (function() {
  288. var properties = ['clones', 'criteria', 'index', 'sources', 'thorough', 'value', 'values'],
  289. snippets = source.match(/( +)(?:function (?:clone|compareAscending|isEqual)|var merge|var sortBy)\b[\s\S]+?\n\1}/g);
  290. if (!snippets) {
  291. return;
  292. }
  293. snippets.forEach(function(snippet) {
  294. var modified = snippet,
  295. isCompilable = /(?:var merge|var sortBy)\b/.test(modified),
  296. isInlined = !/\bcreateIterator\b/.test(modified);
  297. // minify properties
  298. properties.forEach(function(property, index) {
  299. var reBracketProp = RegExp("\\['(" + property + ")'\\]", 'g'),
  300. reDotProp = RegExp('\\.' + property + '\\b', 'g'),
  301. rePropColon = RegExp("([^?\\s])\\s*([\"'])?\\b" + property + "\\2 *:", 'g');
  302. if (isCompilable) {
  303. // add quotes around properties in the inlined `_.merge` and `_.sortBy`
  304. // of the mobile build so Closure Compiler won't mung them
  305. if (isInlined) {
  306. modified = modified
  307. .replace(reBracketProp, "['" + minNames[index] + "']")
  308. .replace(reDotProp, "['" + minNames[index] + "']")
  309. .replace(rePropColon, "$1'" + minNames[index] + "':");
  310. }
  311. else {
  312. modified = modified
  313. .replace(reBracketProp, '.' + minNames[index])
  314. .replace(reDotProp, '.' + minNames[index])
  315. .replace(rePropColon, '$1' + minNames[index] + ':');
  316. }
  317. }
  318. else {
  319. modified = modified
  320. .replace(reBracketProp, "['" + minNames[index] + "']")
  321. .replace(reDotProp, '.' + minNames[index])
  322. .replace(rePropColon, "$1'" + minNames[index] + "':")
  323. // correct `value.source` in regexp branch of `_.clone`
  324. if (property == 'source') {
  325. modified = modified.replace("value['" + minNames[index] + "']", "value['source']");
  326. }
  327. }
  328. });
  329. // replace with modified snippet
  330. source = source.replace(snippet, modified);
  331. });
  332. }());
  333. // minify all compilable snippets
  334. var snippets = source.match(
  335. RegExp([
  336. // match the `iteratorTemplate`
  337. '( +)var iteratorTemplate\\b[\\s\\S]+?\\n\\1}',
  338. // match methods created by `createIterator` calls
  339. 'createIterator\\((?:{|[a-zA-Z]+)[\\s\\S]+?\\);\\n',
  340. // match variables storing `createIterator` options
  341. '( +)var [a-zA-Z]+IteratorOptions\\b[\\s\\S]+?\\n\\2}',
  342. // match the the `createIterator` function
  343. '( +)function createIterator\\b[\\s\\S]+?\\n\\3}'
  344. ].join('|'), 'g')
  345. );
  346. // exit early if no compilable snippets
  347. if (!snippets) {
  348. return source;
  349. }
  350. snippets.forEach(function(snippet, index) {
  351. var isCreateIterator = /function createIterator\b/.test(snippet),
  352. isIteratorTemplate = /var iteratorTemplate\b/.test(snippet),
  353. modified = snippet;
  354. // add brackets to whitelisted properties so Closure Compiler won't mung them
  355. modified = modified.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']");
  356. if (isCreateIterator) {
  357. // replace with modified snippet early and clip snippet to the `factory`
  358. // call so other arguments aren't minified
  359. source = source.replace(snippet, modified);
  360. snippet = modified = modified.replace(/factory\([\s\S]+$/, '');
  361. }
  362. // minify snippet variables / arguments
  363. compiledVars.forEach(function(variable, index) {
  364. // ensure properties in compiled strings aren't minified
  365. modified = modified.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minNames[index]);
  366. // correct `typeof x == 'object'`
  367. if (variable == 'object') {
  368. modified = modified.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'");
  369. }
  370. });
  371. // minify `createIterator` option property names
  372. iteratorOptions.forEach(function(property, index) {
  373. if (isIteratorTemplate) {
  374. // minify property names as interpolated template variables
  375. modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]);
  376. }
  377. else {
  378. if (property == 'array' || property == 'object') {
  379. // minify "array" and "object" sub property names
  380. modified = modified.replace(RegExp("'" + property + "'( *[\\]:])", 'g'), "'" + minNames[index] + "'$1");
  381. }
  382. else {
  383. // minify property name strings
  384. modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'");
  385. // minify property names in regexes and accessors
  386. if (isCreateIterator) {
  387. modified = modified.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]);
  388. }
  389. }
  390. }
  391. });
  392. // replace with modified snippet
  393. source = source.replace(snippet, modified);
  394. });
  395. return source;
  396. }
  397. /*--------------------------------------------------------------------------*/
  398. // expose `preprocess`
  399. if (module != require.main) {
  400. module.exports = preprocess;
  401. }
  402. else {
  403. // read the Lo-Dash source file from the first argument if the script
  404. // was invoked directly (e.g. `node pre-compile.js source.js`) and write to
  405. // the same file
  406. (function() {
  407. var source = fs.readFileSync(process.argv[2], 'utf8');
  408. fs.writeFileSync(process.argv[2], preprocess(source), 'utf8');
  409. }());
  410. }
  411. }());