Skip to content

Instantly share code, notes, and snippets.

@cameronism
Created December 13, 2013 00:12
Show Gist options
  • Select an option

  • Save cameronism/7937945 to your computer and use it in GitHub Desktop.

Select an option

Save cameronism/7937945 to your computer and use it in GitHub Desktop.
Uglify files and inline keys in gettext calls
var UglifyJS = require("uglify-js");
var fs = require('fs');
var defineName = 'define',
getTextModule = 'shared/gettext',
currentGetTextName,
stringDefinitions,
placeholders,
stringKeys,
moduleName;
function getStringNode(node) {
if (node instanceof UglifyJS.AST_String) {
return node;
}
var value = node.name && stringDefinitions[node.name];
if (value) {
return value;
}
console.error(JSON.stringify(node, null, 2));
throw new Error("Unknown string type");
}
function resetState() {
moduleName = currentGetTextName = null;
stringDefinitions = {};
placeholders = [];
stringKeys = [];
}
function getPlaceholderString(node) {
var placeholder = new UglifyJS.AST_String({ value: 'KEY PLACEHOLDER' });
placeholders.push(placeholder);
stringKeys.push({ node: node, module: moduleName });
return placeholder;
}
function before(node, descend) {
if (node instanceof UglifyJS.AST_Call &&
node.expression.name == defineName &&
node.args.length >= 2) {
var defineStarted = false,
previousModuleName = moduleName,
localModuleName,
previousGetTextName = currentGetTextName,
dependencies,
names;
if (node.args.length >= 3 &&
node.args[0] instanceof UglifyJS.AST_String &&
node.args[1] instanceof UglifyJS.AST_Array &&
node.args[2] instanceof UglifyJS.AST_Function) {
localModuleName = node.args[0].value;
dependencies = node.args[1].elements;
names = node.args[2].argnames;
} else if (node.args[0] instanceof UglifyJS.AST_Array &&
node.args[1] instanceof UglifyJS.AST_Function) {
dependencies = node.args[0].elements;
names = node.args[1].argnames;
}
if (dependencies && names) {
for (var i = 0; i < dependencies.length; i++) {
if (dependencies[i] instanceof UglifyJS.AST_String &&
dependencies[i].value.toLowerCase() == getTextModule &&
names.length > i) {
currentGetTextName = names[i].name;
defineStarted = true;
break;
}
}
}
if (defineStarted) {
if (localModuleName) {
moduleName = localModuleName;
}
descend(node, this);
currentGetTextName = previousGetTextName;
if (localModuleName) {
moduleName = previousModuleName;
}
return node;
}
}
else if (node instanceof UglifyJS.AST_VarDef &&
node.value &&
node.value instanceof UglifyJS.AST_String) {
// TODO support shadowing
stringDefinitions[node.name.name] = node.value;
descend(node, this);
return node;
}
}
function after(node) {
if (node instanceof UglifyJS.AST_Call &&
node.args.length > 0 &&
node.expression.name &&
node.expression.name == currentGetTextName) {
var stringNode = getStringNode(node.args[0]),
placeholder = getPlaceholderString(stringNode);
node.args.unshift(placeholder);
return node;
}
}
var getTextWalker = new UglifyJS.TreeTransformer(before, after);
function processFileSync(file, keyCreator, astPrintOptions) {
resetState();
var code = fs.readFileSync(file);
var ast = UglifyJS.parse(String(code), { filename: file, toplevel: null });
ast = ast.transform(getTextWalker);
var keys = keyCreator(file, stringKeys);
placeholders.forEach(function(placeholder, ix) {
placeholder.value = keys[ix];
});
// compressor needs figure_out_scope too
ast.figure_out_scope();
var compressor = UglifyJS.Compressor({ pure_getters: true })
ast = ast.transform(compressor);
// need to figure out scope again so mangler works optimally
ast.figure_out_scope();
ast.compute_char_frequency();
ast.mangle_names();
return ast.print_to_string(astPrintOptions || {});
}
var argv,
exampleKeyCreator;
// process files specified on command line if run directly
if (require.main === module) {
argv = process.argv.slice(2);
exampleKeyCreator = function(filename, nodes) {
return nodes.map(function(node) {
return 'success - ' + (node.module || filename) + ' - ' + node.node.value;
});
}
if (argv.length == 0) {
argv = ['something.js'];
}
argv.forEach(function(file) {
console.log('// ' + file);
console.log(processFileSync(file, exampleKeyCreator, { beautify: true }));
});
}
module.exports.processFileSync = processFileSync;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment