build.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345
  1. #!/usr/bin/env node
  2. ;(function() {
  3. 'use strict';
  4. /** Load modules */
  5. var fs = require('fs'),
  6. path = require('path'),
  7. vm = require('vm'),
  8. minify = require(path.join(__dirname, 'build', 'minify')),
  9. _ = require(path.join(__dirname, 'lodash'));
  10. /** The current working directory */
  11. var cwd = process.cwd();
  12. /** Shortcut used to convert array-like objects to arrays */
  13. var slice = [].slice;
  14. /** Shortcut to the `stdout` object */
  15. var stdout = process.stdout;
  16. /** Used to associate aliases with their real names */
  17. var aliasToRealMap = {
  18. 'all': 'every',
  19. 'any': 'some',
  20. 'collect': 'map',
  21. 'detect': 'find',
  22. 'drop': 'rest',
  23. 'each': 'forEach',
  24. 'foldl': 'reduce',
  25. 'foldr': 'reduceRight',
  26. 'head': 'first',
  27. 'include': 'contains',
  28. 'inject': 'reduce',
  29. 'methods': 'functions',
  30. 'select': 'filter',
  31. 'tail': 'rest',
  32. 'take': 'first',
  33. 'unique': 'uniq'
  34. };
  35. /** Used to associate real names with their aliases */
  36. var realToAliasMap = {
  37. 'contains': ['include'],
  38. 'every': ['all'],
  39. 'filter': ['select'],
  40. 'find': ['detect'],
  41. 'first': ['head', 'take'],
  42. 'forEach': ['each'],
  43. 'functions': ['methods'],
  44. 'map': ['collect'],
  45. 'reduce': ['foldl', 'inject'],
  46. 'reduceRight': ['foldr'],
  47. 'rest': ['drop', 'tail'],
  48. 'some': ['any'],
  49. 'uniq': ['unique']
  50. };
  51. /** Used to track function dependencies */
  52. var dependencyMap = {
  53. 'after': [],
  54. 'bind': ['isFunction'],
  55. 'bindAll': ['bind', 'isFunction'],
  56. 'chain': ['mixin'],
  57. 'clone': ['extend', 'forIn', 'forOwn', 'isArguments', 'isFunction'],
  58. 'compact': [],
  59. 'compose': [],
  60. 'contains': [],
  61. 'countBy': [],
  62. 'debounce': [],
  63. 'defaults': ['isArguments'],
  64. 'defer': [],
  65. 'delay': [],
  66. 'difference': ['indexOf'],
  67. 'escape': [],
  68. 'every': ['identity'],
  69. 'extend': ['isArguments'],
  70. 'filter': ['identity'],
  71. 'find': [],
  72. 'first': [],
  73. 'flatten': ['isArray'],
  74. 'forEach': [],
  75. 'forIn': ['isArguments'],
  76. 'forOwn': ['isArguments'],
  77. 'functions': ['isArguments', 'isFunction'],
  78. 'groupBy': [],
  79. 'has': [],
  80. 'identity': [],
  81. 'indexOf': ['sortedIndex'],
  82. 'initial': [],
  83. 'intersection': ['indexOf'],
  84. 'invert': [],
  85. 'invoke': [],
  86. 'isArguments': [],
  87. 'isArray': [],
  88. 'isBoolean': [],
  89. 'isDate': [],
  90. 'isElement': [],
  91. 'isEmpty': ['isArguments', 'isFunction'],
  92. 'isEqual': ['isArguments', 'isFunction'],
  93. 'isFinite': [],
  94. 'isFunction': [],
  95. 'isNaN': [],
  96. 'isNull': [],
  97. 'isNumber': [],
  98. 'isObject': [],
  99. 'isRegExp': [],
  100. 'isString': [],
  101. 'isUndefined': [],
  102. 'keys': ['isArguments'],
  103. 'last': [],
  104. 'lastIndexOf': [],
  105. 'map': ['identity'],
  106. 'max': [],
  107. 'memoize': [],
  108. 'merge': ['isArguments', 'isArray', 'forIn'],
  109. 'min': [],
  110. 'mixin': ['forEach', 'functions'],
  111. 'noConflict': [],
  112. 'object': [],
  113. 'omit': ['indexOf', 'isArguments'],
  114. 'once': [],
  115. 'pairs': [],
  116. 'partial': [],
  117. 'pick': [],
  118. 'pluck': [],
  119. 'random': [],
  120. 'range': [],
  121. 'reduce': [],
  122. 'reduceRight': ['keys'],
  123. 'reject': ['identity'],
  124. 'rest': [],
  125. 'result': ['isFunction'],
  126. 'shuffle': [],
  127. 'size': ['keys'],
  128. 'some': ['identity'],
  129. 'sortBy': [],
  130. 'sortedIndex': ['bind'],
  131. 'tap': ['mixin'],
  132. 'template': ['escape'],
  133. 'throttle': [],
  134. 'times': [],
  135. 'toArray': ['isFunction', 'values'],
  136. 'unescape': [],
  137. 'union': ['indexOf'],
  138. 'uniq': ['identity', 'indexOf'],
  139. 'uniqueId': [],
  140. 'value': ['mixin'],
  141. 'values': ['isArguments'],
  142. 'where': ['forIn'],
  143. 'without': ['indexOf'],
  144. 'wrap': [],
  145. 'zip': ['max', 'pluck']
  146. };
  147. /** Used to inline `iteratorTemplate` */
  148. var iteratorOptions = [
  149. 'args',
  150. 'array',
  151. 'arrayBranch',
  152. 'beforeLoop',
  153. 'bottom',
  154. 'exit',
  155. 'firstArg',
  156. 'hasDontEnumBug',
  157. 'inLoop',
  158. 'init',
  159. 'isKeysFast',
  160. 'object',
  161. 'objectBranch',
  162. 'noArgsEnum',
  163. 'noCharByIndex',
  164. 'shadowed',
  165. 'top',
  166. 'useHas',
  167. 'useStrict'
  168. ];
  169. /** List of all Lo-Dash methods */
  170. var allMethods = _.keys(dependencyMap);
  171. /** List Backbone's Lo-Dash dependencies */
  172. var backboneDependencies = [
  173. 'bind',
  174. 'bindAll',
  175. 'clone',
  176. 'contains',
  177. 'escape',
  178. 'every',
  179. 'extend',
  180. 'filter',
  181. 'find',
  182. 'first',
  183. 'forEach',
  184. 'groupBy',
  185. 'has',
  186. 'indexOf',
  187. 'initial',
  188. 'invoke',
  189. 'isArray',
  190. 'isEmpty',
  191. 'isEqual',
  192. 'isFunction',
  193. 'isObject',
  194. 'isRegExp',
  195. 'keys',
  196. 'last',
  197. 'lastIndexOf',
  198. 'map',
  199. 'max',
  200. 'min',
  201. 'mixin',
  202. 'reduce',
  203. 'reduceRight',
  204. 'reject',
  205. 'rest',
  206. 'result',
  207. 'shuffle',
  208. 'size',
  209. 'some',
  210. 'sortBy',
  211. 'sortedIndex',
  212. 'toArray',
  213. 'uniqueId',
  214. 'without'
  215. ];
  216. /** List of methods used by Underscore */
  217. var underscoreMethods = _.without.apply(_, [allMethods].concat([
  218. 'countBy',
  219. 'forIn',
  220. 'forOwn',
  221. 'invert',
  222. 'merge',
  223. 'object',
  224. 'omit',
  225. 'pairs',
  226. 'partial',
  227. 'random',
  228. 'unescape',
  229. 'where'
  230. ]));
  231. /** List of ways to export the `LoDash` function */
  232. var exportsAll = [
  233. 'amd',
  234. 'commonjs',
  235. 'global',
  236. 'node'
  237. ];
  238. /*--------------------------------------------------------------------------*/
  239. /**
  240. * Removes unnecessary comments, whitespace, and pseudo private properties.
  241. *
  242. * @private
  243. * @param {String} source The source to process.
  244. * @returns {String} Returns the modified source.
  245. */
  246. function cleanupSource(source) {
  247. return source
  248. // remove pseudo private properties
  249. .replace(/(?:(?:\s*\/\/.*)*\s*lodash\._[^=]+=.+\n)+/g, '\n')
  250. // remove lines with just whitespace and semicolons
  251. .replace(/^ *;\n/gm, '')
  252. // consolidate consecutive horizontal rule comment separators
  253. .replace(/(?:\s*\/\*-+\*\/\s*){2,}/g, function(separators) {
  254. return separators.match(/^\s*/)[0] + separators.slice(separators.lastIndexOf('/*'));
  255. });
  256. }
  257. /**
  258. * Writes the help message to standard output.
  259. *
  260. * @private
  261. */
  262. function displayHelp() {
  263. console.log([
  264. '',
  265. ' Commands:',
  266. '',
  267. ' lodash backbone Build with only methods required by Backbone',
  268. ' lodash csp Build supporting default Content Security Policy restrictions',
  269. ' lodash legacy Build tailored for older browsers without ES5 support',
  270. ' lodash mobile Build with IE < 9 bug fixes & method compilation removed',
  271. ' lodash strict Build with `_.bindAll`, `_.defaults`, & `_.extend` in strict mode',
  272. ' lodash underscore Build with iteration fixes removed and only Underscore’s API',
  273. ' lodash exclude=... Comma separated names of methods to exclude from the build',
  274. ' lodash include=... Comma separated names of methods to include in the build',
  275. ' lodash category=... Comma separated categories of methods to include in the build',
  276. ' (i.e. “arrays”, “chaining”, “collections”, “functions”, “objects”, and “utilities”)',
  277. ' lodash exports=... Comma separated names of ways to export the `LoDash` function',
  278. ' (i.e. “amd”, “commonjs”, “global”, “node”, and “none”)',
  279. ' lodash iife=... Code to replace the immediately-invoked function expression that wraps Lo-Dash',
  280. ' (e.g. “!function(window,undefined){%output%}(this)”)',
  281. '',
  282. ' All arguments, except `exclude` with `include` & `legacy` with `csp`/`mobile`,',
  283. ' may be combined.',
  284. '',
  285. ' Options:',
  286. '',
  287. ' -c, --stdout Write output to standard output',
  288. ' -h, --help Display help information',
  289. ' -o, --output Write output to a given path/filename',
  290. ' -s, --silent Skip status updates normally logged to the console',
  291. ' -V, --version Output current version of Lo-Dash',
  292. ''
  293. ].join('\n'));
  294. }
  295. /**
  296. * Gets the aliases associated with a given function name.
  297. *
  298. * @private
  299. * @param {String} methodName The name of the method to get aliases for.
  300. * @returns {Array} Returns an array of aliases.
  301. */
  302. function getAliases(methodName) {
  303. return realToAliasMap[methodName] || [];
  304. }
  305. /**
  306. * Gets the Lo-Dash method assignments snippet from `source`.
  307. *
  308. * @private
  309. * @param {String} source The source to inspect.
  310. * @returns {String} Returns the method assignments snippet.
  311. */
  312. function getMethodAssignments(source) {
  313. return (source.match(/lodash\.VERSION *= *[\s\S]+?\/\*-+\*\/\n/) || [''])[0];
  314. }
  315. /**
  316. * Gets an array of depenants for a method by a given name.
  317. *
  318. * @private
  319. * @param {String} methodName The name of the method to query.
  320. * @returns {Array} Returns an array of method dependants.
  321. */
  322. function getDependants(methodName) {
  323. // iterate over the `dependencyMap`, adding the names of methods that
  324. // have `methodName` as a dependency
  325. return _.reduce(dependencyMap, function(result, dependencies, otherName) {
  326. if (_.contains(dependencies, methodName)) {
  327. result.push(otherName);
  328. }
  329. return result;
  330. }, []);
  331. }
  332. /**
  333. * Gets an array of dependencies for a given method name. If passed an array
  334. * of dependencies it will return an array containing the given dependencies
  335. * plus any additional detected sub-dependencies.
  336. *
  337. * @private
  338. * @param {Array|String} methodName A single method name or array of
  339. * dependencies to query.
  340. * @returns {Array} Returns an array of method dependencies.
  341. */
  342. function getDependencies(methodName) {
  343. var dependencies = Array.isArray(methodName) ? methodName : dependencyMap[methodName];
  344. if (!dependencies) {
  345. return [];
  346. }
  347. // recursively accumulate the dependencies of the `methodName` function, and
  348. // the dependencies of its dependencies, and so on.
  349. return _.uniq(dependencies.reduce(function(result, otherName) {
  350. result.push.apply(result, getDependencies(otherName).concat(otherName));
  351. return result;
  352. }, []));
  353. }
  354. /**
  355. * Gets the formatted source of the given function.
  356. *
  357. * @private
  358. * @param {Function} func The function to process.
  359. * @returns {String} Returns the formatted source.
  360. */
  361. function getFunctionSource(func) {
  362. var source = func.source || (func + '');
  363. // format leading whitespace
  364. return source.replace(/\n(?:.*)/g, function(match, index) {
  365. match = match.slice(1);
  366. return (
  367. match == '}' && source.indexOf('}', index + 2) == -1 ? '\n ' : '\n '
  368. ) + match;
  369. });
  370. }
  371. /**
  372. * Gets the `_.isArguments` fallback snippet from `source`.
  373. *
  374. * @private
  375. * @param {String} source The source to inspect.
  376. * @returns {String} Returns the `isArguments` fallback snippet.
  377. */
  378. function getIsArgumentsFallback(source) {
  379. return (source.match(/(?:\s*\/\/.*)*\n( +)if *\(noArgsClass[\s\S]+?};\n\1}/) || [''])[0];
  380. }
  381. /**
  382. * Gets the real name, not alias, of a given method name.
  383. *
  384. * @private
  385. * @param {String} methodName The name of the method to resolve.
  386. * @returns {String} Returns the real method name.
  387. */
  388. function getRealName(methodName) {
  389. return aliasToRealMap[methodName] || methodName;
  390. }
  391. /**
  392. * Determines if all functions of the given names have been removed from `source`.
  393. *
  394. * @private
  395. * @param {String} source The source to inspect.
  396. * @param {String} [funcName1, funcName2, ...] The names of functions to check.
  397. * @returns {Boolean} Returns `true` if all functions have been removed, else `false`.
  398. */
  399. function isRemoved(source) {
  400. return slice.call(arguments, 1).every(function(funcName) {
  401. return !matchFunction(source, funcName);
  402. });
  403. }
  404. /**
  405. * Searches `source` for a `funcName` function declaration, expression, or
  406. * assignment and returns the matched snippet.
  407. *
  408. * @private
  409. * @param {String} source The source to inspect.
  410. * @param {String} funcName The name of the function to match.
  411. * @returns {String} Returns the matched function snippet.
  412. */
  413. function matchFunction(source, funcName) {
  414. var result = source.match(RegExp(
  415. // match multi-line comment block (could be on a single line)
  416. '\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/\\n' +
  417. // begin non-capturing group
  418. '(?:' +
  419. // match a function declaration
  420. '( +)function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' +
  421. // match a variable declaration with `createIterator`
  422. ' +var ' + funcName + ' *=.*?createIterator\\((?:{|[a-zA-Z])[\\s\\S]+?\\);|' +
  423. // match a variable declaration with function expression
  424. '( +)var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\2};' +
  425. // end non-capturing group
  426. ')\\n'
  427. ));
  428. return result ? result[0] : '';
  429. }
  430. /**
  431. * Removes the all references to `refName` from `createIterator` in `source`.
  432. *
  433. * @private
  434. * @param {String} source The source to process.
  435. * @param {String} refName The name of the reference to remove.
  436. * @returns {String} Returns the modified source.
  437. */
  438. function removeFromCreateIterator(source, refName) {
  439. var snippet = matchFunction(source, 'createIterator');
  440. if (snippet) {
  441. // clip the snippet at the `factory` assignment
  442. snippet = snippet.match(/Function\([\s\S]+$/)[0];
  443. var modified = snippet.replace(RegExp('\\b' + refName + '\\b,? *', 'g'), '');
  444. source = source.replace(snippet, modified);
  445. }
  446. return source;
  447. }
  448. /**
  449. * Removes the `funcName` function declaration, expression, or assignment and
  450. * associated code from `source`.
  451. *
  452. * @private
  453. * @param {String} source The source to process.
  454. * @param {String} funcName The name of the function to remove.
  455. * @returns {String} Returns the source with the function removed.
  456. */
  457. function removeFunction(source, funcName) {
  458. var modified,
  459. snippet = matchFunction(source, funcName);
  460. // exit early if function is not found
  461. if (!snippet) {
  462. return source;
  463. }
  464. // remove function
  465. source = source.replace(matchFunction(source, funcName), '');
  466. // grab the method assignments snippet
  467. snippet = getMethodAssignments(source);
  468. // remove assignment and aliases
  469. modified = getAliases(funcName).concat(funcName).reduce(function(result, otherName) {
  470. return result.replace(RegExp('(?:\\n *//.*\\s*)* *lodash\\.' + otherName + ' *= *.+\\n'), '');
  471. }, snippet);
  472. // replace with the modified snippet
  473. source = source.replace(snippet, modified);
  474. return removeFromCreateIterator(source, funcName);
  475. }
  476. /**
  477. * Removes the `_.isArguments` fallback from `source`.
  478. *
  479. * @private
  480. * @param {String} source The source to process.
  481. * @returns {String} Returns the source with the `isArguments` fallback removed.
  482. */
  483. function removeIsArgumentsFallback(source) {
  484. return source.replace(getIsArgumentsFallback(source), '');
  485. }
  486. /**
  487. * Removes the `_.isFunction` fallback from `source`.
  488. *
  489. * @private
  490. * @param {String} source The source to process.
  491. * @returns {String} Returns the source with the `isFunction` fallback removed.
  492. */
  493. function removeIsFunctionFallback(source) {
  494. return source.replace(/(?:\s*\/\/.*)*\n( +)if *\(isFunction\(\/x\/[\s\S]+?};\n\1}/, '');
  495. }
  496. /**
  497. * Removes the `Object.keys` object iteration optimization from `source`.
  498. *
  499. * @private
  500. * @param {String} source The source to process.
  501. * @returns {String} Returns the modified source.
  502. */
  503. function removeKeysOptimization(source) {
  504. return removeVar(source, 'isKeysFast')
  505. // remove optimized branch in `iteratorTemplate`
  506. .replace(/(?: *\/\/.*\n)* *'( *)<% *if *\(isKeysFast[\s\S]+?'\1<% *} *else *\{ *%>.+\n([\s\S]+?) *'\1<% *} *%>.+/, '$2')
  507. // remove `isKeysFast` from `beforeLoop.object` of `mapIteratorOptions`
  508. .replace(/=\s*'\s*\+\s*\(isKeysFast.+/, "= []'")
  509. // remove `isKeysFast` from `inLoop.object` of `mapIteratorOptions`, `invoke`, `pairs`, `pluck`, and `sortBy`
  510. .replace(/'\s*\+\s*\(isKeysFast[^)]+?\)\s*\+\s*'/g, '.push')
  511. // remove data object property assignment in `createIterator`
  512. .replace(/\s*.+?\.isKeysFast *=.+/, '');
  513. }
  514. /**
  515. * Removes all `noArgsClass` references from `source`.
  516. *
  517. * @private
  518. * @param {String} source The source to process.
  519. * @returns {String} Returns the modified source.
  520. */
  521. function removeNoArgsClass(source) {
  522. return removeVar(source, 'noArgsClass')
  523. // remove `noArgsClass` from `_.clone` and `_.isEqual`
  524. .replace(/ *\|\| *\(noArgsClass *&&[^)]+?\)\)/g, '')
  525. // remove `noArgsClass` from `_.isEqual`
  526. .replace(/if *\(noArgsClass[^}]+?}\n/, '');
  527. }
  528. /**
  529. * Removes all `noNodeClass` references from `source`.
  530. *
  531. * @private
  532. * @param {String} source The source to process.
  533. * @returns {String} Returns the modified source.
  534. */
  535. function removeNoNodeClass(source) {
  536. return source
  537. // remove `noNodeClass` assignment
  538. .replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *try *\{(?:\s*\/\/.*)*\n *var noNodeClass[\s\S]+?catch[^}]+}\n/, '')
  539. // remove `noNodeClass` from `isPlainObject`
  540. .replace(/\(!noNodeClass *\|\|[\s\S]+?\)\) *&&/, '')
  541. // remove `noNodeClass` from `_.isEqual`
  542. .replace(/ *\|\| *\(noNodeClass *&&[\s\S]+?\)\)\)/, '');
  543. }
  544. /**
  545. * Removes a given variable from `source`.
  546. *
  547. * @private
  548. * @param {String} source The source to process.
  549. * @param {String} varName The name of the variable to remove.
  550. * @returns {String} Returns the source with the variable removed.
  551. */
  552. function removeVar(source, varName) {
  553. source = source.replace(RegExp(
  554. // begin non-capturing group
  555. '(?:' +
  556. // match multi-line comment block
  557. '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' +
  558. // match a variable declaration that's not part of a declaration list
  559. '( +)var ' + varName + ' *= *(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\1.+?;)\\n|' +
  560. // match a variable in a declaration list
  561. '\\n +' + varName + ' *=.+?,' +
  562. // end non-capturing group
  563. ')'
  564. ), '');
  565. // remove a varaible at the start of a variable declaration list
  566. source = source.replace(RegExp('(var +)' + varName + ' *=.+?,\\s+'), '$1');
  567. // remove a variable at the end of a variable declaration list
  568. source = source.replace(RegExp(',\\s*' + varName + ' *=.+?;'), ';');
  569. // remove variable reference from `arrayLikeClasses` and `cloneableClasses` assignments
  570. source = source.replace(RegExp('(?:arrayLikeClasses|cloneableClasses)\\[' + varName + '\\] *= *(?:false|true)?', 'g'), '');
  571. return removeFromCreateIterator(source, varName);
  572. }
  573. /**
  574. * Searches `source` for a `varName` variable declaration and replaces its
  575. * assigned value with `varValue`.
  576. *
  577. * @private
  578. * @param {String} source The source to inspect.
  579. * @param {String} varName The name of the variable to replace.
  580. * @returns {String} Returns the source with the variable replaced.
  581. */
  582. function replaceVar(source, varName, varValue) {
  583. // replace a variable that's not part of a declaration list
  584. source = source.replace(RegExp(
  585. '(( +)var ' + varName + ' *= *)' +
  586. '(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\2.+?;)\\n'
  587. ), '$1' + varValue + ';\n');
  588. // replace a varaible at the start or middle of a declaration list
  589. source = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), '$1 ' + varValue + ',');
  590. // replace a variable at the end of a variable declaration list
  591. source = source.replace(RegExp('(,\\s*' + varName + ' *=).+?;'), '$1 ' + varValue + ';');
  592. return source;
  593. }
  594. /**
  595. * Hard-codes the `useStrict` template option value for `iteratorTemplate`.
  596. *
  597. * @private
  598. * @param {String} source The source to process.
  599. * @param {Boolean} value The value to set.
  600. * @returns {String} Returns the modified source.
  601. */
  602. function setUseStrictOption(source, value) {
  603. // remove `isStrictFast` assignment
  604. return removeVar(source, 'isStrictFast')
  605. // replace `useStrict` branch in `value` with hard-coded option
  606. .replace(/(?: *\/\/.*\n)*(\s*)' *<% *if *\(useStrict\).+/, value ? "$1'\\'use strict\\';\\n' +" : '')
  607. // remove `useStrict` from iterator options
  608. .replace(/ *'useStrict': *false,\n/g, '')
  609. // remove `useStrict` variable assignment in `createIterator`
  610. .replace(/,\s*useStrict *=[^;]+/, '')
  611. // remove `useStrict` data object property assignment in `createIterator`
  612. .replace(/\s*.+?\.useStrict *=.+/, '');
  613. }
  614. /*--------------------------------------------------------------------------*/
  615. /**
  616. * Creates a debug and minified build, executing the `callback` for each.
  617. * The `callback` is invoked with 2 arguments; (filepath, source)
  618. *
  619. * @param {Array} options The options array.
  620. * @param {Function} callback The function called per build.
  621. */
  622. function build(options, callback) {
  623. options || (options = []);
  624. // the debug version of `source`
  625. var debugSource;
  626. // used to report invalid command-line arguments
  627. var invalidArgs = _.reject(options.slice(options[0] == 'node' ? 2 : 0), function(value, index, options) {
  628. if (/^(?:-o|--output)$/.test(options[index - 1]) ||
  629. /^(?:category|exclude|exports|iife|include)=.*$/.test(value)) {
  630. return true;
  631. }
  632. return [
  633. 'backbone',
  634. 'csp',
  635. 'legacy',
  636. 'mobile',
  637. 'strict',
  638. 'underscore',
  639. '-c', '--stdout',
  640. '-h', '--help',
  641. '-o', '--output',
  642. '-s', '--silent',
  643. '-V', '--version'
  644. ].indexOf(value) > -1;
  645. });
  646. // report invalid arguments
  647. if (invalidArgs.length) {
  648. console.log(
  649. '\n' +
  650. 'Invalid argument' + (invalidArgs.length > 1 ? 's' : '') +
  651. ' passed: ' + invalidArgs.join(', ')
  652. );
  653. displayHelp();
  654. return;
  655. }
  656. // display help message
  657. if (_.find(options, function(arg) {
  658. return /^(?:-h|--help)$/.test(arg);
  659. })) {
  660. displayHelp();
  661. return;
  662. }
  663. // display `lodash.VERSION`
  664. if (_.find(options, function(arg) {
  665. return /^(?:-V|--version)$/.test(arg);
  666. })) {
  667. console.log(_.VERSION);
  668. return;
  669. }
  670. /*------------------------------------------------------------------------*/
  671. // collections of method names to exclude or include
  672. var excludeMethods = [],
  673. includeMethods = [];
  674. // flag used to specify a Backbone build
  675. var isBackbone = options.indexOf('backbone') > -1;
  676. // flag used to specify a Content Security Policy build
  677. var isCSP = options.indexOf('csp') > -1 || options.indexOf('CSP') > -1;
  678. // flag used to specify a legacy build
  679. var isLegacy = options.indexOf('legacy') > -1;
  680. // flag used to specify an Underscore build
  681. var isUnderscore = options.indexOf('underscore') > -1;
  682. // flag used to specify a mobile build
  683. var isMobile = !isLegacy && (isCSP || isUnderscore || options.indexOf('mobile') > -1);
  684. // flag used to specify writing output to standard output
  685. var isStdOut = options.indexOf('-c') > -1 || options.indexOf('--stdout') > -1;
  686. // flag used to specify skipping status updates normally logged to the console
  687. var isSilent = isStdOut || options.indexOf('-s') > -1 || options.indexOf('--silent') > -1;
  688. // flag used to specify `_.bindAll`, `_.extend`, and `_.defaults` are
  689. // constructed using the "use strict" directive
  690. var isStrict = options.indexOf('strict') > -1;
  691. // flag used to specify if the build should include the "use strict" directive
  692. var useStrict = isStrict || !(isLegacy || isMobile);
  693. // the lodash.js source
  694. var source = fs.readFileSync(path.join(__dirname, 'lodash.js'), 'utf8');
  695. // used to specify the ways to export the `LoDash` function
  696. var exportsOptions = options.reduce(function(result, value) {
  697. var match = value.match(/^exports=(.*)$/);
  698. if (!match) {
  699. return result;
  700. }
  701. return match[1].split(/, */).sort();
  702. }, exportsAll.slice());
  703. // used to specify whether filtering is for exclusion or inclusion
  704. var filterType = options.reduce(function(result, value) {
  705. if (result) {
  706. return result;
  707. }
  708. var pair = value.match(/^(exclude|include)=(.*)$/);
  709. if (!pair) {
  710. return result;
  711. }
  712. // remove nonexistent method names
  713. var methodNames = _.intersection(allMethods, pair[2].split(/, */).map(getRealName));
  714. if (pair[1] == 'exclude') {
  715. excludeMethods = methodNames;
  716. } else {
  717. includeMethods = methodNames;
  718. }
  719. // return `filterType`
  720. return pair[1];
  721. }, '');
  722. // used to specify a custom IIFE to wrap Lo-Dash
  723. var iife = options.reduce(function(result, value) {
  724. return result || (result = value.match(/^iife=(.*)$/)) && result[1];
  725. }, '');
  726. // load customized Lo-Dash module
  727. var lodash = (function() {
  728. var context = vm.createContext({
  729. 'clearTimeout': clearTimeout,
  730. 'setTimeout': setTimeout
  731. });
  732. if (isStrict) {
  733. source = setUseStrictOption(source, true);
  734. } else {
  735. // remove "use strict" directive
  736. source = source.replace(/(["'])use strict\1;( *\n)?/, '');
  737. if (!useStrict) {
  738. source = setUseStrictOption(source, false);
  739. }
  740. }
  741. if (isLegacy) {
  742. ['isBindFast', 'isKeysFast', 'isStrictFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
  743. source = replaceVar(source, varName, 'false');
  744. });
  745. source = replaceVar(source, 'noArgsClass', 'true');
  746. source = removeKeysOptimization(source);
  747. }
  748. else if (isUnderscore) {
  749. // remove `deep` clone functionality
  750. source = source.replace(/( +)function clone[\s\S]+?\n\1}/, [
  751. ' function clone(value) {',
  752. ' if (value == null) {',
  753. ' return value;',
  754. ' }',
  755. ' var isObj = objectTypes[typeof value];',
  756. ' if (isObj && value.clone && isFunction(value.clone)) {',
  757. ' return value.clone(deep);',
  758. ' }',
  759. ' if (isObj) {',
  760. ' var className = toString.call(value);',
  761. ' if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) {',
  762. ' return value;',
  763. ' }',
  764. ' var isArr = className == arrayClass;',
  765. ' }',
  766. ' return isObj',
  767. ' ? (isArr ? slice.call(value) : extend({}, value))',
  768. ' : value;',
  769. ' }'
  770. ].join('\n'));
  771. }
  772. if (isMobile) {
  773. source = replaceVar(source, 'isKeysFast', 'false');
  774. source = removeKeysOptimization(source);
  775. // remove Opera 10.53-10.60 JIT fixes
  776. source = source.replace(/length *> *-1 *&& *length/g, 'length');
  777. // remove `prototype` [[Enumerable]] fix from `_.keys`
  778. source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(.+?propertyIsEnumerable[\s\S]+?\n\1}/, '');
  779. // remove `prototype` [[Enumerable]] fix from `iteratorTemplate`
  780. source = source
  781. .replace(/(?: *\/\/.*\n)*\s*' *(?:<% *)?if *\(!hasDontEnumBug *(?:&&|\))[\s\S]+?<% *} *(?:%>|').+/g, '')
  782. .replace(/!hasDontEnumBug *\|\|/g, '');
  783. }
  784. vm.runInContext(source, context);
  785. return context._;
  786. }());
  787. /*------------------------------------------------------------------------*/
  788. // customize for Backbone and Underscore builds
  789. if (isUnderscore) {
  790. // don't expose `_.forIn` or `_.forOwn` if `isUnderscore` is `true` unless
  791. // requested by `include`
  792. if (includeMethods.indexOf('forIn') == -1) {
  793. source = source.replace(/ *lodash\.forIn *=.+\n/, '');
  794. }
  795. if (includeMethods.indexOf('forOwn') == -1) {
  796. source = source.replace(/ *lodash\.forOwn *=.+\n/, '');
  797. }
  798. }
  799. // include methods required by Backbone and Underscore builds
  800. [
  801. { 'flag': isBackbone, 'methodNames': backboneDependencies },
  802. { 'flag': isUnderscore, 'methodNames': underscoreMethods }
  803. ]
  804. .some(function(data) {
  805. var flag = data.flag,
  806. methodNames = data.methodNames;
  807. if (!flag) {
  808. return false;
  809. }
  810. // add any additional sub-dependencies
  811. methodNames = getDependencies(methodNames);
  812. if (filterType == 'exclude') {
  813. // remove excluded methods from `methodNames`
  814. includeMethods = _.without.apply(_, [methodNames].concat(excludeMethods));
  815. }
  816. else if (filterType) {
  817. // merge `methodNames` into `includeMethods`
  818. includeMethods = _.union(includeMethods, methodNames);
  819. }
  820. else {
  821. // include only the `methodNames`
  822. includeMethods = methodNames;
  823. }
  824. filterType = 'include';
  825. return true;
  826. });
  827. /*------------------------------------------------------------------------*/
  828. // include methods by category
  829. options.some(function(value) {
  830. var categories = value.match(/^category=(.*)$/);
  831. if (!categories) {
  832. return false;
  833. }
  834. // resolve method names belonging to each category
  835. var categoryMethods = categories[1].split(/, */).reduce(function(result, category) {
  836. return result.concat(allMethods.filter(function(methodName) {
  837. return RegExp('@category ' + category + '\\b', 'i').test(matchFunction(source, methodName));
  838. }));
  839. }, []);
  840. if (filterType == 'exclude') {
  841. // remove excluded methods from `categoryMethods`
  842. includeMethods = _.without.apply(_, [categoryMethods].concat(excludeMethods));
  843. }
  844. else if (filterType) {
  845. // merge `categoryMethods` into `includeMethods`
  846. includeMethods = _.union(includeMethods, categoryMethods);
  847. }
  848. else {
  849. // include only the `categoryMethods`
  850. includeMethods = categoryMethods;
  851. }
  852. filterType = 'include';
  853. return true;
  854. });
  855. /*------------------------------------------------------------------------*/
  856. // remove methods from the build
  857. (function() {
  858. // exit early if "exclude" or "include" options aren't specified
  859. if (!filterType) {
  860. return;
  861. }
  862. if (filterType == 'exclude') {
  863. // remove methods that are named in `excludeMethods` and their dependants
  864. excludeMethods.forEach(function(methodName) {
  865. getDependants(methodName).concat(methodName).forEach(function(otherName) {
  866. source = removeFunction(source, otherName);
  867. });
  868. });
  869. }
  870. else {
  871. // add dependencies to `includeMethods`
  872. includeMethods = getDependencies(includeMethods);
  873. // remove methods that aren't named in `includeMethods`
  874. allMethods.forEach(function(otherName) {
  875. if (!_.contains(includeMethods, otherName)) {
  876. source = removeFunction(source, otherName);
  877. }
  878. });
  879. }
  880. }());
  881. /*------------------------------------------------------------------------*/
  882. // simplify template snippets by removing unnecessary brackets
  883. source = source.replace(
  884. RegExp("{(\\\\n' *\\+\\s*.*?\\+\\n\\s*' *)}(?:\\\\n)?' *([,\\n])", 'g'), "$1'$2"
  885. );
  886. source = source.replace(
  887. RegExp("{(\\\\n' *\\+\\s*.*?\\+\\n\\s*' *)}(?:\\\\n)?' *\\+", 'g'), "$1;\\n'+"
  888. );
  889. // remove `isArguments` fallback before `isArguments` is transformed by
  890. // other parts of the build process
  891. if (isRemoved(source, 'isArguments')) {
  892. source = removeIsArgumentsFallback(source);
  893. }
  894. // DRY out isType functions
  895. (function() {
  896. var iteratorName = _.find(['forEach', 'forOwn'], function(methodName) {
  897. return !isRemoved(source, methodName);
  898. });
  899. // skip this optimization if there are no iteration methods to use
  900. if (!iteratorName) {
  901. return;
  902. }
  903. var funcNames = [],
  904. objectSnippets = [];
  905. // build replacement code
  906. _.forOwn({
  907. 'Arguments': 'argsClass',
  908. 'Date': 'dateClass',
  909. 'Number': 'numberClass',
  910. 'RegExp': 'regexpClass',
  911. 'String': 'stringClass'
  912. },
  913. function(value, key) {
  914. if (!isUnderscore && key == 'Arguments') {
  915. return;
  916. }
  917. var funcName = 'is' + key,
  918. funcCode = matchFunction(source, funcName);
  919. if (funcCode) {
  920. funcNames.push(funcName);
  921. objectSnippets.push("'" + key + "': " + value);
  922. }
  923. });
  924. // skip this optimization if there are less than 2 isType functions
  925. if (funcNames.length < 2) {
  926. return;
  927. }
  928. // remove existing isType functions
  929. funcNames.forEach(function(funcName) {
  930. source = removeFunction(source, funcName);
  931. });
  932. // insert new DRY code after the method assignments
  933. var snippet = getMethodAssignments(source);
  934. source = source.replace(snippet, snippet + '\n' +
  935. ' // add `_.' + funcNames.join('`, `_.') + '`\n' +
  936. ' ' + iteratorName + '({\n ' + objectSnippets.join(',\n ') + '\n }, function(className, key) {\n' +
  937. " lodash['is' + key] = function(value) {\n" +
  938. ' return toString.call(value) == className;\n' +
  939. ' };\n' +
  940. ' });\n'
  941. );
  942. }());
  943. /*------------------------------------------------------------------------*/
  944. if (isLegacy) {
  945. ['isBindFast', 'nativeBind', 'nativeIsArray', 'nativeKeys'].forEach(function(varName) {
  946. source = removeVar(source, varName);
  947. });
  948. ['bind', 'isArray'].forEach(function(methodName) {
  949. var snippet = matchFunction(source, methodName),
  950. modified = snippet;
  951. // remove native `Function#bind` branch in `_.bind`
  952. if (methodName == 'bind') {
  953. modified = modified.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, '');
  954. }
  955. // remove native `Array.isArray` branch in `_.isArray`
  956. else {
  957. modified = modified.replace(/nativeIsArray * \|\|/, '');
  958. }
  959. source = source.replace(snippet, modified);
  960. });
  961. // replace `_.keys` with `shimKeys`
  962. if (!isRemoved(source, 'keys')) {
  963. source = source.replace(
  964. matchFunction(source, 'keys').replace(/[\s\S]+?var keys *=/, ''),
  965. matchFunction(source, 'shimKeys').replace(/[\s\S]+?var shimKeys *=/, '')
  966. );
  967. source = removeFunction(source, 'shimKeys');
  968. }
  969. // replace `_.isArguments` with fallback
  970. if (!isRemoved(source, 'isArguments')) {
  971. source = source.replace(
  972. matchFunction(source, 'isArguments').replace(/[\s\S]+?function isArguments/, ''),
  973. getIsArgumentsFallback(source).match(/isArguments *= *function([\s\S]+?) *};/)[1] + ' }\n'
  974. );
  975. source = removeIsArgumentsFallback(source);
  976. }
  977. source = removeVar(source, 'reNative');
  978. source = removeFromCreateIterator(source, 'nativeKeys');
  979. }
  980. if (isMobile) {
  981. // inline all functions defined with `createIterator`
  982. _.functions(lodash).forEach(function(methodName) {
  983. // match `methodName` with pseudo private `_` prefixes removed to allow matching `shimKeys`
  984. var reFunc = RegExp('(\\bvar ' + methodName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n');
  985. // skip if not defined with `createIterator`
  986. if (!reFunc.test(source)) {
  987. return;
  988. }
  989. // extract, format, and inject the compiled function's source code
  990. source = source.replace(reFunc, '$1' + getFunctionSource(lodash[methodName]) + ';\n');
  991. });
  992. // replace `callee` in `_.merge` with `merge`
  993. source = source.replace(matchFunction(source, 'merge'), function(match) {
  994. return match.replace(/\bcallee\b/g, 'merge');
  995. });
  996. if (!isUnderscore) {
  997. source = removeIsArgumentsFallback(source);
  998. source = removeNoArgsClass(source);
  999. }
  1000. // remove `hasDontEnumBug`, `hasObjectSpliceBug`, `iteratesOwnLast`, `noArgsEnum` assignment
  1001. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug\b[\s\S]+?}\(1\)\);\n/, '');
  1002. // remove `iteratesOwnLast` from `isPlainObject`
  1003. source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(iteratesOwnLast[\s\S]+?\n\1}/, '');
  1004. // remove JScript [[DontEnum]] fix from `_.isEqual`
  1005. source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(hasDontEnumBug[\s\S]+?\n\1}/, '');
  1006. // remove `hasObjectSpliceBug` fix from the mutator Array functions mixin
  1007. source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(hasObjectSpliceBug[\s\S]+?\n\1}/, '');
  1008. // remove `noArraySliceOnStrings` from `_.toArray`
  1009. source = source.replace(/noArraySliceOnStrings *\?[^:]+: *([^)]+)/g, '$1');
  1010. // remove `noCharByIndex` from `_.reduceRight`
  1011. source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1');
  1012. source = removeVar(source, 'extendIteratorOptions');
  1013. source = removeVar(source, 'iteratorTemplate');
  1014. source = removeVar(source, 'noArraySliceOnStrings');
  1015. source = removeVar(source, 'noCharByIndex');
  1016. source = removeNoNodeClass(source);
  1017. }
  1018. else {
  1019. // inline `iteratorTemplate` template
  1020. source = source.replace(/(( +)var iteratorTemplate *= *)[\s\S]+?\n\2.+?;\n/, (function() {
  1021. var snippet = getFunctionSource(lodash._iteratorTemplate);
  1022. // prepend data object references to property names to avoid having to
  1023. // use a with-statement
  1024. iteratorOptions.forEach(function(property) {
  1025. snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property);
  1026. });
  1027. // remove unnecessary code
  1028. snippet = snippet
  1029. .replace(/var __t.+/, "var __p = '';")
  1030. .replace(/function print[^}]+}/, '')
  1031. .replace(/'(?:\\n|\s)+'/g, "''")
  1032. .replace(/__p *\+= *' *';/g, '')
  1033. .replace(/(__p *\+= *)' *' *\+/g, '$1')
  1034. .replace(/(\{) *;|; *(\})/g, '$1$2')
  1035. .replace(/\(\(__t *= *\( *([^)]+) *\)\) *== *null *\? *'' *: *__t\)/g, '$1');
  1036. // remove the with-statement
  1037. snippet = snippet.replace(/ *with *\(.+?\) *{/, '\n').replace(/}([^}]*}[^}]*$)/, '$1');
  1038. // minor cleanup
  1039. snippet = snippet
  1040. .replace(/obj *\|\| *\(obj *= *\{}\);/, '')
  1041. .replace(/var __p = '';\s*__p \+=/, 'var __p =');
  1042. // remove comments, including sourceURLs
  1043. snippet = snippet.replace(/\s*\/\/.*(?:\n|$)/g, '');
  1044. return '$1' + snippet + ';\n';
  1045. }()));
  1046. }
  1047. /*------------------------------------------------------------------------*/
  1048. // customize Lo-Dash's export bootstrap
  1049. if (exportsOptions.indexOf('amd') == -1) {
  1050. source = source.replace(/(?: *\/\/.*\n)*( +)if *\(typeof +define[\s\S]+?else /, '$1');
  1051. }
  1052. if (exportsOptions.indexOf('node') == -1) {
  1053. source = source.replace(/(?: *\/\/.*\n)* *if *\(typeof +module[\s\S]+?else *{\n([\s\S]+?) *}\n/, '$1');
  1054. }
  1055. if (exportsOptions.indexOf('commonjs') == -1) {
  1056. source = source.replace(/(?: *\/\/.*\n)*(?:( +)else *{)?\s*freeExports\._ *=.+(\n\1})?\n/, '');
  1057. }
  1058. if (exportsOptions.indexOf('global') == -1) {
  1059. source = source.replace(/(?:( +)else *{)?(?:\s*\/\/.*)*\s*window\._ *= *lodash.+(\n\1})?\n/g, '');
  1060. }
  1061. // remove `if (freeExports) {...}` if it's empty
  1062. source = source.replace(/(?: *\/\/.*\n)* *(?:else )?if *\(freeExports\) *{\s*}(?:\s*else *{\n([\s\S]+?) *})?/, '$1');
  1063. /*------------------------------------------------------------------------*/
  1064. // customize Lo-Dash's IIFE
  1065. (function() {
  1066. if (iife) {
  1067. var token = '%output%',
  1068. index = iife.indexOf(token);
  1069. source = source.match(/\/\*![\s\S]+?\*\/\n/) +
  1070. iife.slice(0, index) +
  1071. source.replace(/^[^(]+?\(function[^{]+?{|}\(this\)\)[;\s]*$/g, '') +
  1072. iife.slice(index + token.length);
  1073. }
  1074. }());
  1075. /*------------------------------------------------------------------------*/
  1076. // modify/remove references to removed methods/variables
  1077. if (isRemoved(source, 'isArguments')) {
  1078. source = replaceVar(source, 'noArgsClass', 'false');
  1079. }
  1080. if (isRemoved(source, 'isFunction')) {
  1081. source = removeIsFunctionFallback(source);
  1082. }
  1083. if (isRemoved(source, 'mixin')) {
  1084. // remove `LoDash` constructor
  1085. source = removeFunction(source, 'LoDash');
  1086. // remove `LoDash` calls
  1087. source = source.replace(/(?:new +LoDash(?!\()|(?:new +)?LoDash\([^)]*\));?/g, '');
  1088. // remove `LoDash.prototype` additions
  1089. source = source.replace(/(?:\s*\/\/.*)*\s*LoDash.prototype *=[\s\S]+?\/\*-+\*\//, '');
  1090. // remove `hasObjectSpliceBug` assignment
  1091. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasObjectSpliceBug;|.+?hasObjectSpliceBug *=.+/g, '');
  1092. }
  1093. // remove pseudo private properties
  1094. source = source.replace(/(?:(?:\s*\/\/.*)*\s*lodash\._[^=]+=.+\n)+/g, '\n');
  1095. // assign debug source before further modifications that rely on the minifier
  1096. // to remove unused variables and other dead code
  1097. debugSource = source;
  1098. // remove associated functions, variables, and code snippets that the minifier may miss
  1099. if (isRemoved(source, 'clone')) {
  1100. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var cloneableClasses *=[\s\S]+?true;\n/g, '');
  1101. }
  1102. if (isRemoved(source, 'isArray')) {
  1103. source = removeVar(source, 'nativeIsArray');
  1104. }
  1105. if (isRemoved(source, 'keys')) {
  1106. source = removeFunction(source, 'shimKeys');
  1107. }
  1108. if (isRemoved(source, 'template')) {
  1109. // remove `templateSettings` assignment
  1110. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *lodash\.templateSettings[\s\S]+?};\n/, '');
  1111. }
  1112. if (isRemoved(source, 'toArray')) {
  1113. source = removeVar(source, 'noArraySliceOnStrings');
  1114. }
  1115. if (isUnderscore
  1116. ? isRemoved(source, 'merge')
  1117. : isRemoved(source, 'clone', 'merge')
  1118. ) {
  1119. source = removeFunction(source, 'isPlainObject');
  1120. }
  1121. if (isRemoved(source, 'clone', 'isArguments', 'isEmpty', 'isEqual')) {
  1122. source = removeNoArgsClass(source);
  1123. }
  1124. if (isRemoved(source, 'isEqual', 'isPlainObject')) {
  1125. source = removeNoNodeClass(source);
  1126. }
  1127. if ((source.match(/\bcreateIterator\b/g) || []).length < 2) {
  1128. source = removeFunction(source, 'createIterator');
  1129. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var noArgsEnum;|.+?noArgsEnum *=.+/g, '');
  1130. }
  1131. if (isRemoved(source, 'createIterator', 'bind')) {
  1132. source = removeVar(source, 'isBindFast');
  1133. source = removeVar(source, 'isStrictFast');
  1134. source = removeVar(source, 'nativeBind');
  1135. }
  1136. if (isRemoved(source, 'createIterator', 'bind', 'isArray', 'keys')) {
  1137. source = removeVar(source, 'reNative');
  1138. }
  1139. if (isRemoved(source, 'createIterator', 'isEmpty', 'isEqual')) {
  1140. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var arrayLikeClasses *=[\s\S]+?true;\n/g, '');
  1141. }
  1142. if (isRemoved(source, 'createIterator', 'isEqual')) {
  1143. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug;|.+?hasDontEnumBug *=.+/g, '');
  1144. }
  1145. if (isRemoved(source, 'createIterator', 'isPlainObject')) {
  1146. source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast;|.+?iteratesOwnLast *=.+/g, '');
  1147. }
  1148. if (isRemoved(source, 'createIterator', 'keys')) {
  1149. source = removeVar(source, 'nativeKeys');
  1150. }
  1151. if (!source.match(/var (?:hasDontEnumBug|hasObjectSpliceBug|iteratesOwnLast|noArgsEnum)\b/g)) {
  1152. // remove `hasDontEnumBug`, `hasObjectSpliceBug`, `iteratesOwnLast`, and `noArgsEnum` assignment
  1153. source = source.replace(/ *\(function\(\) *{[\s\S]+?}\(1\)\);/, '');
  1154. }
  1155. debugSource = cleanupSource(debugSource);
  1156. source = cleanupSource(source);
  1157. /*------------------------------------------------------------------------*/
  1158. // used to specify creating a custom build
  1159. var isCustom = !_.isEqual(exportsOptions, exportsAll) || filterType || iife || isBackbone || isLegacy || isMobile || isStrict || isUnderscore;
  1160. // used to specify the output path for builds
  1161. var outputPath = options.reduce(function(result, value, index) {
  1162. return result || (/^(?:-o|--output)$/.test(value) ? options[index + 1] : result);
  1163. }, '');
  1164. // used to name temporary files created in `dist/`
  1165. var workingName = 'lodash' + (isCustom ? '.custom' : '') + '.min';
  1166. // output debug build
  1167. if (isCustom && !outputPath && !isStdOut) {
  1168. callback(debugSource, path.join(cwd, 'lodash.custom.js'));
  1169. }
  1170. // begin the minification process
  1171. minify(source, {
  1172. 'silent': isSilent,
  1173. 'workingName': workingName,
  1174. 'onComplete': function(source) {
  1175. // correct overly aggressive Closure Compiler minification
  1176. source = source.replace(/prototype\s*=\s*{\s*valueOf\s*:\s*1\s*}/, 'prototype={valueOf:1,y:1}');
  1177. // inject "use strict" directive
  1178. if (isStrict) {
  1179. source = source.replace(/^(\/\*![\s\S]+?\*\/\n;\(function[^)]+\){)([^'"])/, '$1"use strict";$2');
  1180. }
  1181. if (isStdOut) {
  1182. stdout.write(source);
  1183. callback(source);
  1184. } else {
  1185. callback(source, outputPath || path.join(cwd, workingName + '.js'));
  1186. }
  1187. }
  1188. });
  1189. }
  1190. /*--------------------------------------------------------------------------*/
  1191. // expose `build`
  1192. if (module != require.main) {
  1193. module.exports = build;
  1194. }
  1195. else {
  1196. // or invoked directly
  1197. build(process.argv, function(source, filepath) {
  1198. filepath && fs.writeFileSync(filepath, source, 'utf8');
  1199. });
  1200. }
  1201. }());