#!/usr/bin/env node 'use strict'; var _ = require('lodash'), glob = require('glob'), semver = require('semver'), vm = require('vm'), consts = require('../lib/const'), listing = require('../lib/listing'), mapping = require('../lib/mapping'), minify = require('../lib/minify'), util = require('../lib/util'); var fs = util.fs, Hash = util.Hash, path = util.path; var buildExports = listing.buildExports, cwd = process.cwd(), floor = Math.floor, push = Array.prototype.push, stdout = process.stdout, uninlinables = listing.uninlinables; /** Used to indicate whether this file is executed directly from Node.js. */ var isBin = module == require.main; /*----------------------------------------------------------------------------*/ /** * Adds build `commands` to the copyright header of `source`. * * @private * @param {string} source The source to process. * @param {Array} [commands=[]] An array of commands. * @returns {string} Returns the modified source. */ function addCommandsToHeader(source, commands) { return replace(source, getHeader(source), function(header) { // Add quotes to commands with spaces or equals signs. commands = _.map(commands, function(command) { var separator = /[= ]/.exec(command); if (separator) { separator = separator[0]; var parts = command.split(separator); command = parts[0] + separator + '"' + parts.slice(1).join(separator) + '"'; } // Escape newlines, carriage returns, multi-line comment end tokens. return command .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\*\//g, '*\\/'); }); // Remove any existing custom build information. header = header .replace(' (Custom Build)', '') .replace(/^ *\* *Build:.+\n/m, ''); // Add build commands to copyright header. return header.replace(/(\/\**\n)( \*)( *@license[\s*]+)?( *Lodash)(.*)/, function(match, prelude, indent, tag, title, postlude) { return ( prelude + indent + (tag || '') + title + ' (Custom Build)' + postlude + '\n' + indent + ' Build: `lodash ' + commands.join(' ') + '`' ); }); }); } /** * Creates modules based on the provided build state. * * @private * @param {Object} state The build state object. * @param {Function} [onComplete] The function called when all module builds * are completed. */ function buildModule(state, onComplete) { var buildFuncs = state.buildFuncs, funcDepMap = state.funcDepMap, includeFuncs = state.includeFuncs, includeVars = state.includeVars, isAMD = state.isAMD, isES = state.isES, isNode = state.isNode, isNpm = state.isNpm, isSilent = state.isSilent, minusFuncs = state.minusFuncs, outputPath = state.outputPath, plusFuncs = state.plusFuncs, stamp = state.stamp, varDepMap = state.varDepMap; var depMap = new Hash, identifiers = _.without(_.union(buildFuncs, includeVars), 'main'), moduleCount = 0, removedDeps = new Hash; var buildCallback = function(data) { moduleCount++; data.source = cleanupSource(addCommandsToHeader(data.source, state.options)); defaultBuildCallback(data); }; var getUnusedDeps = function(source, depNames) { source = cleanupSource(removeStrings(removeComments(source))); return _.reject(depNames, function(depName) { return RegExp('[^.$"\'\\w]' + depName + '\\b').test(source); }); }; var toDepName = function(value) { var prefix = (isPrivate(value) && !_.startsWith(value, '_')) ? '_' : '', result = prefix + value; return isNpm ? ('lodash.' + result.toLowerCase()) : result; }; var toDepPath = function(value) { return './' + toDepName(value) + (isES ? '.js' : ''); }; if (isNpm) { // Exclude "Seq" methods and most internal functions when exporting for npm. identifiers = _.reject(_.difference(identifiers, mapping.category.Seq), function(identifier) { return isPrivate(identifier) && !_.includes(uninlinables, identifier) && !_.includes(includeFuncs, identifier); }); // Remove unused packages. var newPackages = _.map(identifiers, toDepName), oldPackages = _.map(glob.sync(path.join(outputPath, 'lodash.*')), _.ary(path.basename, 1)), unusedPackages = _.difference(oldPackages, newPackages); _.each(unusedPackages, function(packageName) { var pathname = path.join(outputPath, packageName); _.each(fs.readdirSync(pathname), function(identifier) { fs.unlinkSync(path.join(pathname, identifier)); }); fs.rmdirSync(pathname); }); } // List of identifiers that keep their copyright headers. var includeHeaders = isNpm ? identifiers : _.difference(_.union(includeFuncs, plusFuncs), minusFuncs); if (_.isEmpty(includeHeaders)) { includeHeaders = ['main']; } // Sort identifiers so private packages with the least number of interdependent // dependencies are first. identifiers = _.orderBy(identifiers, [ isPrivate, function(identifier) { var depNames = _.union( getDependencies(identifier, funcDepMap), getDependencies(identifier, varDepMap) ); var inlinees = [identifier]; if (isNpm) { var moreInlinees = _.transform(_.difference(depNames, uninlinables), function(result, identifier) { push.apply(result, getAllDependencies([identifier], funcDepMap, varDepMap, _.clone(uninlinables))); return result; }); depNames = _.union(_.intersection(uninlinables, depNames), _.intersection(uninlinables, moreInlinees)); inlinees = inlinees.concat(_.difference(moreInlinees, depNames)); } // Populate `depMap`. depMap[identifier] = new Hash({ 'depNames': depNames, 'inlinees': inlinees, 'isPrivate': isPrivate(identifier) }); return depNames.length; }, function(identifier) { if (!depMap[identifier].isPrivate) { return true; } return _.isString(_.findKey(depMap, function(value, key) { var data = depMap[key]; return data.isPrivate && _.includes(data.depNames, identifier); })); }], ['desc'] ); // Create modules for each identifier. _.each(identifiers, function(identifier) { var basename = _.get(mapping.forceAlias, identifier, identifier), category = getCategory(identifier), depData = depMap[identifier], depName = toDepName(basename), depNames = depData.depNames, inlinees = depData.inlinees; if (isNpm) { var inlineFuncs = _.intersection(listing.funcs, inlinees), inlineVars = _.intersection(listing.varDeps, inlinees); } else { inlineFuncs = _.includes(listing.funcs, identifier) ? inlinees : []; inlineVars = _.includes(listing.varDeps, identifier) ? inlinees : []; } state.outputPath = path.join(outputPath, isNpm ? (depName + '/index.js') : (depName + '.js')); state.buildFuncs = state.includeFuncs = inlineFuncs; state.includeVars = inlineVars; build(state, function(data) { var iife = [], source = data.source, unusedDeps = getUnusedDeps(source, depNames); // Track and remove unused dependencies. removedDeps[identifier] = unusedDeps; depNames = _.sortBy(_.difference(depNames, unusedDeps), toDepName); depNames = _.map(depNames, function(depName) { return _.get(mapping.forceAlias, depName, depName); }).sort(); var depPaths = isNpm ? _.map(depNames, toDepName) : _.map(depNames, toDepPath); if (isAMD) { var depArgs = _.map(depNames, function(depName) { return _.get(mapping.wrapperToReal, depName, depName); }); iife.push( 'define([' + (_.isEmpty(depPaths) ? '' : "'" + depPaths.join("', '") + "'") + '], function(' + depArgs.join(', ') + ') {', '%output%', ' return ' + identifier + ';', '});' ); } else if (isES) { iife.push( _.map(depPaths, function(depPath, index) { var depName = depNames[index], varName = _.get(mapping.wrapperToReal, depName, depName); return 'import ' + varName + " from '" + depPath + "';"; }) .join('\n'), '%output%', 'export default ' + identifier + ';' ); } else { iife.push( _.map(depPaths, function(depPath, index) { var depName = depNames[index], varName = _.get(mapping.wrapperToReal, depName, depName), lastIndex = depPaths.length - 1; return (index ? '' : 'var ') + varName + " = require('" + depPath + "')" + (index == lastIndex ? ';' : ''); }) .join(',\n '), '%output%', 'module.exports = ' + identifier + ';' ); } if (!isAMD) { source = replaceIndent(source, 0, 1); } source = _.includes(includeHeaders, identifier) ? removeLicenseTag(source) : removeHeader(source); source = replaceIIFE(source, iife.join('\n')); if (isNpm) { var templatePath = fs.realpathSync(path.join(__dirname, '..', 'template')), licenseTemplate = fs.readFileSync(path.join(templatePath, 'license.jst'), 'utf8'), packageTemplate = fs.readFileSync(path.join(templatePath, 'package.jst'), 'utf8'), readmeTemplate = fs.readFileSync(path.join(templatePath, 'readme.jst'), 'utf8'); var type = 'function', pkgVer = semver.parse(state.lodash.VERSION), pkgDepNames = _.sortBy(depNames, toDepName), pkgDepPaths = _.sortBy(depPaths); if (_.includes(listing.varDeps, identifier)) { type = 'variable'; } var templateData = { 'identifier': identifier, 'name': depName, 'type': type, 'version': pkgVer.raw }; templateData.dependencies = _.transform(pkgDepNames, function(result, depName, index) { var minor = '0', depPath = pkgDepPaths[index], depRange = '^' + pkgVer.major + '.0.0', oldDepPkgPath = path.join(path.resolve(outputPath), depPath, 'package.json'); if (fs.existsSync(oldDepPkgPath)) { var oldDepPkg = JSON.parse(fs.readFileSync(oldDepPkgPath, 'utf8')), oldDepVer = semver.parse(oldDepPkg.version); depRange = '^' + oldDepVer.major + '.' + minor + '.0'; if (oldDepVer.major > pkgVer.major) { pkgVer = oldDepVer; templateData.version = pkgVer.major + '.0.0'; } } result[depPath] = depRange; }, {}); // Independently update the package version. if (fs.existsSync(data.outputPath)) { var laxDeps = _.map(listing.laxSemVerDeps, toDepName), oldPkgPath = path.join(path.resolve(outputPath), depName, 'package.json'), oldPkg = JSON.parse(fs.readFileSync(oldPkgPath, 'utf8')), oldVer = oldPkg.version, oldDeps = _.omit(oldPkg.dependencies, laxDeps), pkgDeps = _.omit(templateData.dependencies, laxDeps); if (_.isEqual(pkgDeps, oldDeps)) { var oldSource = fs.readFileSync(data.outputPath, 'utf8'); // Exit early if sources are identical. if (removeHeader(cleanupSource(oldSource)) === removeHeader(cleanupSource(source))) { return; } // Bump the `patch` version if the source has changed. templateData.version = cleanupSource(removeComments(oldSource)) === cleanupSource(removeComments(source)) ? oldVer : semver.inc(oldVer, 'patch', true); } else { // Bump the `minor` version if the dependencies have changed. templateData.version = semver.inc(oldVer, 'minor', true); } var isSameVersion = templateData.version == oldVer; source = source.replace(getHeader(source), function(header) { // Use the old header if the package version is unchanged, // otherwise remove the version from the header. return isSameVersion ? getHeader(oldSource) : header; }); } if (!isSameVersion) { var oldPath = path.join(outputPath, depName, 'LICENSE.txt'); if (fs.existsSync(oldPath)) { fs.unlinkSync(oldPath); } fs.writeFileSync(path.join(outputPath, depName, 'package.json'), _.template(packageTemplate)(templateData)); fs.writeFileSync(path.join(outputPath, depName, 'LICENSE'), _.template(licenseTemplate)(templateData)); fs.writeFileSync(path.join(outputPath, depName, 'README.md'), _.template(readmeTemplate)(templateData)); } } data.source = source; buildCallback(data); }); }); // Add alias modules. _.each(!isNpm && identifiers, function(identifier) { var basename = _.get(mapping.forceAlias, identifier, identifier), aliases = _.without(getAliases(identifier), basename), category = getCategory(identifier), depName = toDepName(basename), depPath = toDepPath(basename); _.each(aliases, function(alias) { var iife = []; if (isAMD) { iife.push( 'define(["' + depPath + '"], function(' + depName + ') {', ' return ' + depName + ';', '});' ); } else if (isES) { iife.push( "export { default } from '" + depPath + "'" ); } else { iife.push( 'module.exports' + " = require('" + depPath + "');" ); } buildCallback({ 'outputPath': path.join(outputPath, alias + '.js'), 'source': iife.join('\n') }); }); }); // Create main module. _.times(isES ? 2 : 1, function(index) { var identifier = 'main'; if (isNpm || !_.includes(buildFuncs, identifier)) { return; } var categories = _.uniq(_.compact(_.map(identifiers, function(identifier) { return getCategory(identifier); }))).sort(); var categoryDeps = _.map(categories, function(category) { return mapping.categoryToDepName[category] || category.toLowerCase(); }); var categoryDepPaths = _.map(categories, function(category) { return toDepPath(category).toLowerCase(); }); var deps = _.union( getDependencies(identifier, funcDepMap), getDependencies(identifier, varDepMap) ); var basename = 'lodash'; if (isAMD) { basename = 'main'; } else if (isES) { basename += (index ? '.default' : ''); } state.buildFuncs = state.includeFuncs = [identifier]; state.outputPath = path.join(outputPath, basename + '.js'); build(state, function(data) { var source = data.source; // Remove unused method and alias assignments. _.each(_.difference(listing.funcs, buildFuncs), function(funcName) { source = removeMethodAssignment(source, funcName); }); // Wrap `_.mixin`. source = source.replace(/^(?: *\/\/.*\n)* *lodash\.[$\w]+\s*=[^;]+;\n/m, function(match) { return [ ' // wrap `_.mixin` so it works when provided only one argument', ' ' + (isES ? 'var ' : '') + 'mixin = (function(func) {', ' return function(object, source, options) {', ' if (options == null) {', ' var isObj = isObject(source),', ' props = isObj && keys(source),', ' methodNames = props && props.length && baseFunctions(source, props);', '', ' if (!(methodNames ? methodNames.length : isObj)) {', ' options = source;', ' source = object;', ' object = this;', ' }', ' }', ' return func(object, source, options);', ' };', ' }(' + (isES ? '_' : '') + 'mixin));', '', match ].join('\n'); }); // Add `lodash.templateSettings` and placeholder assignments. source = source.replace(/^ *lodash\.VERSION\b.+\n/m, function(match) { var code = []; if (_.includes(identifiers, 'templateSettings')) { code.push(' (lodash.templateSettings = ' + mapping.categoryToDepName.String + '.templateSettings).imports._ = lodash;'); } var funcNames = _.intersection(buildFuncs, listing.placeholderFuncs); if (!_.isEmpty(funcNames)) { code.push( '', ' // Assign default placeholders.' ); if (_.size(funcNames) > 1) { code.push( " arrayEach(['" + funcNames.join("', '") + "'], function(methodName) {", ' lodash[methodName].placeholder = lodash;', ' });' ); } else { code.push(' lodash.' + funcNames[0] + '.placeholder = lodash;'); } } code.push(''); return match + code.join('\n'); }); // Add category namespaces to each lodash function assignment. source = source.replace(/(lodash(?:\.prototype)?(?:\[[$\w]+\]|\.[$\w]+)\s*=\s*)(?!lodash\b)([$\w]+)/g, function(match, left, identifier) { if (_.includes(deps, identifier)) { return match; } var category = mapping.categoryToDepName[getCategory(identifier)]; identifier = _.get(mapping.wrapperToReal, identifier, identifier); return left + (category ? category + '.' : '') + identifier; }); // Track and remove unused dependencies. var unusedDeps = getUnusedDeps(source, deps); removedDeps[identifier] = unusedDeps; deps = _.difference(deps, unusedDeps); deps = _.map(deps, function(depName) { return _.get(mapping.forceAlias, depName, depName); }).sort(); var depNames = categoryDeps.concat(deps); if (isES) { // Avoid a syntax error caused by reassigning `mixin` by naming the // dependency `_mixin` instead. depNames[_.indexOf(depNames, 'mixin')] = '_mixin'; } var iife = []; var depArgs = _.map(depNames, function(depName) { return _.get(mapping.wrapperToReal, depName, depName); }); var depPaths = categoryDepPaths.concat(_.map(deps, toDepPath)); if (isAMD) { iife.push( 'define([' + (_.isEmpty(depPaths) ? '' : "'" + depPaths.join("', '") + "'") + '], function(' + depArgs.join(', ') + ') {', '%output%', ' return lodash;', '});' ); } else if (isES) { if (index) { iife.push( _.map(depPaths, function(depPath, index) { var depName = depNames[index], varName = _.get(mapping.wrapperToReal, depName, depName); return 'import ' + varName + " from '" + depPath + "';"; }).join('\n'), '%output%', 'export default lodash;' ); } else { deps = _.reject(identifiers, isPrivate); var depData = _.sortBy(_.transform(deps, function(result, depName) { var aliases = getAliases(depName), forceAlias = mapping.forceAlias[depName], dataName = forceAlias === undefined ? depName : forceAlias, dataPath = toDepPath(_.includes(aliases, forceAlias) ? forceAlias : dataName); result.push({ 'name': depName, 'path': dataPath }); push.apply(result, _.map(aliases, function(alias) { return { 'name': alias, 'path': toDepPath(alias) }; })); }), 'name'); depNames = _.map(depData, 'name'); depPaths = _.map(depData, 'path'); iife.push( _.map(depPaths, function(depPath, index) { var depName = depNames[index]; return 'export { default as ' + depName + " } from '" + depPath + "';"; }).join('\n'), "export { default } from './lodash.default.js';" ); } } else { iife.push( _.map(depPaths, function(depPath, index) { var depName = depNames[index], varName = _.get(mapping.wrapperToReal, depName, depName), lastIndex = depPaths.length - 1; return (index ? '' : 'var ') + varName + " = require('" + depPath + "')" + (index == lastIndex ? ';' : ''); }).join(',\n '), '%output%', 'module.exports = lodash;' ); } if (!isAMD) { source = replaceIndent(source, 0, 1); } if (!_.includes(includeHeaders, identifier)) { source = removeHeader(source); } source = replaceIIFE(source, iife.join('\n')); if (isNode) { fs.writeFileSync(path.join(outputPath, 'index.js'), "module.exports = require('./lodash');"); } data.source = source; buildCallback(data); }); }); // Create category modules. _.each(!isNpm && _.uniq(_.compact(_.flatten(_.map(identifiers, getCategory)))), function(category) { _.times(isES ? 2 : 1, function(index) { var basename = category.toLowerCase(), deps = _.intersection(getNamesByCategory(category), identifiers); var depData = _.sortBy(_.transform(deps, function(result, depName) { var forceAlias = mapping.forceAlias[depName], aliases = _.without(getAliases(depName), forceAlias, mapping.wrapperToReal[depName]), dataName = forceAlias === undefined ? depName : forceAlias, dataPath = toDepPath(_.includes(aliases, forceAlias) ? forceAlias : dataName); dataName = _.get(mapping.wrapperToReal, dataName, dataName); result.push({ 'name': dataName, 'path': dataPath }); push.apply(result, _.map(aliases, function(alias) { return { 'name': alias, 'path': toDepPath(alias) }; })); }), 'name'); var depNames = _.map(depData, 'name'), depPaths = _.map(depData, 'path'), iife = []; if (isAMD) { iife.push( "define(['" + depPaths.join("', '") + "'], function(" + depNames.join(', ') + ') {', ' return {', _.map(depNames, function(depName) { var key = _.get(mapping.wrapperToReal, depName, depName); return " '" + key + "': " + depName; }) .join(',\n'), ' };', '});' ); } else { if (isES) { if (index) { iife.push( _.map(depNames, function(depName, index) { return 'import ' + depName + " from '" + depPaths[index] + "';"; }) .join('\n'), '', 'export default {', _(depNames) .chunk(5) .map(function(chunk) { return " " + chunk.join(', '); }) .join(',\n'), '};' ); } else { iife.push( '', _.map(depNames, function(depName, index) { var key = _.get(mapping.wrapperToReal, depName, depName); return 'export { default as ' + key + " } from '" + depPaths[index] + "';"; }) .join('\n'), "export { default } from './" + basename + ".default.js';" ); } } else { iife.push( 'module.exports = {', _.map(depNames, function(depName) { var depPath = depPaths[_.indexOf(depNames, depName)], key = _.get(mapping.wrapperToReal, depName, depName); return " '" + key + "': require('" + depPath + "')"; }) .join(',\n'), '};' ); } } buildCallback({ 'outputPath': path.join(outputPath, basename + (index ? '.default' : '') + '.js'), 'source': iife.join('\n') }); }); }); if (!isSilent) { // Warn of removed dependencies. _.forOwn(removedDeps, function(depNames, identifier) { if (!_.isEmpty(depNames)) { var plural = _.size(depNames) > 1; console.warn('Warning: Removed ' + (plural ? '' : 'an ') + 'unused dependenc' + (plural ? 'ies' : 'y') + ' from `' + identifier + '`: ' + depNames.sort().join(', ')); } }); console.log('Created %d modules in %d seconds.', moduleCount, (_.now() - stamp) / 1000); } if (onComplete) { onComplete({ 'outputPath': fs.realpathSync(outputPath) }); } } /** * Compiles template files based on the provided build state extending * `_.templates` with precompiled templates named after each file's basename. * * @private * @param {Object} state The build state object. * @returns {string} Returns the compiled source. */ function buildTemplate(state) { var moduleId = state.moduleId || 'lodash', isStandalone = moduleId == 'none', pattern = state.templatePattern, settings = state.templateSettings; pattern = path.normalize(pattern || path.join(cwd, '*.jst')); var source = [ ';(function() {', ' var undefined;', '', " var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;", '', " var freeGlobalThis = typeof globalThis == 'object' && globalThis !== null && globalThis.Object == Object && globalThis;", '', " var freeSelf = typeof self == 'object' && self && self.Object === Object && self;", '', " var root = freeGlobalThis || freeGlobal || freeSelf || Function('return this')();", '', " var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;", '', " var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;", '' ]; if (isStandalone) { build(['exports=none', 'include=escape', 'iife=%output%', '-d', '-s'], function(data) { var escapeSource = data.source; escapeSource = removeHeader(escapeSource); escapeSource = removeFunction(escapeSource, 'lodash'); escapeSource = removeAssignments(escapeSource); escapeSource = cleanupSource(escapeSource); source.push( '', escapeSource, '', " var _ = { 'escape': escape };", '' ); }); } else { source.push( ' var _ = root._ || {};', '' ); } source.push( consts.hr, '' ); var dirname = path.dirname(pattern), filePaths = glob.sync(pattern); if (dirname == '.') { dirname = ''; } var basePath = (dirname + path.sep).replace(RegExp('(^|' + path.sepEscaped + ')\\*\\*.*$'), '$1'), insertAt = source.length, templates = new Hash; _.each(filePaths, function(filePath) { var string = fs.readFileSync(filePath, 'utf8'), precompiled = cleanupCompiled(getFunctionSource(_.template(string, settings), 2)); // Glob uses *nix path separators even on Windows. // See https://github.com/isaacs/node-glob#windows. var clipped = filePath.slice(dirname ? basePath.length : 0).replace(/\..*$/, ''), props = clipped.split('/'); // Create namespace objects. _.reduce(props, function(object, key) { return object[key] || (object[key] = new Hash); }, templates); // Escape namespace property names. props = _.map(props, function(key) { return "['" + key.replace(/['\n\r\t]/g, '\\$&') + "']"; }); // Add template assignment to `source`. source.push(' templates' + props.join('') + ' = ' + precompiled + ';', ''); }); // Add the initial `_.templates` object to `source`. source.splice(insertAt, 0, ' var templates = ' + JSON.stringify(templates, null, 4) .replace(/^ *\}$/m, ' $&') .replace(/'/g, "\\'") .replace(/([^\\])"/g, "$1'") + ';', '' ); source.push( consts.hr, '', " if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {" ); if (isStandalone) { source.push( ' define(function() {', ' return templates;' ); } else { source.push( " define(['" + moduleId + "'], function(lodash) {", ' _ = lodash;', ' lodash.templates = lodash.extend(lodash.templates || {}, templates);' ); } source.push( ' });', ' }', ' else if (freeModule) {' ); if (!isStandalone) { source.push(" _ = require('" + moduleId + "');"); } source.push( ' (freeModule.exports = templates).templates = templates;', ' freeExports.templates = templates;' ); if (isStandalone) { source.push( ' }', ' else {', ' root.templates = templates;', ' }' ); } else { source.push( ' }', ' else if (_) {', ' _.templates = _.extend(_.templates || {}, templates);', ' }' ); } source.push('}.call(this));'); return source.join('\n'); } /** * Removes unnecessary semicolons and whitespace from compiled code. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function cleanupCompiled(source) { return stringFree(source, function(source) { return source .replace(/\b(function)\s*(\()/g, '$1$2') .replace(/([{}])\s*;/g, '$1'); }); } /** * Removes unnecessary comments, and whitespace. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function cleanupSource(source) { return stringFree(source, function(source) { return source // Consolidate consecutive horizontal rule comment separators. .replace(/(?:\s*\/\*-+\*\/\s*){2,}/g, function(separators) { var indent = /^\s*/.exec(separators)[0]; return indent + separators.slice(separators.lastIndexOf('/*')); }) // Remove unused single line comments. .replace(/(\{\s*)?(\n *\/\/.*)(\s*\})/g, function(match, prelude, comment, postlude) { return (!prelude && postlude) ? postlude : match; }) // Remove unattached multi-line and single line comments. .replace(/^ *(\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/|\/\/.*)\n\n/gm, function(match, comment, index) { var isCopyright = !index && /\bcopyright\b/i.test(comment), isHR = /^\/\*-+\*\/$/.test(comment); return (isCopyright || isHR) ? match : '\n'; }) // Remove unused horizontal rule comment separators. .replace(/(\{\s*\n) *\/\*-+\*\/\n|^ *\/\*-+\*\/\n(\s*\})/gm, '$1$2') // Remove trailing horizontal rule comment separators. .replace(/\s*\/\*-+\*\/\s*$/, '') // Remove incomplete variable declarations. .replace(/^ *var\s*;\n/gm, '') // Remove lines with just spaces and semicolons. .replace(/^ *;\n/gm, '') // Remove trailing spaces from lines. .replace(/ *$/gm, '') // Consolidate multiple newlines. .replace(/\n{3,}/g, '\n\n') // Remove leading empty lines. .replace(/^ *\n+/, '') // Add trailing newline. .trimRight() + '\n'; }); } /** * Invokes `callback` providing `source` with comments removed and returns the * modified source with comments restored. * * @private * @param {string} source The source to modify. * @param {Function} [callback] The function to modify the comment free source. * @returns {string} Returns the modified source. */ function commentFree(source, callback) { var comments = []; source = callback(replace(source, consts.reComment, function(match) { var index = comments.length; comments.push(match); return '<#com_token' + index + '#>\n'; })) || ''; return replace(source, consts.reCommentToken, function(match) { return comments[match.slice(11, -3)]; }); } /** * The default callback used for `build` invocations. * * @private * @param {Object} data The data for the given build. * gzip - The gzipped output of the built source * outputPath - The path where the built source is to be written * source - The built source output * sourceMap - The source map output */ function defaultBuildCallback(data) { var outputPath = data.outputPath, sourceMap = data.sourceMap; if (outputPath) { fs.writeFileSync(outputPath, data.source); if (sourceMap) { fs.writeFileSync(path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.map'), sourceMap); } } } /** * Gets the aliases associated with a given identifier. * * @private * @param {string} identifier The identifier to get aliases for. * @returns {Array} Returns an array of aliases. */ function getAliases(identifier) { return _.get(mapping.realToAlias, identifier, []); } /** * Creates an array of all function, object, and variable dependencies for the * given identifier(s). * * @private * @param {string|string[]} identifier The identifier or array of identifiers to query. * @param {Object} funcDepMap The dependency map to look up function dependencies. * @param {Object} varDepMap The dependency map to look up variable dependencies. * @param- {Array} [stackA=[]] Internally used track queried identifiers. * @returns {Array} Returns an array of identifier dependencies. */ function getAllDependencies(identifier, funcDepMap, varDepMap, stack) { var isInit = !stack, result = _.isArray(identifier) ? (isInit ? _.clone(identifier) : identifier) : [identifier]; stack || (stack = []); _.each(result, function(identifier) { if (!_.includes(stack, identifier)) { push.apply(result, getDependencies(identifier, funcDepMap)); push.apply(result, getDependencies(identifier, varDepMap)); stack.push(identifier); getAllDependencies(result, funcDepMap, varDepMap, stack); } }); return isInit ? _.uniq(result) : result; } /** * Gets the category of the given identifier. * * @private * @param {string} identifier The identifier to query. * @returns {string|undefined} Returns the category. */ function getCategory(identifier) { identifier = getRealName(identifier); return _.find(listing.categories, function(category) { return _.includes(mapping.category[category], identifier); }); } /** * Creates an array of depenants for the given identifier(s). * * @private * @param {string} identifier The identifier or array of identifiers to query. * @param {Object} depMap The dependency map to look up dependants. * @param {boolean} [isDeep=false] A flag to specify retrieving nested dependants. * @param- {Array} [stackA=[]] Internally used track queried identifiers. * @returns {Array} Returns an array of identifier dependants. */ function getDependants(identifier, depMap, isDeep, stack) { var isInit = !stack, identifiers = _.isArray(identifier) ? identifier : [identifier]; stack || (stack = []); // Iterate over the dependency map, adding names of functions that have `identifier` as a dependency. var result = _.transform(depMap, function(result, depNames, otherName) { if (!_.includes(stack, otherName) && _.some(identifiers, _.partial(_.includes, depNames, _, 0))) { result.push(otherName); if (isDeep) { stack.push(otherName); push.apply(result, getDependants(otherName, depMap, isDeep, stack)); } } }, []); return isInit ? _.uniq(result) : result; } /** * Creates an array of dependencies for the given identifier(s). * * @private * @param {string|string[]} identifier The identifier or array of identifiers to query. * @param {Object} depMap The dependency map to look up dependencies. * @param {boolean} [isDeep=false] A flag to specify retrieving nested dependencies. * @param- {Array} [stackA=[]] Internally used track queried identifiers. * @returns {Array} Returns an array of identifier dependencies. */ function getDependencies(identifier, depMap, isDeep, stack) { var isInit = !stack, depNames = _.isArray(identifier) ? identifier : depMap[identifier]; stack || (stack = []); if (!isDeep) { return depNames ? _.difference(depNames, stack) : []; } // Recursively accumulate the dependencies of the `identifier` function, // the dependencies of its dependencies, and so on. var result = _.transform(depNames, function(result, otherName) { if (!_.includes(stack, otherName)) { stack.push(otherName); result.push(otherName); push.apply(result, getDependencies(otherName, depMap, isDeep, stack)); } }, []); return isInit ? _.uniq(result) : result; } /** * Gets the formatted source of the given function. * * @private * @param {Function} func The function to process. * @param {number|string} [indent=0] The level to indent. * @returns {string} Returns the formatted source. */ function getFunctionSource(func, indent) { var source = toString(func.source || func), srcIndent = getIndent(source), forceIndent = _.size(source.match(RegExp('^' + srcIndent + '}', 'gm'))) > 1; indent || (indent = ''); if (typeof indent == 'number') { indent = _.repeat(' ', indent); } // Remove any existing indent. if (srcIndent) { source = source.replace(RegExp('^' + srcIndent, 'gm'), ''); } // Set indent of source. return indent + source.replace(/\n(?:.*)/g, function(match, index) { var prelude = '\n' + indent; match = match.slice(1); if (forceIndent) { prelude += (match == '}' && !_.includes(source, '}', index + 2) ? '' : ' '); } return prelude + match; }); } /** * Gets the `getTag` fork from `source`. * * @private * @param {string} source The source to inspect. * @returns {string} Returns the fork. */ function getGetTagFork(source) { return _.get(/^(?: *\/\/.*\n)*( *)if\s*\(\s*\(DataView\s*&&[\s\S]+?\n\1 getTag\s*=[\s\S]+?\n\1\}\n/m.exec(source), 0, ''); } /** * Gets the copyright header of `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the copyright header. */ function getHeader(source) { source = toString(source); return _.get(/^\s*\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/\n|^(?:\s*\/\/.*\n)+/.exec(source), 0, ''); } /** * Gets the indent string of the given function. * * @private * @param {Function|string} func The function or function source to process. * @returns {string} Returns the indent string. */ function getIndent(func) { return _.get(/^ *(?=\S)/m.exec(func.source || func), 0, ''); } /** * Gets the lodash method assignments snippet from `source`. * * @private * @param {string} source The source to inspect. * @returns {string} Returns the method assignments snippet. */ function getMethodAssignments(source) { source = toString(source); return _.get(/\n\n(?: *\/\/.*\n)* *lodash(?:\.(?!prototype\b)[$\w]+)+\s*=[\s\S]+\n *lodash(?:\.[$\w]+)+\s=[^;]+;(?=\n)/.exec(source), 0, ''); } /** * Gets the names of identifiers in `source` that belong to the given category. * * @private * @param {string} category The category to filter by. * @returns {Array} Returns a new array of names. */ function getNamesByCategory(category) { return _.get(mapping.category, category, []); } /** * Gets the value of a given name from the `options` array. If no value is * available the `defaultValue` is returned. * * @private * @param {Array} options The options array to inspect. * @param {string|string[]} names The name(s) of the option. * @param {*} defaultValue The default option value. * @returns {*} Returns the option value. */ function getOption(options, names, defaultValue) { var isArr = _.isArray(defaultValue); names = _.isArray(names) ? names : [names]; return _.reduce(options, function(result, option) { _.each(names, function(name) { if (isArr) { var array = optionToArray(name, option); result = _.isEmpty(array) ? result : array; } else { var value = optionToValue(name, option); result = value == null ? result : value; } }); return result; }, defaultValue); } /** * Gets the real name of `alias`. * * @private * @param {string} alias The alias to resolve. * @returns {string} Returns the real name. */ function getRealName(alias) { return _.get(mapping.aliasToReal, alias, alias); } /** * Gets the real category of `alias`. * * @private * @param {string} alias The alias to resolve. * @returns {string} Returns the real category. */ function getRealCategory(alias) { return _.get(mapping.oldCategory, alias, alias); } /** * Creates an array variables names from all variables defined outside of * lodash functions. * * @private * @param {string} source The source to process. * @returns {Array} Returns a new array of variable names. */ function getVars(source) { var isDeep = consts.reHasDeepVars.test(source), indentA = isDeep ? ' {2,4}' : ' {2}', indentB = isDeep ? ' {6,8}' : ' {6}', result = []; var patterns = [ // Match varaibles at the start of a declaration list. ['^(' + indentA + 'var\\s+)([$\\w]+)\\s*=.+?,\\n *', 2, 1], // Match variable declarations in a declaration list. [',\\n' + consts.rsComment + indentB + '([$\\w]+)\\s*=[\\s\\S]+?(?=[,;]\\n)', 1, -1], // Match variables that aren't part of a declaration list. ['^(' + indentA + ')var\\s+([$\\w]+)\\s*(?:|=[^;]+);\\n', 2, -1] ]; source = removeStrings(removeComments(source)); _.each(listing.complexVars, function(varName) { source = modifyVar(source, varName, function() { result.push(varName); return ''; }); }); return _.uniq(_.transform(patterns, function(result, pattern) { source = source.replace(RegExp(pattern[0], 'gm'), function() { result.push(arguments[pattern[1]]); return pattern[2] > -1 ? arguments[pattern[2]] : ''; }); }, result)); } /** * Checks if `source` is a function snippet. * * @private * @param {string} source The source to inspect. * @returns {boolean} Returns `true` for a function snippet, else `false`. */ function isFunctionSnippet(source) { var header = getHeader(source); return consts.reHasFuncTags.test(header) || consts.reIsFuncSnippet.test(replace(source, header, '')); } /** * Checks if `identifier` is private. * * @private * @param {string} identifier The identifier to query. * @returns {boolean} Returns `true` if the identifier is private, else `false`. */ function isPrivate(identifier) { identifier = getRealName(identifier); if (_.includes(listing.categories, identifier)) { return false; } return _.isUndefined(_.findKey(mapping.category, function(identifiers) { return _.includes(identifiers, identifier); })); } /** * Checks if the variable `varName` is used in `source`. * * @private * @param {string} source The source to process. * @param {string} varName The name of the variable. * @returns {boolean} Returns `true` if the variable is used, else `false`. */ function isVarUsed(source, varName) { var escapedName = _.escapeRegExp(varName); source = removeVar(source, varName); return RegExp('[^.$"\'\\w]' + escapedName + '\\b(?!\\s*=)').test(source); } /** * Searches `source` for a `funcName` function declaration, expression, or * assignment and returns the matched snippet. * * @private * @param {string} source The source to inspect. * @param {string} funcName The name of the function to match. * @param {boolean} [leadingComments] A flag to specify including leading comments. * @returns {string} Returns the matched function snippet. */ var matchFunction = _.memoize(function(source, funcName, leadingComments) { var escapedName = _.escapeRegExp(funcName), otherKey = funcName + ':' + !leadingComments; var patterns = [ // Match function declarations. ['^(' + consts.rsComment + ')(( *)function\\s+' + escapedName + '\\((?:\\)\\s*\\{\\s*|[\\s\\S]+?\\n\\3)\\}\\n)', 2, 0], // Match single line function expressions at the start of a declaration list. ['^( *var\\s+)(' + escapedName + '\\s*=\\s*\\(?function\\b.+?\\}\\)?,\\n *)', 2, 2], // Match single line function expressions in a declaration list. ['(,\\n' + consts.rsComment + ')( *' + escapedName + '\\s*=\\s*\\(?function\\b.+?\\}\\)?(?=[,;]\\n))', 2, 0], // Match single line function expressions that aren't in a declaration list. ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*=\\s*\\(?function\\b.+?\\}\\)?;\\n)', 2, 0], // Match variable declarations containing built-in constructors. ['^( *var\\s+)(' + escapedName + '\\s*=\\s*root\\.(?:[A-Z][a-z0-9]+)+,\\n *)', 2, 2], ['(,\\n' + consts.rsComment + ')( *' + escapedName + '\\s*=\\s*root\\.(?:[A-Z][a-z0-9]+)+(?=[,;]\\n))', 2, 0], ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*=\\s*root\\.(?:[A-Z][a-z0-9]+)+;\\n)', 2, 0], // Match variable declarations using `baseProperty` or `basePropertyOf`. ['(,\\n' + consts.rsComment + ')( *' + escapedName + '\\s*=\\s*baseProperty(?:Of)?\\([\\s\\S]+?\\)(?=[,;]\\n))', 2, 0], ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*=\\s*baseProperty(?:Of)?\\([\\s\\S]+?\\);\\n)', 2, 0], // Match variable declarations using creator functions. ['(,\\n' + consts.rsComment + ')(( *)' + escapedName + '\\s*=\\s*create(?:[A-Z][a-z]+)+\\((?:.*|[\\s\\S]+?\\n\\3(?:\\S.*?)?)\\)(?=[,;]\\n))', 2, 0], ['^(' + consts.rsComment + ')(( *)var\\s+' + escapedName + '\\s*=\\s*create(?:[A-Z][a-z]+)+\\((?:.*|[\\s\\S]+?\\n\\3(?:\\S.*?)?)\\);\\n)', 2, 0], // Match variable declarations using `getNative`. ['^( *var\\s+)(' + escapedName + '\\s*=\\s*getNative\\(.+?\\),\\n *)', 2, 2], ['(,\\n' + consts.rsComment + ')( *' + escapedName + '\\s*=\\s*getNative\\(.+?\\)(?=[,;]\\n))', 2, 0], ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*=\\s*getNative\\(.+?\\);\\n)', 2, 0], // Match variable declarations using `overArg`. ['(,\\n' + consts.rsComment + ')( *' + escapedName + '\\s*=\\s*overArg\\([\\s\\S]+?\\)(?=[,;]\\n))', 2, 0], ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*=\\s*overArg\\([\\s\\S]+?\\);\\n)', 2, 0], // Match variable declarations using `shortOut`. ['(,\\n' + consts.rsComment + ')( *' + escapedName + '\\s*=\\s*shortOut\\([\\s\\S]+?\\)(?=[,;]\\n))', 2, 0], ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*=\\s*shortOut\\([\\s\\S]+?\\);\\n)', 2, 0], // Match variable declarations using `_.memoize` or `_.rest`. ['(,\\n' + consts.rsComment + ')(( *)' + escapedName + '\\s*=\\s*(?:[a-z]+Rest|memoize(?:Capped)?)\\((?:.+|[\\s\\S]+?\\n\\3\\})\\)(?=[,;]\\n))', 2, 0], ['^(' + consts.rsComment + ')(( *)var\\s+' + escapedName + '\\s*=\\s*(?:[a-z]+Rest|memoize(?:Capped)?)\\((?:.+|[\\s\\S]+?\\n\\3\\})\\);\\n)', 2, 0], // Match simple variable declaration. ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*=.+?;\\n)', 2, 0], // Match multiple line function expressions. ['^(' + consts.rsComment + ')(( *)var\\s+' + escapedName + '\\s*=.*?function\\b[\\s\\S]+?\\{\\n[\\s\\S]+?\\n\\3\\}(?:(?:\\(\\))?\\))?;\\n)', 2, 0] ]; var result = _.reduce(patterns, function(result, pattern) { if (!result) { result = RegExp(pattern[0], 'm').exec(source) || ''; if (isFunctionSnippet(_.get(result, 0))) { matchFunction.cache.set(otherKey, result[pattern[leadingComments ? 1 : 2]]); result = result[pattern[leadingComments ? 2 : 1]]; } } return result; }, ''); if (!result) { matchFunction.cache.set(otherKey, result); } return result; }, function(source, funcName, leadingComments) { return funcName + ':' + !!leadingComments; }); /** * Searches `source` for a lodash property, of the given property name, and * returns the matched snippet. * * @private * @param {string} source The source to inspect. * @param {string} propName The name of the property to match. * @param {boolean} [leadingComments] A flag to specify including leading comments. * @returns {string} Returns the matched property snippet. */ function matchProp(source, propName, leadingComments) { var escapedName = _.escapeRegExp(propName); return _.get(RegExp( '^' + (leadingComments ? consts.rsComment : '') + '(?: {2,4}var\\s+' + escapedName + '\\b.+|(?:\\s*|.*?=\\s*)lodash\\._?' + escapedName + '\\s*)=[\\s\\S]+?' + '(?:\\(function\\([\\s\\S]+?\\}\\([^)]*\\)\\);\\n(?=\\n)|' + '[;}]\\n(?=\\n(?!\\s*\\(function\\b)))' , 'm').exec(source), 0, ''); } /** * Searches `source` for a `varName` variable assignment and returns * the matched snippet. * * @private * @param {string} source The source to inspect. * @param {string} varName The name of the variable to match. * @param {boolean} [leadingComments] A flag to specify including leading comments. * @returns {string} Returns the matched variable snippet. */ function matchVar(source, varName, leadingComments) { var escapedName = _.escapeRegExp(varName); var patterns = [ // Match varaibles at the start of a declaration list. ['^( *var\\s+)(' + escapedName + '\\s*=.+?,\\n *)', 2, 2], // Match variables in a declaration list. ['(,\\n' + consts.rsComment + ')( *' + escapedName + '\\s*=[\\s\\S]+?(?=[,;]\\n))', 2, 0], // Match variable declarations that aren't in a declaration list. ['^(' + consts.rsComment + ')( *var\\s+' + escapedName + '\\s*(?:|=[^;]+);\\n)', 2, 0] ]; // Match complex variable assignments. if (_.includes(listing.complexVars, varName)) { patterns.splice(2, 0, ['^(' + consts.rsComment + ')(( *)var\\s+' + escapedName + '\\s*=[\\s\\S]+?[};]\\n(?=\\s*\\n(?:\\S|\\3(?:function\\b|if\\b|lodash\\b|var\\s|/[/*]))))', 2, 0]); } return _.reduce(patterns, function(result, pattern) { return result || _.get(RegExp(pattern[0], 'm').exec(source), pattern[leadingComments ? 2 : 1], ''); }, ''); } /** * Modifies the `funcName` function of `source`. * * @private * @param {string} source The source to modify. * @param {string} funcName The name of the function to match. * @param {Function} replacer The function to modify the matched source. * @returns {string} Returns the modified source. */ function modifyFunction(source, funcName, replacer) { return replace(source, matchFunction(source, funcName), function(match) { var result = replacer(match); matchFunction.cache.set(funcName + ':false', result); matchFunction.cache.delete(funcName + ':true'); return result; }); } /** * Modifies the `propName` lodash property of `source`. * * @private * @param {string} source The source to modify. * @param {string} propName The name of the property to match. * @param {Function} replacer The function to modify the matched source. * @returns {string} Returns the modified source. */ function modifyProp(source, propName, replacer) { return replace(source, matchProp(source, propName), _.unary(replacer)); } /** * Modifies the `varName` variable of `source`. * * @private * @param {string} source The source to modify. * @param {string} varName The name of the variable to match. * @param {Function} replacer The function to modify the matched source. * @returns {string} Returns the modified source. */ function modifyVar(source, varName, replacer) { return replace(source, matchVar(source, varName), _.unary(replacer)); } /** * Converts a comma separated option value into an array. * * @private * @param {string} name The name of the option to inspect. * @param {string} string The options string. * @returns {Array} Returns the new converted array. */ function optionToArray(name, string) { return _.compact(_.invokeMap((optionToValue(name, string) || '').split(/, */), 'trim')); } /** * Extracts the option value from an option string. * * @private * @param {string} name The name of the option to inspect. * @param {string} string The options string. * @returns {string|undefined} Returns the option value, else `undefined`. */ function optionToValue(name, string) { var result = RegExp('^' + _.escapeRegExp(name) + '(?:=([\\s\\S]+))?$').exec(string); if (result) { result = _.get(result, 1); result = result ? _.trim(result) : true; } return (result !== 'false') && (result || undefined); } /** * Removes all lodash method and property assignments from `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function removeAssignments(source) { // Remove method and intermediate assignments. source = removeMethodAssignments(source); return source.replace(/(=\s*)lodash\.[$\w]+\s*=\s*/g, '$1'); } /** * Removes support for lodash wrapper chaining in `source`. * * @private * @param {string} source The source to process. * @param {Object} [seqDepMap] The chain dependency map to modify. * @param {Object} [funcDepMap] The function dependency map to modify. * @param {Object} [varDepMap] The variable dependency map to modify. * @returns {string} Returns the modified source. */ function removeChaining(source, seqDepMap, funcDepMap, varDepMap) { source = removeLazyChaining(source, seqDepMap, funcDepMap, varDepMap); source = removeMixinCalls(source); // Remove all `lodash.prototype` additions. return source .replace(/^(?: *\/\/.*\n)*( *)[$\w]+\(\['pop'[\s\S]+?\n\1\}\);\n/m, '') .replace(/^(?: *\/\/.*\n)*( *)if\s*\(symIterator\)[\s\S]+?\n\1\}\n/gm, '') .replace(/^(?: *\/\/.*\n)*( *)lodash\.prototype\.(?!constructor\b)[$\w]+\s*=[\s\S]+?;\n/gm, ''); } /** * Removes all comments from `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function removeComments(source) { return replace(source, consts.reComment, ''); } /** * Removes the `getTag` fork from `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function removeGetTagFork(source) { return replace(source, getGetTagFork(source), ''); } /** * Removes support for lazy chaining in `source`. * * @private * @param {string} source The source to process. * @param {Object} [seqDepMap] The chain dependency map to modify. * @param {Object} [funcDepMap] The function dependency map to modify. * @param {Object} [varDepMap] The variable dependency map to modify. * @returns {string} Returns the modified source. */ function removeLazyChaining(source, seqDepMap, funcDepMap, varDepMap) { if (seqDepMap) { _.pull(seqDepMap.varDep, 'realNames'); _.pull(seqDepMap.funcDep, 'baseInvoke', 'baseRest', 'createHybrid', 'getIteratee', 'identity', 'isArray', 'last', 'lazyClone', 'lazyReverse', 'lazyValue', 'LazyWrapper', 'toInteger', 'wrapperAt', 'wrapperReverse' ); } if (funcDepMap) { funcDepMap.createFlow = ['baseFlatten', 'flatRest']; } if (varDepMap) { source = removeVar(source, 'realNames'); _.forOwn(varDepMap, function(depNames, identifier) { _.pull(depNames, 'realNames'); }); } source = replaceFunction(source, 'createFlow', [ 'function createFlow(fromRight) {', ' return flatRest(function(funcs) {', ' funcs = baseFlatten(funcs, 1);', '', ' var length = funcs.length,', ' index = length;', '', ' if (fromRight) {', ' funcs.reverse();', ' }', ' while (index--) {', " if (typeof funcs[index] != 'function') {", ' throw new TypeError(FUNC_ERROR_TEXT);', ' }', ' }', ' return function() {', ' var index = 0,', ' result = length ? funcs[index].apply(this, arguments) : arguments[0];', '', ' while (++index < length) {', ' result = funcs[index].call(this, result);', ' }', ' return result;', ' };', ' });', '}' ].join('\n')); return source // Remove bulk `LazyWrapper.prototype` assignments. .replace(/^(?: *\/\/.*\n)*( *)arrayEach\(\['(?:drop|filter|head|initial)\b[\s\S]+?\n\1\}\);\n/gm, '') // Remove individual `LazyWrapper` method assignments. .replace(/^(?: *\/\/.*\n)*( *)LazyWrapper\.prototype\.(?:compact|find|findLast|invokeMap|reject|slice|takeRightWhile|toArray)\s*=[\s\S]+?\n\1\}.*?;\n/gm, '') // Remove other `LazyWrapper.prototype` assignments. .replace(/^(?: *\/\/.*\n)* *LazyWrapper\.prototype\.(?!constructor\b)[$\w]+\s*=[^;]+;\n/gm, '') // Remove `LazyWrapper` additions to `LodashWrapper` and `realNames` assignments. .replace(/^(?: *\/\/.*\n)*( *)baseForOwn\(LazyWrapper\.prototype\b[\s\S]+?\n\1\}\);\n/gm, '') // Remove `realNames` assignment for `wrapper`. .replace(/^(?: *\/\/.*\n)* *realNames\[createHybrid\b[\s\S]+?;\n/m, '') // Remove lazy alias `lodash.prototype` assignments. .replace(/^(?: *\/\/.*\n)* *lodash\.prototype\.first\s*=[^;]+;\n/gm, ''); } /** * Removes metadata optimizations from `source`. * * @private * @param {string} source The source to process. * @param {Object} [funcDepMap] The function dependency map to modify. * @returns {string} Returns the modified source. */ function removeMetadata(source, funcDepMap) { var deps = _.get(funcDepMap, 'createWrap', []); _.pull(deps, 'baseSetData', 'getData', 'mergeData', 'setData'); deps = _.get(funcDepMap, 'createRecurry', []); _.pull(deps, 'isLaziable', 'setData'); // Remove metadata related code. source = modifyFunction(source, 'createWrap', function(match) { match = _.reduce(['data', 'funcBitmask', 'funcIsPartialed', 'setter'], removeVar, match); return match .replace(/^(?: *\/\/.*\n)*( *)if\s*\((?:typeof\s+)?data\b[\s\S]+?\n\1\}\n/gm, '') .replace(/\bsetter\(([^,]+),[^)]+\)/, '$1'); }); source = modifyFunction(source, 'createRecurry', function(match) { match = removeVar(match, 'newData'); match = replaceVar(match, 'result', 'wrapFunc(func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, newHoldersRight, argPos, ary, arity)'); return match.replace(/^(?: *\/\/.*\n)*( *)if\s*\(isLaziable\b[\s\S]+?\n\1\}\n/m, ''); }); return source; } /** * Removes the `funcName` function declaration, expression, or assignment and * associated code from `source`. * * @private * @param {string} source The source to process. * @param {string} funcName The name of the function to remove. * @returns {string} Returns the modified source. */ function removeFunction(source, funcName) { source = toString(source); source = funcName == 'runInContext' ? removeRunInContext(source, funcName) : replace(source, matchFunction(source, funcName, true), ''); matchFunction.cache.set(funcName + ':true', ''); matchFunction.cache.set(funcName + ':false', ''); return source; } /** * Removes all references to `getIteratee` from `source` and replaces calls * with `baseIteratee`. * * @private * @param {string} source The source to process. * @param {Object} [funcDepMap] The function dependency map to modify. * @returns {string} Returns the modified source. */ function removeGetIteratee(source, funcDepMap) { source = removeFunction(source, 'getIteratee'); _.forOwn(funcDepMap, function(deps, identifier) { if (_.includes(deps, 'getIteratee')) { _.pull(deps, 'getIteratee'); if (!_.includes(deps, 'baseIteratee')) { deps.push('baseIteratee'); } source = modifyFunction(source, identifier, function(match) { return match.replace(consts.reGetIteratee, 'baseIteratee'); }); } }); return source.replace(getMethodAssignments(source), function(match) { var deps = _.get(funcDepMap, 'main', []); if (_.includes(deps, 'baseIteratee')) { match = match.replace(consts.reGetIteratee, 'baseIteratee'); } return match; }); } /** * Removes the copyright header from `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function removeHeader(source) { return replace(source, getHeader(source), ''); } /** * Removes the `@license` tag from the copyright header so minifiers * and build optimizers may strip them. * * @private * @param {string} source The source to inspect. * @returns {string} Returns the modified source. */ function removeLicenseTag(source) { return replace(source, /^ \* *@license\n/m, ''); } /** * Removes a method assignment by name from `source`. * * @private * @param {string} source The source to process. * @param {string} [methodName] The name of the method assignment to remove. * @returns {string} Returns the modified source. */ function removeMethodAssignment(source, methodName) { return replace(source, getMethodAssignments(source), function(match) { var pattern = RegExp( '^( *//.*\\n)*( *)' + '(?:lodash(?:\\.prototype)?\\.[$\\w]+\\s*=\\s*)*' + 'lodash(?:\\.prototype)?\\.' + '(?:[$\\w]+\\s*=\\s*' + methodName + '|' + methodName + '\\s*=\\s*(?:[$\\w]+|function\\b[\\s\\S]+?\\n\\2\\}));' + '\\n(\\n)?' , 'gm'); return match.replace(pattern, function(match, comment, indent, newline) { return newline || comment || ''; }); }); } /** * Removes all lodash method assignments from `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function removeMethodAssignments(source) { return replace(source, getMethodAssignments(source), ''); } /** * Removes all `_.mixin` calls from `source`. * * @private * @param {string} source The source to inspect. * @returns {string} Returns the modified source. */ function removeMixinCalls(source) { return replace(source, /^(?: *\/\/.*\n)*( *)mixin\(.+?(?:\{[\s\S]+?\n\1\}.+?)?\);\n/gm, ''); } /** * Removes a lodash property, of the given property name, from `source`. * * @private * @param {string} source The source to process. * @param {string} propName The name of the property to remove. * @returns {string} Returns the modified source. */ function removeProp(source, propName) { return replace(source, matchProp(source, propName, true), ''); } /** * Removes all `runInContext` references from `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function removeRunInContext(source) { source = removeFunction(source, 'clearTimeout'); source = removeFunction(source, 'setTimeout'); source = removeMethodAssignment(source, 'runInContext'); source = removeVar(source, 'contextProps'); // Remove function scaffolding, leaving most of its content. source = replace(source, matchFunction(source, 'runInContext', true), function(match) { // Remove function frame. match = match.replace(/^[\s\S]+?function\s+runInContext\b[\s\S]+?context\s*=\s*context.+?\n+| *return\s+lodash;[\s\S]+$/g, ''); match = replaceIndent(match, 1, 2); // Remove built-in vars and related `context` references. _.each(listing.builtins, function(builtin) { match = removeVar(match, builtin); match = match.replace(RegExp('\\bcontext\\.(?=' + builtin + '\\b)', 'g'), ''); }); // Remove `ctx` vars. _.each(['ctxClearTimeout', 'ctxNow', 'ctxSetTimeout'], function(varName) { match = removeVar(match, varName); match = match.replace(RegExp('\\b' + varName + '\\s*\\|\\|\\s*'), ''); }); // Replace remaining `context` references with `root`. return match.replace(/\bcontext\b/g, 'root'); }); return source // Remove `_` assignment. .replace(/^(?: *\/\/.*\n)* *var\s+_\s*=\s*runInContext\b.+\n+/m, '') // Replace `_` references with `lodash`. .replace(/(\breturn\s+|=\s*)_([;)])/g, '$1lodash$2'); } /** * Removes all strings from `source`. * * @private * @param {string} source The source to process. * @returns {string} Returns the modified source. */ function removeStrings(source) { return replace(source, consts.reString, ''); } /** * Removes a variable of the given variable name from `source`. * * @private * @param {string} source The source to process. * @param {string} varName The name of the variable to remove. * @returns {string} Returns the modified source. */ function removeVar(source, varName) { return replace(source, matchVar(source, varName, true), ''); } /** * Replaces `pattern` matches in `string` with the resolved `replacement` value. * * @private * @param {string} string The string to modify. * @param {RegExp|string} pattern The pattern to match. * @param {Function|string} replacement The replacement value. * @returns {string} Returns the modified string. */ function replace(string, pattern, replacement) { string = toString(string); return pattern ? string.replace(pattern, replacement) : string; } /** * Replaces the `funcName` function body in `source` with `funcValue`. * * @private * @param {string} source The source to process. * @param {string} funcName The name of the function to replace. * @param {string} funcValue The replacement value. * @returns {string} Returns the modified source. */ function replaceFunction(source, funcName, funcValue) { var leadingComments = consts.reIsVarSnippet.test(funcValue); return replace(source, matchFunction(source, funcName, leadingComments), function(match) { var header = leadingComments ? getHeader(match) : ''; if (!isFunctionSnippet(match)) { header = header.replace(/^( *)\* *(?:@(?:category|param|returns)\b|\/)/m, function(match, indent) { return indent + '* @type +\{Function\}\n' + match; }); } funcValue = funcValue .replace(RegExp('^' + getIndent(funcValue), 'gm'), getIndent(match)) .trimRight() + '\n'; if (leadingComments) { matchFunction.cache.set(funcName + ':false', funcValue); } else { matchFunction.cache.delete(funcName + ':true'); } var result = header + funcValue; matchFunction.cache.set(funcName + ':' + leadingComments, result); return result; }); } /** * Replaces the IIFE that wraps `source` with `iife`. If the `%output%` token * is present in `iife` it will be replaced with the unwrapped `source`. * * @private * @param {string} source The source to process. * @param {string} iife The replacement IIFE. * @returns {string} Returns the modified source. */ function replaceIIFE(source, iife) { source = toString(source); iife = toString(iife); var token = '%output%', header = getHeader(source), index = iife.indexOf(token); if (index < 0) { return header + iife; } return header + iife.slice(0, index) + source.replace(/^[\s\S]+?\(function[^{]+\{\n+|\s*\}\.call\(this\)\)[;\s]*$/g, '\n') + iife.slice(index + token.length); } /** * Replaces the indent at level `from` of the given source with the level `to`. * * @private * @param {string} source The source to process. * @param {number} [to=0] The indent level to replace with. * @param {number} [from=1] The indent level to be replaced. * @returns {string} Returns the modified source. */ function replaceIndent(source, to, from) { source = toString(source); if (from === undefined) { from = floor(getIndent(source).length / 2); } return source.replace(RegExp('^(?: ){' + (from || 1) + '}', 'gm'), _.repeat(' ', to || 0)); } /** * Replaces the `varName` variable declaration value in `source` with `varValue`. * * @private * @param {string} source The source to inspect. * @param {string} varName The name of the variable to replace. * @param {string} varValue The replacement value. * @returns {string} Returns the modified source. */ function replaceVar(source, varName, varValue) { source = toString(source); var escapedName = _.escapeRegExp(varName), modified = false; // Replace a variable that's not part of a declaration list. source = source.replace(RegExp( '(( *)var\\s+' + escapedName + '\\s*=)' + '(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\2.+?;)\\n' ), function(match, left) { modified = true; return left + ' ' + varValue + ';\n'; }); if (!modified) { // Replace a varaible at the start or middle of a declaration list. source = source.replace(RegExp('((?:var|\\n)\\s+' + escapedName + '\\s*=).+?(?=,\\n)'), function(match, left) { modified = true; return left + ' ' + varValue; }); } if (!modified) { // Replace a variable at the end of a variable declaration list. source = source.replace(RegExp('(,\\s*' + escapedName + '\\s*=).+?(?=;\\n)'), function(match, left) { return left + ' ' + varValue; }); } return source; } /** * Add or remove the "use strict" directive from `source`. * * @private * @param {string} source The source to process. * @param {boolean} value The value to set. * @returns {string} Returns the modified source. */ function setUseStrictOption(source, value) { return replace(source, /^([\s\S]*?function[^{]+\{)(?:\s*'use strict';)?/, '$1' + (value ? "\n 'use strict';" : '')); } /** * Invokes `callback` providing `source` with strings removed and returns the * modified source with strings restored. * * @private * @param {string} source The source to modify. * @param {Function} [callback] The function to modify the string free source. * @returns {string} Returns the modified source. */ function stringFree(source, callback) { var strings = []; source = callback(replace(source, consts.reString, function(match) { var index = strings.length; strings.push(match); return '<#str_token' + index + '#>'; })) || ''; return replace(source, consts.reStringToken, function(match) { return strings[match.slice(11, -2)]; }); } /** * Converts `value` to a string if it's not one. * An empty string is returned for `null` and `undefined` values. * * @private * @param {*} value The value to process. * @returns {string} Returns the string. */ function toString(value) { if (typeof value == 'string') { return value; } return value == null ? '' : (value + ''); } /*----------------------------------------------------------------------------*/ /** * Creates a development and/or production build invoking `callback` for each. * The `callback` is invoked with one argument: (data). * * **Note:** For a list of commands see `consts.helpText` or run `lodash --help`. * * @param {Array|Object} [options=[]] An array of build commands or the state object. * @param {Function} [callback=defaultBuildCallback] The function called per build. */ function build(options, callback) { options || (options = []); callback || (callback = defaultBuildCallback); // Used to specify the output path for builds. var outputPath; // Used to specify the source map URL. var sourceMapURL; // Used to track the time it takes to complete a build. var stamp = _.now(); // Used to pre-populate the build state. var state = _.isPlainObject(options) && options; var isExcluded = function() { return _.every(arguments, function(value) { return !_.includes(buildFuncs, value); }); }; if (state) { var buildFuncs = state.buildFuncs, filePath = state.filePath, funcDepMap = state.funcDepMap, funcTokenMap = state.funcTokenMap, includeFuncs = state.includeFuncs, includeVars = state.includeVars, isDevelopment = true, isModularize = true, isStdOut = state.isStdOut, isStrict = state.isStrict, minusFuncs = [], outputPath = state.outputPath, plusFuncs = [], source = state.source, varDepMap = state.varDepMap, varNames = state.varNames; } else { // Clear `matchFunction` memoize cache. matchFunction.cache.clear(); // Clone dependencies to modify. var seqDepMap = new Hash(_.cloneDeep(mapping.seqDep)), funcDepMap = new Hash(_.cloneDeep(mapping.funcDep)), varDepMap = new Hash(_.cloneDeep(mapping.varDep)); // The path to the source file. var filePath = require.resolve('lodash'); // Used to specify a custom IIFE to wrap lodash. var iife = getOption(options, 'iife'); // Used to match external template files to precompile. var templatePattern = getOption(options, 'template', ''); // Used as the template settings for precompiled templates. var templateSettings = (function() { var result = getOption(options, 'settings'); return result ? Function('return {' + result.replace(/^\{|\}$/g, '') + '}')() : _.clone(_.templateSettings); }()); // A flag to specify a core build. var isCore = getOption(options, 'core'); // A flag to specify only creating the development build. var isDevelopment = getOption(options, ['-d', '--development']); // A flag to indicate that a custom IIFE was specified. var isIIFE = typeof iife == 'string'; // A flag to specify creating a source map for the minified source. var isMapped = getOption(options, ['-m', '--source-map']); // A flag to specify a modularize build. var isModularize = getOption(options, 'modularize'); // A flag to specify only creating the minified build. var isProduction = getOption(options, ['-p', '--production']); // A flag to specify writing output to standard output. var isStdOut = getOption(options, ['-c', '--stdout']); // A flag to specify skipping status updates normally logged to the console. var isSilent = !isBin || isStdOut || getOption(options, ['-s', '--silent']); // A flag to specify `_.assign`, `_.bindAll`, and `_.defaults` // are constructed using the "use strict" directive. var isStrict = getOption(options, 'strict'); // A flag to specify a template build. var isTemplate = !!templatePattern; // Used to specify the AMD module ID of lodash used by precompiled templates. var moduleId = getOption(options, 'moduleId'); // Used as the output path for the build. var outputPath = _.reduce(options, function(result, value, index) { return /^(?:-o|--output)$/.test(value) ? path.normalize(options[index + 1]) : result; }, isModularize ? ('.' + path.sep + 'modularize') : ''); // Used to specify the ways to export the `lodash` function. var exportsOptions = (function() { var result = getOption(options, 'exports', buildExports.defaults[isModularize ? 'modularize' : 'monolithic']); if (!isModularize && _.includes(result, 'umd')) { result = _.union(result, buildExports.umd); } return isModularize ? _.take(result, 1) : result; }()); // A flag to specify creating a custom build. var isCustom = !isModularize && ( isCore || isMapped || isStrict || isTemplate || /\b(?:category|exports|iife|include|minus|moduleId|plus)=/.test(options) || !_.isEqual(exportsOptions, buildExports.defaults.monolithic) ); // Flags to specify export options. var isAMD = _.includes(exportsOptions, 'amd'), isES = _.includes(exportsOptions, 'es'), isGlobal = _.includes(exportsOptions, 'global'), isNpm = _.includes(exportsOptions, 'npm'), isNode = isNpm || _.includes(exportsOptions, 'node'); if (isTemplate) { isModularize = false; } // The lodash.js source. var source = fs.readFileSync(filePath, 'utf8'); /*------------------------------------------------------------------------*/ // Categories of functions to include in the build. var categoryOptions = _.map(getOption(options, 'category', []), function(category) { return getRealCategory(_.capitalize(category.toLowerCase())); }); // Functions to include in the build. var includeFuncs = _.union(categoryOptions, _.map(getOption(options, 'include', []), getRealName)); // Variables to include in the build. var includeVars = _.intersection(includeFuncs, listing.varDeps); // Functions to remove from the build. var minusFuncs = _.map(getOption(options, 'minus', []), getRealName); // Functions to add to the build. var plusFuncs = _.map(getOption(options, 'plus', []), getRealName); // Expand categories to function names. _.each([includeFuncs, minusFuncs, plusFuncs], function(funcNames) { var categories = _.intersection(funcNames, listing.categories); _.each(categories, function(category) { push.apply(funcNames, _.filter(getNamesByCategory(category), function(key) { var type = typeof _[key]; return type == 'function' || type == 'undefined'; })); }); }); // Remove categories from function names. includeFuncs = _.difference(includeFuncs, listing.categories, includeVars); minusFuncs = _.difference(minusFuncs, listing.categories); plusFuncs = _.difference(plusFuncs, listing.categories); /*------------------------------------------------------------------------*/ // Used to capture warnings for invalid command-line arguments. var warnings = []; // Used to detect invalid command-line arguments. var invalidArgs = _.reject(options, function(value, index, options) { if (/^(?:-o|--output)$/.test(options[index - 1]) || /^(?:category|exports|iife|include|minus|moduleId|plus|settings|template)=[\s\S]*$/.test(value)) { return true; } var result = _.includes(listing.buildFlags, value); if (!result && /^(?:-m|--source-map)$/.test(options[index - 1])) { sourceMapURL = value; return true; } return result; }); // Report invalid command and option arguments. if (!_.isEmpty(invalidArgs)) { warnings.push('Invalid argument' + (_.size(invalidArgs) > 1 ? 's' : '') + ' passed: ' + invalidArgs.join(', ')); } // Report invalid command entries. _.forOwn({ 'category': { 'entries': categoryOptions, 'validEntries': listing.categories }, 'exports': { 'entries': exportsOptions, 'validEntries': buildExports[isModularize ? 'modularize' : 'monolithic'] }, 'include': { 'entries': includeFuncs, 'validEntries': listing.funcs }, 'minus': { 'entries': minusFuncs, 'validEntries': listing.funcs }, 'plus': { 'entries': plusFuncs, 'validEntries': listing.funcs } }, function(data, commandName) { invalidArgs = _.difference(data.entries, data.validEntries, ['none']); if (!_.isEmpty(invalidArgs)) { warnings.push('Invalid `' + commandName + '` entr' + (_.size(invalidArgs) > 1 ? 'ies' : 'y') + ' passed: ' + invalidArgs.join(', ')); } }); if (!_.isEmpty(warnings)) { var warnText = [ '', warnings, 'For more information type: lodash --help' ].join('\n'); if (isBin) { console.warn(warnText); process.exit(1); } else { callback(_.create(Error.prototype, { 'message': warnText, 'source': warnText })); } return; } // Display the help message. if (getOption(options, ['-h', '--help'])) { var helpText = consts.helpText; if (isBin) { console.log(helpText); } else { callback({ 'source': helpText }); } return; } // Display the `lodash.VERSION`. if (getOption(options, ['-V', '--version'])) { if (isBin) { console.log(_.VERSION); } else { callback({ 'source': _.VERSION }); } return; } /*------------------------------------------------------------------------*/ // The names of functions to include in the build. var buildFuncs = !isTemplate && (function() { source = setUseStrictOption(source, isStrict); if (isModularize) { // Remove `clearTimeout` and `setTimeout` deps. _.forOwn(funcDepMap, function(depNames) { _.pull(depNames, 'clearTimeout', 'setTimeout'); }); // Remove `lodash.placeholder` references. _.pull(funcDepMap.getHolder, 'lodash'); source = modifyFunction(source, 'getHolder', function(match) { return replaceVar(match, 'object', 'func'); }); // Add deps to wrap `_.mixin` in `main`. funcDepMap.main.push('baseFunctions', 'isObject', 'keys', 'mixin'); } else { // Add `arrayEach` to functions with placeholder support because it's // used to reduce code for placeholder assignments _.each(listing.placeholderFuncs, function(funcName) { funcDepMap[funcName].push('arrayEach'); }); } if (isModularize) { if (isNpm) { source = removeChaining(source, seqDepMap, funcDepMap, varDepMap); source = removeMetadata(source, funcDepMap); } // Remove `getIteratee` use from iteration methods. _.each(['forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight', 'times'], function(funcName) { _.pull(funcDepMap[funcName], 'getIteratee').push('castFunction'); source = modifyFunction(source, funcName, function(match) { return match.replace(/\bgetIteratee\(([^,\)]+)[\s\S]*?\)/, 'castFunction($1)'); }); }); } if (isCore && _.isEmpty(plusFuncs)) { source = removeLazyChaining(source, seqDepMap, funcDepMap, varDepMap); source = removeMetadata(source, funcDepMap); // Add `func` check to `createPartial`. source = modifyFunction(source, 'createPartial', function(match) { return match.replace(/^( *)(?=var\b)/m, function(match, indent) { var prelude = '\n' + indent; return indent + [ "if (typeof func != 'function') {", ' throw new TypeError(FUNC_ERROR_TEXT);', '}' ].join(prelude) + prelude; }); }); // Remove `apply` and `isSymbol`. _.forOwn(funcDepMap, function(depNames, identifier) { if (_.includes(depNames, 'apply')) { _.pull(depNames, 'apply'); source = modifyFunction(source, identifier, function(match) { return match.replace(/\bapply\(([^,)]+),\s*([^,)]+),\s*([^,)]+)\)/g, '$1.apply($2, $3)'); }); } if (_.includes(depNames, 'isSymbol')) { _.pull(depNames, 'isSymbol'); source = modifyFunction(source, identifier, function(match) { return match.replace(/\bisSymbol\([^)]+\)/g, 'false'); }); } }); // Remove chain dependencies. _.pull(seqDepMap.funcDep, 'baseLodash', 'wrapperCommit', 'wrapperNext', 'wrapperPlant', 'wrapperToIterator' ); // Remove ES5 and ES2015 built-in constructor deps. _.forOwn(funcDepMap, function(deps) { _.pull(deps, 'DataView', 'Map', 'Promise', 'Reflect', 'Set', 'Symbol', 'Uint8Array', 'WeakMap'); }); // Remove map, set, stack, and typed array support from `baseIsEqualDeep`. _.pull(funcDepMap.baseIsEqualDeep, 'getTag', 'isBuffer', 'isTypedArray', 'Stack').push('baseGetTag', 'find'); source = modifyFunction(source, 'baseIsEqualDeep', function(match) { return match .replace(/\bgetTag\b/g, 'baseGetTag') .replace(/\s*\|\|\s*isTypedArray\([^)]+\)/, '') .replace(/^( *)if\s*\(.+?isBuffer\b[\s\S]+?\n\1\}\n/m, '') .replace(/^( *)stack\s*\|\|[\s\S]+?\breturn\b([^;]+)(?=;\n)/gm, function(match, indent, compared) { return indent + [ 'var result =' + compared + ';', "stack.pop();", 'return result' ].join('\n' + indent); }) .replace(/^ *(?=if\s*\(isSameTag\b)/m, function(indent) { return indent + [ 'stack || (stack = []);', 'var objStack = find(stack, function(entry) {', ' return entry[0] == object;', '});', 'var othStack = find(stack, function(entry) {', ' return entry[0] == other;', '});', 'if (objStack && othStack) {', ' return objStack[1] == other;', '}', 'stack.push([object, other]);', 'stack.push([other, object]);', '' ].join('\n' + indent); }); }); // Remove `baseLodash` references. _.each(['lodash', 'LodashWrapper'], function(funcName) { _.pull(funcDepMap[funcName], 'baseLodash'); }); source = source.replace(/\bbaseLodash\.prototype\b/g, 'lodash.prototype'); // Remove `guard` check from `createAssigner`. _.pull(funcDepMap.createAssigner, 'isIterateeCall'); source = modifyFunction(source, 'createAssigner', function(match) { match = removeVar(match, 'guard'); return match.replace(/^(?: *\/\/.*\n)*( *)if\s*\(guard\b[^}]+?\n\1\}\n/m, ''); }); // Remove set cache use from `equalArrays`. _.pull(funcDepMap.equalArrays, 'cacheHas', 'SetCache').push('indexOf'); source = modifyFunction(source, 'equalArrays', function(match) { return match .replace(/\bnew SetCache\b/, '[]') .replace(/\bcacheHas\b/, 'indexOf') .replace(/\badd\b/, 'push'); }); // Remove array buffer, data view, map, set, and symbol support from `equalByTag`. _.pull(funcDepMap.equalByTag, 'mapToArray', 'setToArray'); source = modifyFunction(source, 'equalByTag', function(match) { return match.replace(/^( *) case\s*(?:arrayBuffer|dataView|map|set|symbol)Tag\b[\s\S]+?(?=\s*case\b|\n\1\})/gm, ''); }); // Remove stack use from `equalArrays`, `equalByTag`, and `equalObjects`. _.each(['equalArrays', 'equalByTag', 'equalObjects'], function(funcName) { source = modifyFunction(source, funcName, function(match) { match = removeVar(match, 'stacked'); return match .replace(/^( *)if\s*\(stacked\b[^}]+?\n\1\}\n/m, '') .replace(/^ *stack\.set\([^)]+\);\n/gm, '') .replace(/^ *stack\['delete'\]\([^)]+\);\n/gm, ''); }); }); // Remove `customizer` use from `equalArrays` and `equalObjects`. _.each(['equalArrays', 'equalObjects'], function(funcName) { source = modifyFunction(source, funcName, function(match) { return match.replace(/^( *)if\s*\(customizer\b[\s\S]+?\n\1}/m, '$1var compared;'); }); }); // Remove switch statements from functions. _.each(['createCtor', 'negate'], function(funcName) { source = modifyFunction(source, funcName, function(match) { return match.replace(/^(?: *\/\/.*\n)*( *)switch\b[\s\S]+?\n\1\}\n/m, ''); }); }); // Remove `LazyWrapper` references. _.pull(funcDepMap.baseWrapperValue, 'LazyWrapper'); source = modifyFunction(source, 'baseWrapperValue', function(match) { return match.replace(/^( *)if\b.+?LazyWrapper\b[\s\S]+?\n\1}\n/m, ''); }); // Remove unneeded properties from `LodashWrapper. source = modifyFunction(source, 'LodashWrapper', function(match) { return match.replace(/^ *this\.__(?:index|values)__\s*=[^;]+;\n/gm, ''); }); // Replace array functions with their base counterparts. _.each(['arrayEach', 'arrayFilter', 'arrayMap', 'arraySome', 'arrayReduce'], function(arrayName) { var reArrayName = RegExp('\\b' + arrayName + '\\b', 'g'); var baseName = arrayName == 'arrayReduce' ? arrayName.replace(/^array/, '').toLowerCase() : arrayName.replace(/^array/, 'base'); _.forOwn(funcDepMap, function(deps, funcName) { if (_.includes(deps, arrayName)) { _.pull(deps, arrayName).push(baseName); source = modifyFunction(source, funcName, function(match) { return match.replace(reArrayName, baseName); }); } }); }); // Replace `arrayEach` with `baseEach` in the chain scaffolding. _.each([seqDepMap.funcDep, funcDepMap.mixin], function(depMap) { _.pull(depMap, 'arrayEach').push('baseEach'); }); source = source.replace(/^(?: *\/\/.*\n)*( *)[$\w]+\(\['pop'[\s\S]+?\n\1\}\);\n/m, function(match) { return match .replace(/\barrayEach\b/g, 'baseEach') .replace("'pop',", "$& 'join', 'replace', 'reverse', 'split',") .replace(/\bpop\|/, '$&join|replace|') .replace(/func\s*=[\s\S]+?(?=,\n)/, function() { return 'func = (/^(?:replace|split)$/.test(methodName) ? String.prototype : arrayProto)[methodName]'; }); }); // Replace `getAllKeys` with `keys` in `equalObjects`. _.pull(funcDepMap.equalObjects, 'getAllKeys').push('keys'); source = modifyFunction(source, 'equalObjects', function(match) { return match.replace(/\bgetAllKeys\b/g, 'keys'); }); // Replace `baseAssign` with `assign` in `_.create`. _.pull(funcDepMap.create, 'baseAssign').push('assign'); source = modifyFunction(source, 'create', function(match) { return match.replace(/\bbaseAssign\b/g, 'assign'); }); // Replace `createWrap` with `_.partial` in `_.wrap`. _.pull(funcDepMap.wrap, 'createWrap').push('partial'); source = modifyFunction(source, 'wrap', function(match) { return match.replace(/\bcreateWrap\([\s\S]+?(?=;\n)/, 'partial(wrapper, value)'); }); // Replace type check methods with their base counterparts. _.each(['isArrayBuffer', 'isDate', 'isMap', 'isRegExp', 'isSet', 'isTypedArray'], function(funcName) { var baseName = 'base' + _.upperFirst(funcName); funcDepMap[funcName] = [baseName]; varDepMap[funcName] = []; source = replaceFunction(source, funcName, 'var ' + funcName + ' = ' + baseName + ';'); }); // Replace method implementations. source = replaceFunction(source, 'arrayPush', [ 'function arrayPush(array, values) {', ' array.push.apply(array, values);', ' return array;', '}' ].join('\n')); funcDepMap.baseAssignValue = []; source = replaceFunction(source, 'baseAssignValue', [ 'function baseAssignValue(object, key, value) {', ' object[key] = value;', '}' ].join('\n')); funcDepMap.baseGetTag = ['objectToString']; source = replaceFunction(source, 'baseGetTag', [ 'function baseGetTag(value) {', ' return objectToString(value);', '}' ].join('\n')); funcDepMap.baseIsArguments = ['noop']; source = replaceFunction(source, 'baseIsArguments', 'var baseIsArguments = noop;'); funcDepMap.baseIteratee = ['baseMatches', 'baseProperty', 'identity']; source = replaceFunction(source, 'baseIteratee', [ 'function baseIteratee(func) {', " if (typeof func == 'function') {", ' return func;', ' }', ' if (func == null) {', ' return identity;', ' }', " return (typeof func == 'object' ? baseMatches : baseProperty)(func);", '}' ].join('\n')); funcDepMap.baseMatches = ['baseIsEqual', 'nativeKeys']; source = replaceFunction(source, 'baseMatches', [ 'function baseMatches(source) {', ' var props = nativeKeys(source);', ' return function(object) {', ' var length = props.length;', ' if (object == null) {', ' return !length;', ' }', ' object = Object(object);', ' while (length--) {', ' var key = props[length];', ' if (!(key in object &&', ' baseIsEqual(source[key], object[key], COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG)', ' )) {', ' return false;', ' }', ' }', ' return true;', ' };', '}' ].join('\n')); funcDepMap.basePick = ['reduce']; source = replaceFunction(source, 'basePick', [ 'function basePick(object, props) {', ' object = Object(object);', ' return reduce(props, function(result, key) {', ' if (key in object) {', ' result[key] = object[key];', ' }', ' return result;', ' }, {});', '}' ].join('\n')); funcDepMap.copyArray = ['baseSlice']; source = replaceFunction(source, 'copyArray', [ 'function copyArray(source) {', ' return baseSlice(source, 0, source.length);', '}' ].join('\n')); (function() { var copyArray = matchFunction(source, 'copyArray', true); source = removeFunction(source, 'copyArray'); source = modifyFunction(source, 'baseSlice', function(match) { return match + '\n' + copyArray; }); }()); source = replaceFunction(source, 'isFlattenable', [ 'function isFlattenable(value) {', ' return isArray(value) || isArguments(value);', '}' ].join('\n')); funcDepMap.setToString = ['identity']; source = replaceFunction(source, 'setToString', 'var setToString = identity;'); funcDepMap.wrapperClone = ['copyArray', 'LodashWrapper']; source = replaceFunction(source, 'wrapperClone', [ 'function wrapperClone(wrapper) {', ' var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);', ' result.__actions__ = copyArray(wrapper.__actions__);', ' return result;', '}' ].join('\n')); funcDepMap.lodash = ['LodashWrapper']; source = replaceFunction(source, 'lodash', [ 'function lodash(value) {', ' return value instanceof LodashWrapper', ' ? value', ' : new LodashWrapper(value);', '}' ].join('\n')); funcDepMap.assign = ['createAssigner', 'copyObject', 'nativeKeys']; source = replaceFunction(source, 'assign', [ 'var assign = createAssigner(function(object, source) {', ' copyObject(source, nativeKeys(source), object);', '});' ].join('\n')); funcDepMap.assignIn = ['createAssigner', 'copyObject', 'nativeKeysIn']; source = replaceFunction(source, 'assignIn', [ 'var assignIn = createAssigner(function(object, source) {', ' copyObject(source, nativeKeysIn(source), object);', '});' ].join('\n')); funcDepMap.bind = ['baseRest', 'createPartial']; source = replaceFunction(source, 'bind', [ 'var bind = baseRest(function(func, thisArg, partials) {', ' return createPartial(func, WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG, thisArg, partials);', '});' ].join('\n')); funcDepMap.clone = ['copyArray', 'copyObject', 'isArray', 'isObject', 'nativeKeys']; source = replaceFunction(source, 'clone', [ 'function clone(value) {', ' if (!isObject(value)) {', ' return value;', ' }', ' return isArray(value) ? copyArray(value) : copyObject(value, nativeKeys(value));', '}' ].join('\n')); funcDepMap.compact = ['baseFilter']; source = replaceFunction(source, 'compact', [ 'function compact(array) {', ' return baseFilter(array, Boolean);', '}' ].join('\n')); funcDepMap.filter = ['baseFilter', 'baseIteratee']; source = replaceFunction(source, 'filter', [ 'function filter(collection, predicate) {', ' return baseFilter(collection, baseIteratee(predicate));', '}' ].join('\n')); funcDepMap.forEach = ['baseIteratee', 'baseEach']; source = replaceFunction(source, 'forEach', [ 'function forEach(collection, iteratee) {', ' return baseEach(collection, baseIteratee(iteratee));', '}' ].join('\n')); funcDepMap.every = ['baseEvery', 'baseIteratee']; source = replaceFunction(source, 'every', [ 'function every(collection, predicate, guard) {', ' predicate = guard ? undefined : predicate;', ' return baseEvery(collection, baseIteratee(predicate));', '}' ].join('\n')); funcDepMap.has = []; source = replaceFunction(source, 'has', [ 'function has(object, path) {', ' return object != null && hasOwnProperty.call(object, path);', '}' ].join('\n')); funcDepMap.indexOf = []; source = replaceFunction(source, 'indexOf', [ 'function indexOf(array, value, fromIndex) {', ' var length = array == null ? 0 : array.length;', " if (typeof fromIndex == 'number') {", ' fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : fromIndex;', ' } else {', ' fromIndex = 0;', ' }', ' var index = (fromIndex || 0) - 1,', ' isReflexive = value === value;', '', ' while (++index < length) {', ' var other = array[index];', ' if ((isReflexive ? other === value : other !== other)) {', ' return index;', ' }', ' }', ' return -1;', '}' ].join('\n')); funcDepMap.invokeMap = ['baseMap', 'baseRest']; source = replaceFunction(source, 'invokeMap', [ 'var invokeMap = baseRest(function(collection, path, args) {', " var isFunc = typeof path == 'function';", ' return baseMap(collection, function(value) {', ' var func = isFunc ? path : value[path];', ' return func == null ? func : func.apply(value, args);', ' });', '});' ].join('\n')); funcDepMap.isEmpty = ['isArguments', 'isArray', 'isArrayLike', 'isFunction', 'isString', 'nativeKeys']; source = replaceFunction(source, 'isEmpty', [ 'function isEmpty(value) {', ' if (isArrayLike(value) &&', ' (isArray(value) || isString(value) ||', ' isFunction(value.splice) || isArguments(value))) {', ' return !value.length;', ' }', ' return !nativeKeys(value).length;', '}' ].join('\n')); funcDepMap.iteratee = ['baseIteratee']; source = replaceFunction(source, 'iteratee', 'var iteratee = baseIteratee;'); funcDepMap.keys = ['nativeKeys']; source = replaceFunction(source, 'keys', 'var keys = nativeKeys;'); funcDepMap.keysIn = ['nativeKeysIn']; source = replaceFunction(source, 'keysIn', 'var keysIn = nativeKeysIn;'); funcDepMap.map = ['baseMap', 'baseIteratee']; source = replaceFunction(source, 'map', [ 'function map(collection, iteratee) {', ' return baseMap(collection, baseIteratee(iteratee));', '}' ].join('\n')); funcDepMap.matches = ['assign', 'baseMatches']; source = replaceFunction(source, 'matches', [ 'function matches(source) {', ' return baseMatches(assign({}, source));', '}' ].join('\n')); funcDepMap.partial = ['baseRest', 'createPartial']; source = replaceFunction(source, 'partial', [ 'var partial = baseRest(function(func, partials) {', ' return createPartial(func, PARTIAL_FLAG, undefined, partials);', '});' ].join('\n')); funcDepMap.property = ['baseProperty']; source = replaceFunction(source, 'property', 'var property = baseProperty;'); funcDepMap.reduce = ['baseEach', 'baseIteratee', 'baseReduce']; source = replaceFunction(source, 'reduce', [ 'function reduce(collection, iteratee, accumulator) {', ' return baseReduce(collection, baseIteratee(iteratee), accumulator, arguments.length < 3, baseEach);', '}' ].join('\n')); funcDepMap.result = ['isFunction']; source = replaceFunction(source, 'result', [ 'function result(object, path, defaultValue) {', ' var value = object == null ? undefined : object[path];', ' if (value === undefined) {', ' value = defaultValue;', ' }', ' return isFunction(value) ? value.call(object) : value;', '}' ].join('\n')); funcDepMap.size = ['isArrayLike', 'nativeKeys']; source = replaceFunction(source, 'size', [ 'function size(collection) {', ' if (collection == null) {', ' return 0;', ' }', ' collection = isArrayLike(collection) ? collection : nativeKeys(collection);', ' return collection.length;', '}' ].join('\n')); funcDepMap.slice = ['baseSlice']; source = replaceFunction(source, 'slice', [ 'function slice(array, start, end) {', ' var length = array == null ? 0 : array.length;', ' start = start == null ? 0 : +start;', ' end = end === undefined ? length : +end;', ' return length ? baseSlice(array, start, end) : [];', '}' ].join('\n')); funcDepMap.some = ['baseSome', 'baseIteratee']; source = replaceFunction(source, 'some', [ 'function some(collection, predicate, guard) {', ' predicate = guard ? undefined : predicate;', ' return baseSome(collection, baseIteratee(predicate));', '}' ].join('\n')); funcDepMap.sortBy = ['baseIteratee', 'baseMap', 'baseProperty', 'compareAscending']; source = replaceFunction(source, 'sortBy', [ 'function sortBy(collection, iteratee) {', ' var index = 0;', ' iteratee = baseIteratee(iteratee);', '', ' return baseMap(baseMap(collection, function(value, key, collection) {', " return { 'value': value, 'index': index++, 'criteria': iteratee(value, key, collection) };", ' }).sort(function(object, other) {', ' return compareAscending(object.criteria, other.criteria) || (object.index - other.index);', " }), baseProperty('value'));", '}' ].join('\n')); funcDepMap.toArray = ['copyArray', 'isArrayLike', 'values']; varDepMap.toArray = []; source = replaceFunction(source, 'toArray', [ 'function toArray(value) {', ' if (!isArrayLike(value)) {', ' return values(value);', ' }', ' return value.length ? copyArray(value) : [];', '}' ].join('\n')); funcDepMap.toInteger = []; source = replaceFunction(source, 'toInteger', 'var toInteger = Number;'); funcDepMap.toNumber = []; source = replaceFunction(source, 'toNumber', 'var toNumber = Number;'); funcDepMap.toString = []; varDepMap.toString = []; source = replaceFunction(source, 'toString', [ 'function toString(value) {', " if (typeof value == 'string') {", ' return value;', ' }', " return value == null ? '' : (value + '');", '}' ].join('\n')); } // Redistribute `seqDepMap` deps. if (isModularize) { funcDepMap.main = _.union(funcDepMap.main, _.difference(seqDepMap.funcDep, _.without(mapping.category.Seq, 'thru'))); varDepMap.main = _.union(varDepMap.main, seqDepMap.varDep); } else { funcDepMap.wrapperValue = _.union(funcDepMap.wrapperValue, _.without(seqDepMap.funcDep, 'lodash', 'LodashWrapper')); varDepMap.wrapperValue = _.union(varDepMap.wrapperValue, seqDepMap.varDep); _.each(_.without(mapping.category.Seq, 'lodash', 'wrapperValue'), function(funcName) { funcDepMap[funcName].push('wrapperValue'); }); } if (isModularize || (isCore && _.isEmpty(plusFuncs))) { source = removeGetIteratee(source, funcDepMap); } // Remove iteratee shorthand support from `iteratee`. if (_.isEmpty(_.difference(['matches', 'matchesProperty', 'property'], minusFuncs))) { _.pull(funcDepMap.iteratee, 'baseClone'); source = modifyFunction(source, 'iteratee', function(match) { return match.replace(/^( *return\s+)[\s\S]+?(?=;\n)/m, '$1baseIteratee(func)'); }); } // Remove individual iteratee shorthands from `baseIteratee`. _.each(['baseMatches', 'baseMatchesProperty', 'property'], function(funcName) { var otherName = _.camelCase(_.trimStart(funcName, 'base')); if (_.includes(minusFuncs, otherName)) { _.pull(funcDepMap.baseIteratee, funcName); source = modifyFunction(source, 'baseIteratee', function(match) { return match.replace(RegExp('\\b' + funcName + '\\([\\s\\S]+?\\)(?=[;\\n])', 'm'), 'identity'); }); } }); // Add core function names. if (isCore) { var result = _.clone(listing.coreFuncs); } // Add function names explicitly. else if (!_.isEmpty(includeFuncs)) { result = includeFuncs; } // Add default function names. else if (_.isEmpty(includeVars)) { result = _.clone(listing.includes); } // Remove special "none" entry. _.pull(result, 'none'); // Force removing or adding deps. if (isModularize) { minusFuncs.push('noConflict', 'runInContext'); } else { plusFuncs.push('lodash'); } // Add extra function names. if (!_.isEmpty(plusFuncs)) { result = _.union(result, plusFuncs); } // Subtract function names. if (!_.isEmpty(minusFuncs)) { result = _.difference(result, minusFuncs.concat(getDependants(minusFuncs, funcDepMap, true))); } if (!isModularize) { result.push('main'); } result = _.uniq(result); var hasLodash = _.includes(result, 'lodash'); result = getDependencies(result, funcDepMap, true, ['lodash']); // Simplify `lodash` when not capable of chaining. if (!_.includes(result, 'mixin') && !_.includes(result, 'wrapperValue')) { funcDepMap.lodash.length = 0; source = replaceFunction(source, 'lodash', [ 'function lodash() {', ' // No operation performed.', '}' ].join('\n')); } if (hasLodash) { result = _.union(result, ['lodash'], getDependencies('lodash', funcDepMap, true)); } return result; }()); // Expand properties, variables, and their function dependencies to include in the build. var allDeps = getAllDependencies(buildFuncs, funcDepMap, varDepMap).sort(); buildFuncs = _.intersection(listing.funcs, allDeps); includeVars = _.intersection(listing.varDeps, allDeps); /*------------------------------------------------------------------------*/ // Load customized lodash module. var lodash = !isTemplate && (function() { var context = vm.createContext({ 'console': console }); vm.runInContext(source, context); return context._; }()); /*------------------------------------------------------------------------*/ if (isTemplate) { source = buildTemplate({ 'moduleId': moduleId, 'source': source, 'templatePattern': templatePattern, 'templateSettings': templateSettings }); } else if (isModularize) { // Replace the `lodash.templateSettings` property assignment with a variable assignment. source = source.replace(/\b(lodash\.)(?=templateSettings\s*=)/, 'var '); // Remove the `lodash` namespace from properties. source = source.replace(/( *)lodash\.([$\w]+\.)?([$\w]+)(\s*=\s*(?:function\([\s\S]+?\n\1\}|.+?);\n)?/g, function(match, indent, property, identifier, right) { return (property || right || identifier == 'com' || identifier == 'prototype') ? match : (indent + identifier); }); // Remove all horizontal rule comment separators. source = source.replace(/^ *\/\*-+\*\/\n/gm, ''); // Remove `lodash` branch in `_.mixin`. source = modifyFunction(source, 'mixin', function(match) { return match .replace(/^(?: *\/\/.*\n)*( *)if\s*\(options\s*==\s*null\b[\s\S]+?\n\1\}\n/m, '') .replace(/^(?: *\/\/.*\n)*( *)if\s*\(!methodNames\b[\s\S]+?\n\1\}/m, function(match, indent) { return indent + 'var methodNames = baseFunctions(source, keys(source));\n'; }); }); // Replace `lodash` use in `_.templateSettings.imports`. source = modifyProp(source, 'templateSettings', function(match) { return match.replace(/(:\s*)lodash\b/, "$1{ 'escape': escape }"); }); source = modifyFunction(source, 'template', function(match) { // Assign `settings` using `template.imports`. match = match.replace(/=\s*templateSettings(?=[,;])/, '$&.imports._.templateSettings || templateSettings'); // Remove default `sourceURL` value. return modifyVar(match, 'sourceURL', function(match) { return match.replace(/^( *)(var\s+sourceURL\s*=\s*)[\s\S]+?(?=;\n$)/m, function(submatch, indent, left) { return indent + left + [ "hasOwnProperty.call(options, 'sourceURL')", indent + " ? ('//# sourceURL=' +", indent + " (options.sourceURL + '').replace(/\\s/g, ' ') +", indent + " '\\n')", indent + " : ''" ].join('\n'); }); }); }); if (!isAMD) { source = removeVar(source, 'undefined'); } } if (!isTemplate) { // Remove functions and method assignments from the build. _.each(_.difference(listing.funcs, buildFuncs), function(funcName) { source = removeFunction(source, funcName); if (!isModularize) { source = removeMethodAssignment(source, funcName); } }); // Remove method assignments for non-core methods. if (isCore) { _.each(_.difference(buildFuncs, listing.coreFuncs, plusFuncs), function(funcName) { source = removeMethodAssignment(source, funcName); }); } // Tokenize functions to reduce modularized build times. if (isModularize) { funcTokenMap = _.transform(buildFuncs, function(result, funcName) { var match = matchFunction(source, funcName, true); source = replace(source, match, _.get(/^,\s+/.exec(match), 0, '') + '<<' + funcName + '>>' + _.get(/(?:,?\s+|\n)$/.exec(match), 0, '') ); result[funcName] = match; }, new Hash); } // Detect variables after tokenizing functions. varNames = _.difference(getVars(source), buildFuncs.concat(includeVars, 'freeGlobal')); } /*------------------------------------------------------------------------*/ (function() { var snippet = isTemplate ? source : source.slice(-(getMethodAssignments(source), RegExp.rightContext.length)), modified = snippet; // Set the AMD module ID. if (isAMD && !isModularize && !isTemplate && moduleId != null && moduleId != 'none') { modified = modified.replace(/^ *define\((?=function)/m, "$&'" + moduleId + "', "); } // Customize lodash's exports. if (!isAMD || isModularize) { modified = modified.replace(/^(?: *\/\/.*\n)*( *)if\s*\(typeof\s+define\b[\s\S]+?else\s+/m, '$1'); } if (!isNode || isModularize) { modified = modified.replace(/^(?: *\/\/.*\n)*( *)(?:else\s+)?if\s*\(freeModule\b[\s\S]+?\n\1\}\n+/m, ''); } if (!isGlobal || isModularize) { if (isAMD && !isModularize && !isTemplate) { modified = modified.replace(/^(?: *\/\/.*\n)* *root\._\s*=[\s\S]+?\n+/m, ''); } modified = modified.replace(/^(?: *\/\/.*\n)*( *)else\b[^}]+?(?:\._|_\.templates)\s*=[\s\S]+?\n\1\}\n+/m, ''); } else if (!isAMD && !isNode) { modified = modified .replace(/^( *)else\s+(?=if\s*\(_\))/m, '$1') .replace(/^( *)else\s\{\s*([^}]+?\._\s*=[\s\S]+?)\n\1\}/m, function(match, indent, block) { return indent + block.replace(/\n\s*/g, '\n' + indent); }); } source = isTemplate ? modified : source.replace(snippet, modified); }()); /*------------------------------------------------------------------------*/ source = cleanupSource(source); // Exit early to create modules. if (isModularize) { if (callback == defaultBuildCallback) { callback = null; } buildModule({ 'buildFuncs': buildFuncs, 'filePath': filePath, 'funcDepMap': funcDepMap, 'funcTokenMap': funcTokenMap, 'includeFuncs': includeFuncs, 'includeVars': includeVars, 'isAMD': isAMD, 'isES': isES, 'isNode': isNode, 'isNpm': isNpm, 'isSilent': isSilent, 'isStdOut': isStdOut, 'isStrict': isStrict, 'lodash': lodash, 'minusFuncs': minusFuncs, 'options': options, 'outputPath': outputPath, 'plusFuncs': plusFuncs, 'source': source, 'stamp': stamp, 'varDepMap': varDepMap, 'varNames': varNames }, callback); return; } } /*--------------------------------------------------------------------------*/ // Modify/remove references to removed functions/variables. if (!isTemplate) { // Remove prototype assignments. _.each(['Hash', 'ListCache', 'MapCache', 'SetCache', 'Stack'], function(funcName) { if (isExcluded(funcName)) { source = source.replace(RegExp('^(?: *\\/\\/.*\\n)* *' + funcName + '\\.prototype(?:\\.[$\\w]+|\\[\'[^\']+\'\\])\\s*=[^;]+;\\n', 'gm'), ''); } }); if (isExcluded('getTag')) { source = removeGetTagFork(source); } if (isExcluded('LazyWrapper')) { // Remove `LazyWrapper.prototype` assignment. source = source.replace(/^(?: *\/\/.*\n)* *LazyWrapper\.prototype(\.constructor)?\s*=[^;]+;\n/gm, ''); } if (isExcluded('lodash') || !_.includes(funcDepMap.lodash, 'baseLodash')) { // Remove `lodash.prototype` assignment. source = source.replace(/^(?: *\/\/.*\n)* *lodash\.prototype(\.constructor)?\s*=[^;]+;\n/gm, ''); } if (isExcluded('LodashWrapper')) { // Remove `LodashWrapper.prototype` assignment. source = source.replace(/^(?: *\/\/.*\n)* *LodashWrapper\.prototype(\.constructor)?\s*=[^;]+;\n/gm, ''); } if (isExcluded('memoize')) { // Remove `memoize.Cache` assignment. source = source.replace(/^(?: *\/\/.*\n)* *memoize\.Cache\s*=[^;]+;\n/m, ''); } if (isExcluded(isModularize ? 'main' : 'wrapperToIterator')) { // Remove conditional `wrapperToIterator` assignment. source = source.replace(/^(?: *\/\/.*\n)*( *)if\s*\(symIterator\)[\s\S]+?\n\1\}\n/gm, ''); } if (isExcluded(isModularize ? 'main' : 'mixin')) { source = removeMixinCalls(source); } if (isExcluded(isModularize ? 'main' : 'wrapperValue')) { source = removeChaining(source); } if (isModularize && isExcluded('main')) { source = removeAssignments(source); } // Remove placeholder assignments of removed functions. source = source.replace(/^((?: *\/\/.*\n)*)( *)(arrayEach\(\[')bind\b[\s\S]+?('\][\s\S]+?\n\2\}\))/m, function(match, comment, indent, prelude, postlude) { var funcNames = _.reject(listing.placeholderFuncs, isExcluded), length = isCore ? 0 : funcNames.length; if (length) { return length > 1 ? (comment + indent + prelude + funcNames.join("', '") + postlude) : (comment + indent + funcNames[0] + '.placeholder = ') + (isModularize ? '{}' : 'lodash'); } return ''; }); // Remove unused variable dependencies. _.each(_.difference(listing.varDeps, includeVars.concat('root')), function(varName) { source = removeVar(source, varName); }); if (!_.includes(includeVars, 'root')) { varNames = _.without(varNames, 'root'); source = removeVar(source, 'root'); } if (!_.includes(includeVars, 'templateSettings')) { varNames = _.without(varNames, 'templateSettings'); source = removeProp(source, 'templateSettings'); } if (isModularize) { // Untokenize functions to include in the build. _.each(buildFuncs, function(funcName) { source = source.replace(RegExp(',\\s+<<' + funcName + '>>|<<' + funcName + '>>(,\\s+|\\n)?'), function() { return funcTokenMap[funcName]; }); }); source = source.replace(consts.reNamedToken, ''); } // Remove unused variables. source = stringFree(source, function(source) { return commentFree(source, function(source) { var useMap = new Hash; while (varNames.length) { varNames = _.sortBy(varNames, function(varName) { var result = isVarUsed(source, varName); useMap[varName] = result; return result; }); if (useMap[varNames[0]]) { break; } while (varNames.length && !useMap[varNames[0]]) { source = removeVar(source, varNames.shift()); } } return source; }); }); } // Customize lodash's IIFE. if (isIIFE) { source = replaceIIFE(source, iife); } /*--------------------------------------------------------------------------*/ source = cleanupSource(source); // A flag to track if `outputPath` has been used by `callback`. var outputUsed = false; // Expand `outputPath` and create directories if needed. if (outputPath) { outputPath = (function() { var dirname = path.dirname(outputPath); fs.mkdirpSync(dirname); return path.join(fs.realpathSync(dirname), path.basename(outputPath)); }()); } // Resolve the basename of the output path. var basename = filePath = outputPath; if (!basename) { basename = 'lodash'; if (isTemplate) { basename += '.templates'; } else if (isCustom) { basename += '.custom'; } filePath = path.join(cwd, basename + '.js'); } // Output development build. if (!isProduction) { var devSource = source; if (isCustom) { devSource = addCommandsToHeader(devSource, options); } if (isDevelopment && isStdOut) { stdout.write(devSource); callback({ 'source': devSource }); } else if (!isStdOut) { outputUsed = true; callback({ 'source': devSource, 'outputPath': filePath }); } } // Begin the minification process. if (!isDevelopment) { if (outputPath && outputUsed) { outputPath = path.join(path.dirname(outputPath), path.basename(outputPath, '.js') + '.min.js'); } else if (!outputPath) { outputPath = path.join(cwd, basename + '.min.js'); } minify(source, { 'filePath': filePath, 'isMapped': isMapped, 'isSilent': isSilent, 'isTemplate': isTemplate, 'modes': isIIFE ? ['simple', 'hybrid'] : ['simple', 'advanced', 'hybrid'], 'outputPath': outputPath, 'sourceMapURL': sourceMapURL, 'onComplete': function(data) { if (isCustom) { data.source = addCommandsToHeader(data.source, options); } if (isStdOut) { delete data.outputPath; stdout.write(data.source); } callback(data); } }); } } /*----------------------------------------------------------------------------*/ // Export `build`. if (!isBin) { module.exports = build; } // Handle invoking `build` by the command-line. else if (_.size(process.argv) > 2) { build(process.argv.slice(2)); }