Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reuse removed vars in mangler #395

Merged
merged 10 commits into from May 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions packages/babel-plugin-minify-mangle-names/src/bfs-traverse.js
@@ -0,0 +1,48 @@
"use strict";

module.exports = function bfsTraverseCreator({ types: t, traverse }) {
function getFields(path) {
return t.VISITOR_KEYS[path.type];
}

return function bfsTraverse(path, _visitor) {
if (!path.node) {
throw new Error("Not a valid path");
}
const visitor = traverse.explode(_visitor);

const queue = [path];
let current;

while (queue.length > 0) {
current = queue.shift();

// call
if (
visitor &&
visitor[current.type] &&
Array.isArray(visitor[current.type].enter)
) {
const fns = visitor[current.type].enter;
for (const fn of fns) {
if (typeof fn === "function") fn(current);
}
}

const fields = getFields(current);

for (const field of fields) {
const child = current.get(field);

if (Array.isArray(child)) {
// visit container left to right
for (const c of child) {
if (c.node) queue.push(c);
}
} else {
if (child.node) queue.push(child);
}
}
}
};
};
53 changes: 53 additions & 0 deletions packages/babel-plugin-minify-mangle-names/src/charset.js
@@ -0,0 +1,53 @@
"use strict";

const CHARSET = ("abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ$_").split("");

module.exports = class Charset {
constructor(shouldConsider) {
this.shouldConsider = shouldConsider;
this.chars = CHARSET.slice();
this.frequency = {};
this.chars.forEach(c => {
this.frequency[c] = 0;
});
this.finalized = false;
}

consider(str) {
if (!this.shouldConsider) {
return;
}

str.split("").forEach(c => {
if (this.frequency[c] != null) {
this.frequency[c]++;
}
});
}

sort() {
if (this.shouldConsider) {
this.chars = this.chars.sort(
(a, b) => this.frequency[b] - this.frequency[a]
);
}

this.finalized = true;
}

getIdentifier(num) {
if (!this.finalized) {
throw new Error("Should sort first");
}

let ret = "";
num++;
do {
num--;
ret += this.chars[num % this.chars.length];
num = Math.floor(num / this.chars.length);
} while (num > 0);
return ret;
}
};
28 changes: 28 additions & 0 deletions packages/babel-plugin-minify-mangle-names/src/counted-set.js
@@ -0,0 +1,28 @@
// Set that counts
module.exports = class CountedSet {
constructor() {
// because you can't simply extend Builtins yet
this.map = new Map();
}
keys() {
return [...this.map.keys()];
}
has(value) {
return this.map.has(value);
}
add(value) {
if (!this.has(value)) {
this.map.set(value, 0);
}
this.map.set(value, this.map.get(value) + 1);
}
delete(value) {
if (!this.has(value)) return;
const count = this.map.get(value);
if (count <= 1) {
this.map.delete(value);
} else {
this.map.set(value, count - 1);
}
}
};
@@ -0,0 +1,55 @@
// this fixes a bug where converting let to var
// doesn't change the binding's scope to function scope
// https://github.com/babel/babel/issues/4818
module.exports = function(mangler) {
mangler.program.traverse({
VariableDeclaration(path) {
if (path.node.kind !== "var") {
return;
}
const fnScope = path.scope.getFunctionParent();
const bindingIds = path.getOuterBindingIdentifierPaths();

for (const name in bindingIds) {
const binding = path.scope.getBinding(name);

// var isn't hoisted to fnScope
if (binding.scope !== fnScope) {
const existingBinding = fnScope.bindings[name];
// make sure we are clear that the fnScope doesn't already have
// an existing binding
if (!existingBinding) {
// move binding to the function scope

// update our scopeTracker first before
// we mutate the scope
mangler.scopeTracker.moveBinding(binding, fnScope);

fnScope.bindings[name] = binding;
binding.scope = fnScope;
delete binding.scope.bindings[name];
} else {
// we need a new binding that's valid in both the scopes
// binding.scope and fnScope
const newName = fnScope.generateUid(
binding.scope.generateUid(name)
);

// rename binding in the original scope
mangler.rename(binding.scope, binding, name, newName);

// move binding to fnScope as newName

// update our scopeTracker first before
// we mutate the scope
mangler.scopeTracker.moveBinding(binding, fnScope);

fnScope.bindings[newName] = binding;
binding.scope = fnScope;
delete binding.scope.bindings[newName];
}
}
}
}
});
};