Skip to content

🌳πŸͺš An experimental tree shaker for JS based on Oxc (WIP)

Notifications You must be signed in to change notification settings

KermanX/tree-shaker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Experimental Tree Shaker

[WIP] This is an experimental tree shaker (code size optimizer) for JavaScript based on the Oxc compiler.

Try online

Features

  • Simulate the runtime behavior of the code, instead of applying rules.
  • Single AST pass - Analyzer as much information as possible.
  • As accurate as possible. test262 is used for testing.
  • May not be the fastest. (But I will try my best)

Examples

Constant Folding

This is a simple example, but it's a good start.

Input Output
export function f() {
  function g(a) {
    if (a) console.log("effect");
    else return "str";
  }
  let { ["x"]: y = 1 } = { x: g("") ? undefined : g(1) };
  return y;
}
export function f() {
  return 1;
}

Remove Dead Code

The core of tree-shaking. The execution is simulated to know which code is useless.

And don't worry about the && true in the output, minifier will remove it.

Input Output
function f(value) {
  if (value) console.log(`${value} is truthy`);
}
f(1);
f(0);

function g(t1, t2) {
  if (t1 && t2) console.log(2);
  else if (t1 || t2) console.log(1);
  else console.log(0);
}
g(true, true);
g(false, false);
function f() {
  {
    console.log("1 is truthy");
  }
}
f();

function g(t1) {
  if (t1 && true) console.log(2);
  else {
    console.log(0);
  }
}
g(true);
g(false);

Object Property Mangling

This is beyond the scope of tree-shaking, we need a new name for this project πŸ˜‡.

Input Output
export function main() {
  const obj = {
    foo: v1,
    [t1 ? "bar" : "baz"]: v2,
  };
  const key = t2 ? "foo" : "bar";
  console.log(obj[key]);
}
export function main() {
  const obj = {
    a: v1,
    [t1 ? "b" : "c"]: v2,
  };
  const key = t2 ? "a" : "b";
  console.log(obj[key]);
}

JSX

createElement also works, if it is directly imported from react.

Input Output
function Name({ name, info }) {
  return (
    <span>
      {name}
      {info && <sub> Lots of things never rendered </sub>}
    </span>
  );
}
export function Main() {
  return <Name name={"world"} />;
}
function Name() {
  return (
    <span>
      {"world"}
      {}
    </span>
  );
}
export function Main() {
  return <Name />;
}

React.js

We also have special handling for some React.js APIs. For example, React Context, memo, forwardRef, useMemo, etc.

Input Output
import React from "react";
const MyContext = React.createContext("default");
function Inner() {
  const value = React.useContext(MyContext);
  return <div>{value}</div>;
}
export function main() {
  return (
    <MyContext.Provider value="hello">
      <Inner />
    </MyContext.Provider>
  );
}
import React from "react";
const MyContext = React.createContext();
function Inner() {
  return <div>{"hello"}</div>;
}
export function main() {
  return (
    <MyContext.Provider>
      <Inner />
    </MyContext.Provider>
  );
}

Comparison

  • Rollup: Rollup tree-shakes the code in a multi-module context, while this project is focused on a single module. For some cases, this project can remove 10% more code than Rollup.
  • Closure Compiler: Closure Compiler can be considered as a tree shaker + minifier, while this project is only a tree shaker (for the minifier, we have oxc_minifier). Theoretically, we can shake more than Closure Compiler, but we cannot compare them directly because we don't have a equivalent minifier. Also, it's written in Java, which is hard to be integrated into the JS ecosystem.
  • swc: swc can also be considered as a tree shaker + minifier. TBH, currently swc is much faster and more complete. It is rule-based, which is a different approach from this project. It's also not compatible with the Oxc project, thus a new tree shaker is needed.

Todo

  • Performance!
  • Type narrowing
  • Pure annotation
  • Complete JS Builtins metadata
  • Test against fixtures from other tree shakers like Rollup
  • Rollup-like try-scope optimization/de-optimization
  • Reuse code with oxc_minifier for JS computation logics

Basic Approach

  1. Parse the code via oxc_parser.
  2. Build the semantic information via oxc_semantic.
  3. Tree shake the code.
    • Emulate the runtime behavior of the code. (Control flow, Side effects, ...)
    • Analyze the possible runtime values of the variables.
    • Remove the dead code.
  4. Minify the code via oxc_minifier. (Optional)

Concepts

  • Entity: Represents the analyzed information of a JS value.
  • Consumable: Entity or AST Nodes or some other things that the runtime value of Entity depends on.
  • Scopes:
    • Call Scope: Function call scope.
    • Cf Scope: Control flow scope.
    • Variable Scope: Variable scope.
    • Try Scope: Try statement or function.