Skip to content

Commit

Permalink
Reducing the size of noder by replacing some constants by their value.
Browse files Browse the repository at this point in the history
  • Loading branch information
divdavem committed Jul 15, 2013
1 parent 3b98e49 commit b1c96ca
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 3 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -13,6 +13,7 @@
"grunt-cli": "0.1.7",
"gzip-js": "0.3.1",
"grunt-contrib-uglify": "0.2.0",
"uglify-js": "2.2.5",
"grunt-contrib-jshint": "0.3.0",
"grunt-contrib-copy": "0.4.1",
"grunt-contrib-clean": "0.4.1",
Expand Down
18 changes: 15 additions & 3 deletions tasks/buildNoder.js
Expand Up @@ -17,6 +17,8 @@ module.exports = function(grunt) {
var path = require('path');
var fileUtils = grunt.file;
var log = grunt.log;
var UglifyJS = require("uglify-js");
var replaceConstants = require("./helpers/replaceConstants");

// This regexp only matches calls to require for local modules (starting with './' or '../' or 'noder/')
var requireRegexp = /(^|[^\s.])\s*\brequire\s*\(\s*["']((\.\.?|noder-js)\/[^"']+)["']\s*\)|(\/\*[\s\S]*?\*\/)|((?:[^\\]|^)\/\/.*?([\n\r]|$))/g;
Expand All @@ -28,12 +30,12 @@ module.exports = function(grunt) {
var src = path.join(__dirname, '../src/');
var envConfig = {
"node": {
header: "/*jshint undef:true, node:true*/\nmodule.exports = (function(){\n'use strict';\n",
header: "/*jshint undef:true, node:true, -W069*/\nmodule.exports = (function(){\n'use strict';\n",
footer: "\n})();",
modules: [path.join(src, 'modules/**/*.js'), path.join(src, 'node-modules/**/*.js')]
},
"browser": {
header: "/*jshint undef:true*/\n(function(global,callEval){\n'use strict';\n",
header: "/*jshint undef:true, -W069*/\n(function(global,callEval){\n'use strict';\n",
footer: "\n})((function(){return this;})(),function(c){\n/*jshint evil:true */\neval(c);\n});",
modules: [path.join(src, 'modules/**/*.js'), path.join(src, 'browser-modules/**/*.js')]
}
Expand Down Expand Up @@ -134,7 +136,17 @@ module.exports = function(grunt) {
try {
composeFile();
checkUnusedModules();
fileUtils.write(data.dest, output.join(''));

var ast = UglifyJS.parse(output.join(''));
ast.figure_out_scope();
replaceConstants(ast);
output = ast.print_to_string({
comments: true,
beautify: true,
ascii_only: true
});

fileUtils.write(data.dest, output);
} catch (e) {
log.error(e);
return false;
Expand Down
34 changes: 34 additions & 0 deletions tasks/helpers/cloneNode.js
@@ -0,0 +1,34 @@
/*
* Copyright 2013 Amadeus s.a.s.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

var UglifyJS = require("uglify-js");

var copyToken = function(token) {
token = token || {};
return new UglifyJS.AST_Token({
comments_before: (token.comments_before || []).slice(0)
});
};

module.exports = function(node) {
var transformer = new UglifyJS.TreeTransformer(function(node, descend) {
var nodeClone = node.clone();
nodeClone.start = copyToken(node.start);
nodeClone.end = copyToken(node.end);
descend(nodeClone, this);
return nodeClone;
});
return node.transform(transformer);
};
87 changes: 87 additions & 0 deletions tasks/helpers/replaceConstants.js
@@ -0,0 +1,87 @@
/*
* Copyright 2013 Amadeus s.a.s.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

var UglifyJS = require("uglify-js");
var replaceNode = require("./replaceNode");
var cloneNode = require("./cloneNode");

var constantTypes = [UglifyJS.AST_Number, UglifyJS.AST_String, UglifyJS.AST_Boolean];
var isAcceptedConstantType = function(node) {
return constantTypes.some(function(type) {
return node instanceof type;
});
};

module.exports = function(ast) {
var extraInfoNodeName = "replaceConstantsInfo" + (new Date()).getTime();
var symbols = [];
var walker = new UglifyJS.TreeWalker(function(node) {
if (node instanceof UglifyJS.AST_Symbol && node.thedef) {
var thedef = node.thedef;
var extraInfo = thedef[extraInfoNodeName];
if (!extraInfo) {
thedef[extraInfoNodeName] = extraInfo = {
name: thedef.name,
constant: true, // will be changed to false if the variable is assigned a value
usages: []
};
symbols.push(extraInfo);
}
var parent = walker.parent();
if ((parent instanceof UglifyJS.AST_Assign && parent.left === node) || parent instanceof UglifyJS.AST_Unary) {
extraInfo.constant = false;
}
if (parent instanceof UglifyJS.AST_VarDef && parent.name === node) {
if (extraInfo.definition) {
// 2 different definitions
extraInfo.constant = false;
}
extraInfo.definition = {
varDef: parent,
varList: walker.parent(1),
varListParent: walker.parent(2)
};
extraInfo.value = parent.value;
} else {
extraInfo.usages.push({
node: node,
parent: parent
});
}
}
});
ast.walk(walker);
symbols.forEach(function(extraInfo) {
if (extraInfo.constant && isAcceptedConstantType(extraInfo.value)) {
var originalNode = extraInfo.value;
originalNode.start = new UglifyJS.AST_Token({
comments_before: [{
type: "comment2",
value: extraInfo.name
}
]
});
extraInfo.usages.forEach(function(usage) {
var newNode = cloneNode(extraInfo.value);
replaceNode(usage.node, usage.parent, newNode);
});
if (extraInfo.definition.varList.definitions.length > 1) {
replaceNode(extraInfo.definition.varDef, extraInfo.definition.varList, null);
} else {
replaceNode(extraInfo.definition.varList, extraInfo.definition.varListParent, null);
}
}
});
};
64 changes: 64 additions & 0 deletions tasks/helpers/replaceNode.js
@@ -0,0 +1,64 @@
/*
* Copyright 2013 Amadeus s.a.s.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// This file contains a utility method to replace a node by another in an UglifyJS2 AST.

var replaceNodeInArray = function(array, oldNode, newNode) {
for (var i = 0, l = array.length; i < l; i++) {
if (array[i] === oldNode) {
if (newNode) {
array[i] = newNode;
} else {
array.splice(i, 1);
}
return true;
}
}
return false;
};

var replaceNodeInProperties = function(parent, oldNode, newNode) {
var properties = parent.CTOR.PROPS;
for (var i = 0, l = properties.length; i < l; i++) {
var curProperty = properties[i];
var curValue = parent[curProperty];
if (curValue === oldNode) {
if (newNode) {
parent[curProperty] = newNode;
} else {
delete parent[curProperty];
}
return true;
} else if (Array.isArray(curValue)) {
if (replaceNodeInArray(curValue, oldNode, newNode)) {
return true;
}
}
}
return false;
};

module.exports = function(node, parent, newNode) {
if (newNode && !newNode.start) {
// this is needed because of a bug in uglify-js if start is not defined on some specific nodes
newNode.start = {
comments_before: []
};
}
if (replaceNodeInProperties(parent, node, newNode)) {
return;
}
throw new Error("Internal error: unable to find the node to replace");
};

0 comments on commit b1c96ca

Please sign in to comment.