Skip to content

Latest commit

Β 

History

History
758 lines (590 loc) Β· 16 KB

README.md

File metadata and controls

758 lines (590 loc) Β· 16 KB

@wakaru/unminify

This package offers a comprehensive set of transformation rules designed to unminify and enhance the readability of code.

It covers most of the patterns that are used by the following tools:

Table of Contents

Readability

un-boolean

Converts minified boolean to simple true/false.

- !0
+ true

- !1
+ false

un-undefined

Converts void 0 to undefined.

- if(input === void 0) {}
+ if(input === undefined) {}

un-infinity

Converts 1 / 0 to Infinity.

- 1 / 0
+ Infinity
- -1 / 0
+ -Infinity

un-numeric-literal

Converts numeric literal to its decimal representation.
A comment will be added to indicate the original value.

- 1e3
+ 1000 /* 1e3 */

- 0b101010
+ 42 /* 0b101010 */

- 0x123
+ 291 /* 0x123 */

un-typeof

Converts minified typeof to its long form.

- typeof x > "u"
+ typeof x === "undefined"
- typeof x < "u"
+ typeof x !== "undefined"

un-sequence-expression

Separate sequence expressions into multiple statements.

- a(), b(), c()
+ a()
+ b()
+ c()

- return a(), b()
+ a()
+ return b()

- while (a(), b(), c++ > 0) {}
+ a()
+ b()
+ while (c++ > 0) {}

un-variable-merging

Separate variable declarators into multiple statements.

- var a = 1, b = true, c = func(d):
+ var a = 1;
+ var b = true;
+ var c = func(d);

Separate variable declarators that are not used in for statements.

- for (var i = 0, j = 0, k = 0; j < 10; k++) {}
+ var i = 0
+ for (var j = 0, k = 0; j < 10; k++) {}

un-assignment-expression

Separate chained assignment into multiple statements.

- a = b = c = 1
+ a = 1
+ b = 1
+ c = 1

un-bracket-notation

Simplify bracket notation.

- obj['prop']
+ obj.prop

- obj['var']
+ obj['var']

un-while-loop

Converts for loop without init and update to while loops.

- for (;;) {}
+ while (true) {}

- for (; i < 10;) {
-  console.log(i);
- }
+ while (i < 10) {
+   console.log(i);
+ }

un-flip-comparisons

Flips comparisons that are in the form of "literal comes first" to "literal comes second".

- if ("dark" === theme) {}
+ if (theme === "dark") {}

- while (10 < count) {}
+ while (count > 10) {}

un-conditionals

Unwraps nested ternary expressions and binary expression into if-else statements or switch statements.

If-Else

- a ? b() : c ? d() : e()
+ if (a) {
+   b();
+ } else if (c) {
+   d();
+ } else {
+   e();
+ }

This rule will try to adopt the Early Exit strategy if possible.

function fn () {
-   return a ? b() : c ? d() : e()
+   if (a) {
+     return b();
+   }
+   if (c) {
+     return d();
+   }
+   return e();
}

Switch

- foo == 'bar' ? bar() : foo == 'baz' ? baz() : foo == 'qux' || foo == 'quux' ? qux() : quux()
+ switch (foo) {
+   case 'bar':
+     bar()
+     break
+   case 'baz':
+     baz()
+     break
+   case 'qux':
+   case 'quux':
+     qux()
+     break
+   default:
+     quux()
+ }

un-return

Simplify the last return statements.

function foo() {
  const a = 1
  if (a) {
    return a;
  }
- return void 0;
}

const bar = () => {
- return void foo();
+ foo();
}

un-indirect-call

Converts indirect call expressions to direct call expressions.

- import s from 'react'
- (0, s.useRef)(0);
+ import { useRef } from 'react'
+ useRef(0);
var s = require('react')
- (0, s.useRef)(0);
+ const { useRef } = s
+ useRef(0);

un-type-constructor (Unsafe)

Restore type constructors from minified code.

- +x;
+ Number(x);

- x + "";
+ String(x);

- [,,,];
+ Array(3);

Unsafe:

  • BigInt: +1n will throw TypeError
  • Symbol: Symbol('foo') + "" will throw TypeError

un-builtin-prototype

Convert function calls on instances of built-in objects to equivalent calls on their prototypes.

- [].splice.apply(a, [1, 2, b, c]);
+ Array.prototype.splice.apply(a, [1, 2, b, c]);

- (function() {}).call.apply(console.log, console, ["foo"]),
+ Function.prototype.call.apply(console.log, console, ["foo"]),

- 0..toFixed.call(Math.PI, 2);
+ Number.prototype.toFixed.call(Math.PI, 2);

- ({}).hasOwnProperty.call(d, "foo");
+ Object.prototype.hasOwnProperty.call(d, "foo");

- /t/.test.call(/foo/, "bar");
+ RegExp.prototype.test.call(/foo/, "bar");

- "".indexOf.call(e, "bar");
+ String.prototype.indexOf.call(e, "bar");

un-import-rename

Rename import specifier back to the original name

-import { foo as a } from 'bar';
- a();
+import { foo } from 'bar';
+ foo();

smart-rename

Rename minified identifiers with heuristic rules.

- const {
-   gql: t,
-   dispatchers: o,
-   listener: i
- } = n;
- o.delete(t, i);
+ const {
+   gql,
+   dispatchers,
+   listener
+ } = n;
+ dispatchers.delete(gql, listener);

React Hooks:

- const th = createContext('light');
+ const ThContext = createContext('light');

- const [e, f] = useState(0);
+ const [e, setE] = useState(0);

- const g = useRef(null);
+ const gRef = useRef(null);

- const [e, f] = o.useReducer(reducer, initialArg, init?);
+ const [eState, fDispatch] = o.useReducer(reducer, initialArg, init?);

- const Z = o.forwardRef((e, t) => { ... })
+ const Z = o.forwardRef((props, ref) => { ... })

un-iife

Improve the readability of code inside IIFE. Useful for short code snippets / userscripts.

Rename the parameters and move the passed-in arguments to the top.

- (function(i, s, o, g, r, a, m) {
-   i['GoogleAnalyticsObject'] = r;
-   i[r].l = 1 * new Date();
-   a = s.createElement(o);
-   m = s.getElementsByTagName(o)[0];
-   a.async = 1;
-   a.src = g;
-   m.parentNode.insertBefore(a, m);
- })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
+ (function(window, document, a, m) {
+   const o = 'script';
+   const g = 'https://www.google-analytics.com/analytics.js';
+   const r = 'ga';
+   window['GoogleAnalyticsObject'] = r;
+   window[r].l = 1 * new Date();
+   a = document.createElement(o);
+   m = document.getElementsByTagName(o)[0];
+   a.async = 1;
+   a.src = g;
+   m.parentNode.insertBefore(a, m);
+ })(window, document);

Syntax Upgrade

un-template-literal

Restore template literal syntax from string concatenation.

- "the ".concat(first, " take the ").concat(second, " and ").concat(third);
+ `the ${first} take the ${second} and ${third}`

un-parameter

Restore parameters. Support normal parameters and default parameters.

- function foo() {
-   var a = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "foo";
-   var b = arguments.length > 1 ? arguments[1] : undefined;
-   if (c === void 0) c = "bar";
- }
+ function foo(a = "foo", b, c = "bar") {}
- function foo() {
-   console.log(arguments);
- }
+ function foo(...args) {
+   console.log(args);
+ }

un-argument-spread

Transform fn.apply calls to spread operator.

- fn.apply(void 0, arr);
+ fn(...arr);
- obj.fn.apply(obj, arr);
+ obj.fn(...arr);

un-enum

Restore TypeScript enum syntax.

- var Direction;
- (function (Direction) {
-   Direction["Up"] = "UP";
-   Direction[Direction["Down"] = 2] = "Down";
- })(Direction || (Direction = {}));
+ var Direction = {
+   Up: "UP",
+   Down: 2,

+   // reverse mapping
+   2: "Down"
+ }

smart-inline

Converts object property accesses and array index accesses to destructuring.

- const t = e.x;
- const n = e.y;
- const r = e.color;
- console.log(t, n, r)
+ const { x, y, color } = e;
+ console.log(x, y, color)
- const t = e[0];
- const n = e[1];
- const r = e[2];
- console.log(t, n, r);
+ const [t, n, r] = e;
+ console.log(t, n, r);

Inline reassigned temp variables.

- const a = d;
- const b = a;
- const c = b;
+ const c = d;

Inline common global variable references.

- const d = document;
- const c = d.createElement('canvas');
+ const c = document.createElement('canvas');

Rename variables based on object property access.

- const t = s.target;
- const p = t.parent;
- const v = p.value;
+ const s_target = s.target;
+ const s_target_parent = s_target.parent;
+ const s_target_parent_value = s_target_parent.value;

un-optional-chaining

Restore optional chaining syntax.
Support output from TypeScript, Babel and SWC.

- (_foo = foo) === null || _foo === void 0 ? void 0 : _foo.bar;
+ foo?.bar;

- (_foo = foo) === null || _foo === void 0 ? void 0 : (_foo_bar = _foo.bar) === null || _foo_bar === void 0 ? void 0 : _foo_bar.baz;
+ foo?.bar?.baz;

un-nullish-coalescing

Restore nullish coalescing syntax.
Support output from TypeScript, Babel and SWC.

- (_ref = foo !== null && foo !== void 0 ? foo : bar) !== null && _ref !== void 0 ? _ref : "quz";
+ foo ?? bar ?? "quz";

un-esm (Unsafe)

Converts CommonJS's require and module.exports to ES6's import and export.

- const foo = require('foo')
- var { bar } = require('bar')
- var baz = require('baz').baz
- require('side-effect')
+ import foo from 'foo'
+ import { bar } from 'bar'
+ import { baz } from 'baz'
+ import 'side-effect'
- module.exports.foo = 1
- exports.bar = bar
+ export const foo = 1
+ export { bar }
- module.exports.default = foo
+ export default foo

Warning

Please aware that CJS and ESM are not fully compatible, and this transformation is not perfect. We have a comprehensive test suite to ensure the correctness, but there are still some edge cases that are not covered. Feel free to open an issue if you find any bugs.

Limitations:

  • require(dynamic) is not supported as ESM does not support dynamic imports. Convert it to await import() is not appropriate as it require the whole execution context to be async.
  • Some packages require import * as name from 'package' instead of import name from 'package'. We cannot detect this automatically, so you might need to fix it manually.
  • Currently, it won't aware the exports format of other files generated by the unpacker.

un-es6-class

Restore Class definition from the constructor and the prototype.
Currently, this transformation only supports output from TypeScript.

Supported features:

  • constructor
  • instance properties
  • instance methods
  • static methods
  • static properties
  • getters and setters
  • async method (share the same limitations from un-async-await)

Unsupported features:

  • inheritance
  • decorators
  • private flag(#)
- var Foo = (function() {
-   function t(name) {
-     this.name = name;
-     this.age = 18;
-   }
-   t.prototype.hi = function logger() {
-     console.log("Hello", this.name);
-   };
-   t.staticMethod = function staticMethod() {
-     console.log("static");
-   };
-   t.instance = new t("foo");
-   return t;
- })();
+ class Foo {
+   constructor(name) {
+     this.name = name;
+     this.age = 18;
+   }
+   hi() {
+     console.log("Hello", this.name);
+   }
+   static staticMethod() {
+     console.log("static");
+   }
+   static instance = new Foo("foo");
+ }

un-async-await (Experimental) (WIP)

Restore async/await from helper __awaiter and __generator.
Currently, this transformation only supports output from TypeScript.

And it does not handle control flow properly, as it needs graph analysis.

Please aware there are tons of edge cases that are not covered by this rule.

-function func() {
-  return __awaiter(this, void 0, void 0, function () {
-    var result, json;
-    return __generator(this, function (_a) {
-      switch (_a.label) {
-        case 0:
-          console.log('Before sleep');
-          return [4 /*yield*/, sleep(1000)];
-        case 1:
-          _a.sent();
-          return [4 /*yield*/, fetch('')];
-        case 2:
-          result = _a.sent();
-          return [4 /*yield*/, result.json()];
-        case 3:
-          json = _a.sent();
-          return [2 /*return*/, json];
-      }
-    });
-  });
-}
+async function func() {
+  var result, json;
+  console.log('Before sleep');
+  await sleep(1000);
+  result = await fetch('');
+  json = await result.json();
+  return json;
+}

un-jsx

Converts React.createElement and jsxRuntime.jsx back to JSX.

- React.createElement("div", { className: "title" }, "Hello World");
+ <div className="title">Hello World</div>

Built-in pragma: jsx, jsxs, _jsx, _jsxs, jsxDEV, jsxsDEV and h

Pass pragma option to specify the JSX pragma.
Pass pragmaFrag option to specify the JSX fragment pragma.

// pragma: "jsx", pragmaFrag: "Fragment"
- jsx(React.Fragment, null, jsx("span", { className: "title" }, "Hello"), jsx("span", null, "World"));
+ <>
+   <span className="title">Hello</span>
+   <span>World</span>
+ </>

Component name will be guessed from the displayName property automatically.

- var S = /*#__PURE__*/React.createElement("div", null);
- S.displayName = "Foo-Bar";
- var Bar = () => (
-   <div>
-     <S />
-   </div>
- )
+ var FooBar = <div />;
+ FooBar.displayName = "Foo-Bar";
+ var Bar = () => (
+   <div>
+     <FooBar />
+   </div>
+ )

Clean Up

un-esmodule-flag

Removes the __esModule flag from the module.

- Object.defineProperty(exports, "__esModule", { value: true });

un-use-strict

Removes the "use strict" directive.

- "use strict";

Style

prettier

Formats the code with prettier.

Extra

lebab

Lebab transpiles your ES5 code to ES6/ES7. It does exactly the opposite of what Babel does.

We integrated part of rules from Lebab to unminify the code.
By utilizing Lebab, we can save the repetitive work of writing the same transformations ourselves.

TODO

  • un-string-literal to decode printable unicode
  • Terser loops contains several useful patterns
  • let a; a = 1; to let a = 1;
  • Support for Logical Assignment Operators (a ||= b, a &&= b, a ??= b) [ES2021]