UNPKG

@gerhobbelt/mathjax-third-party-extensions

Version:

A list of MathJax extensions provided by third-party contributors

348 lines (310 loc) 12.3 kB
// clean.js // ======== // Removes any AMD and/or CommonJS trace from the provided source code define([ 'utils', 'defaultValues', 'traverseAndUpdateAst', 'findAndStoreAllModuleIds', 'createAst', 'generateCode', 'normalizeModuleName' ], function( utils, defaultValues, traverseAndUpdateAst, findAndStoreAllModuleIds, createAst, generateCode, normalizeModuleName ) { // clean // ----- // Creates an AST using Esprima, traverse and updates the AST using Estraverse, and generates standard JavaScript using Escodegen. return function clean() { var amdclean = this, options = amdclean.options, ignoreModules = options.ignoreModules, originalAst = {}, ast = {}, configAst = {}, generatedCode, declarations = [], hoistedVariables = {}, hoistedCallbackParameters = {}, defaultRange = defaultValues.defaultRange, defaultLOC = defaultValues.defaultLOC; // Creates and stores an AST representation of the code originalAst = createAst.call(amdclean); // Loops through the AST, finds all module ids, and stores them in the current instance storedModules property findAndStoreAllModuleIds.call(amdclean, originalAst); // Traverses the AST and removes any AMD trace ast = traverseAndUpdateAst.call(amdclean, { ast: originalAst }); // Post Clean Up // Removes all empty statements from the source so that there are no single semicolons and // Makes sure that all require() CommonJS calls are converted // And all aggressive optimizations (if the option is turned on) are handled if (ast && _.isArray(ast.body)) { estraverse.replace(ast, { enter: function(node, parent) { var normalizedModuleName, assignmentName = node && node.left && node.left.name ? node.left.name : '', cb = node.right, assignmentNodes = [], assignments = {}, mappedParameters = _.filter(amdclean.callbackParameterMap[assignmentName], function(currentParameter) { return currentParameter && currentParameter.count > 1; }), mappedCbDependencyNames, mappedCbParameterNames, paramsToRemove = []; if (node === undefined || node.type === 'EmptyStatement') { _.each(parent.body, function(currentNode, iterator) { if (currentNode === undefined || currentNode.type === 'EmptyStatement') { parent.body.splice(iterator, 1); } }); } else if (utils.isRequireExpression(node)) { if (node['arguments'] && node['arguments'][0] && node['arguments'][0].value) { normalizedModuleName = normalizeModuleName.call(amdclean, node['arguments'][0].value); if (ignoreModules.indexOf(normalizedModuleName) === -1) { return { 'type': 'Identifier', 'name': normalizedModuleName, 'range': (node.range || defaultRange), 'loc': (node.loc || defaultLOC) }; } else { return node; } } else { return node; } } else if (options.aggressiveOptimizations === true && node.type === 'AssignmentExpression' && assignmentName) { // The names of all of the current callback function parameters mappedCbParameterNames = _.map((cb && cb.callee && cb.callee.params ? cb.callee.params : []), function(currentParam) { return currentParam.name; }); // The names of all of the current callback function dependencies mappedCbDependencyNames = _.map(cb.arguments, function(currentArg) { return currentArg.name; }); // Loop through the dependency names _.each(mappedCbDependencyNames, function(currentDependencyName) { // Nested loop to see if any of the dependency names map to a callback parameter _.each(amdclean.callbackParameterMap[currentDependencyName], function(currentMapping) { var mappedName = currentMapping.name, mappedCount = currentMapping.count; // Loops through all of the callback function parameter names to see if any of the parameters should be removed _.each(mappedCbParameterNames, function(currentParameterName, iterator) { if (mappedCount > 1 && mappedName === currentParameterName) { paramsToRemove.push(iterator); } }); }); }); _.each(paramsToRemove, function(currentParam) { cb.arguments.splice(currentParam, currentParam + 1); cb.callee.params.splice(currentParam, currentParam + 1); }); // If the current Assignment Expression is a mapped callback parameter if (amdclean.callbackParameterMap[assignmentName]) { node.right = (function() { // If aggressive optimizations are turned on, the mapped parameter is used more than once, and there are mapped dependencies to be removed if (options.aggressiveOptimizations === true && mappedParameters.length) { // All of the necessary assignment nodes assignmentNodes = _.map(mappedParameters, function(currentDependency, iterator) { return { 'type': 'AssignmentExpression', 'operator': '=', 'left': { 'type': 'Identifier', 'name': currentDependency.name, 'range': defaultRange, 'loc': defaultLOC }, 'right': (iterator < mappedParameters.length - 1) ? { 'range': defaultRange, 'loc': defaultLOC } : cb, 'range': defaultRange, 'loc': defaultLOC }; }); // Creates an object containing all of the assignment expressions assignments = _.reduce(assignmentNodes, function(result, assignment) { result.right = assignment; return result; }); // The constructed assignment object node return assignmentNodes.length ? assignments : cb; } else { return cb; } }()); return node; } } } }); } // Makes any necessary modules global by appending a global instantiation to the code // eg: window.exampleModule = exampleModule; if (_.isArray(options.globalModules)) { _.each(options.globalModules, function(currentModule) { if (_.isString(currentModule) && currentModule.length) { ast.body.push({ 'type': 'ExpressionStatement', 'expression': { 'type': 'AssignmentExpression', 'operator': '=', 'left': { 'type': 'MemberExpression', 'computed': false, 'object': { 'type': 'Identifier', 'name': 'window', 'range': defaultRange, 'loc': defaultLOC }, 'property': { 'type': 'Identifier', 'name': currentModule, 'range': defaultRange, 'loc': defaultLOC }, 'range': defaultRange, 'loc': defaultLOC }, 'right': { 'type': 'Identifier', 'name': currentModule, 'range': defaultRange, 'loc': defaultLOC }, 'range': defaultRange, 'loc': defaultLOC }, 'range': defaultRange, 'loc': defaultLOC }); } }); } hoistedCallbackParameters = (function() { var obj = {}, callbackParameterMap = amdclean.callbackParameterMap, currentParameterName; _.each(callbackParameterMap, function(mappedParameters) { _.each(mappedParameters, function(currentParameter) { if (currentParameter.count > 1) { currentParameterName = currentParameter.name; obj[currentParameterName] = true; } }); }); return obj; }()); // Hoists all modules and necessary callback parameters hoistedVariables = _.merge(_.cloneDeep(_.reduce(amdclean.storedModules, function(storedModules, key, val) { if (key !== false) { storedModules[val] = true; } return storedModules; }, {})), hoistedCallbackParameters); // Creates variable declarations for each AMD module/callback parameter that needs to be hoisted _.each(hoistedVariables, function(moduleValue, moduleName) { if (!_.contains(options.ignoreModules, moduleName)) { var _initValue = amdclean.exportsModules[moduleName] !== true ? null : { type: 'ObjectExpression', properties: [] }; declarations.push({ 'type': 'VariableDeclarator', 'id': { 'type': 'Identifier', 'name': moduleName, 'range': defaultRange, 'loc': defaultLOC }, 'init': _initValue, 'range': defaultRange, 'loc': defaultLOC }); } }); // Adds a local module variable if a user wants local module information available to them if (_.isObject(options.config) && !_.isEmpty(options.config)) { configAst = (function() { var props = []; _.each(options.config, function(val, key) { var currentModuleConfig = options.config[key]; props.push({ 'type': 'Property', 'key': { 'type': 'Literal', 'value': key }, 'value': { 'type': 'ObjectExpression', 'properties': [{ 'type': 'Property', 'key': { 'type': 'Literal', 'value': 'config' }, 'value': { 'type': 'FunctionExpression', 'id': null, 'params': [], 'defaults': [], 'body': { 'type': 'BlockStatement', 'body': [{ 'type': 'ReturnStatement', 'argument': createAst.call(amdclean, 'var x =' + JSON.stringify(currentModuleConfig)).body[0].declarations[0].init }] } }, 'kind': 'init' }] } }); }); return { 'type': 'VariableDeclarator', 'id': { 'type': 'Identifier', 'name': 'module' }, 'init': { 'type': 'ObjectExpression', 'properties': props } }; }()); declarations.push(configAst); } // If there are declarations, the declarations are preprended to the beginning of the code block if (declarations.length) { ast.body.unshift({ 'type': 'VariableDeclaration', 'declarations': declarations, 'kind': 'var', 'range': defaultRange, 'loc': defaultLOC }); } // Converts the updated AST to a string of code generatedCode = generateCode.call(amdclean, ast); // If there is a wrap option specified if (_.isObject(options.wrap)) { if (_.isString(options.wrap.start) && options.wrap.start.length) { generatedCode = options.wrap.start + generatedCode; } if (_.isString(options.wrap.end) && options.wrap.end.length) { generatedCode = generatedCode + options.wrap.end; } } return generatedCode; }; });