{"id":18315773,"url":"https://github.com/KermanX/tree-shaker","last_synced_at":"2025-04-05T20:32:42.631Z","repository":{"id":253755342,"uuid":"844428182","full_name":"kermanx/tree-shaker","owner":"kermanx","description":"🌳🪚 An experimental tree shaker for JS based on Oxc (WIP)","archived":false,"fork":false,"pushed_at":"2025-04-03T09:27:33.000Z","size":2928,"stargazers_count":342,"open_issues_count":12,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-03T09:30:44.350Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://kermanx.com/tree-shaker/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kermanx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-19T08:36:34.000Z","updated_at":"2025-04-03T09:27:36.000Z","dependencies_parsed_at":"2024-08-19T10:15:44.173Z","dependency_job_id":"38950324-08e9-4a99-9cfa-5242b8912a51","html_url":"https://github.com/kermanx/tree-shaker","commit_stats":{"total_commits":957,"total_committers":3,"mean_commits":319.0,"dds":0.02612330198537094,"last_synced_commit":"2f454c18a9c1743934100f3eb0b61304a0fd697a"},"previous_names":["kermanx/tree-shaker-prototype","kermanx/tree-shaker"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kermanx%2Ftree-shaker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kermanx%2Ftree-shaker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kermanx%2Ftree-shaker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kermanx%2Ftree-shaker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kermanx","download_url":"https://codeload.github.com/kermanx/tree-shaker/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247399818,"owners_count":20932875,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-05T17:01:06.391Z","updated_at":"2025-04-05T20:32:42.619Z","avatar_url":"https://github.com/kermanx.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# Experimental Tree Shaker\n\n\\[WIP\\] This is an experimental tree shaker (code size optimizer) for JavaScript based on [the Oxc compiler](https://oxc.rs).\n\n[**Try online**](https://kermanx.github.io/tree-shaker/) | [**Run locally**](#run-locally)\n\n## Features\n\n- Simulate the runtime behavior of the code, instead of applying rules.\n- Single AST pass - Analyzer as much information as possible.\n- As accurate as possible. [test262](https://github.com/tc39/test262) is used for testing.\n- May not be the fastest. (But I will try my best)\n\n## Examples\n\n### Constant Folding\n\n\u003e This is a simple example, but it's a good start.\n\n\u003ctable\u003e\u003ctbody\u003e\u003ctr\u003e\u003ctd width=\"500px\"\u003e Input \u003c/td\u003e\u003ctd width=\"500px\"\u003e Output \u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n```js\nexport function f() {\n  function g(a) {\n    if (a) console.log(\"effect\");\n    else return \"str\";\n  }\n  let { [\"x\"]: y = 1 } = { x: g(\"\") ? undefined : g(1) };\n  return y;\n}\n```\n\n\u003c/td\u003e\u003ctd valign=\"top\"\u003e\n\n```js\nexport function f() {\n  return 1;\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n### Remove Dead Code\n\n\u003e The core of tree-shaking. The execution is simulated to know which code is useless.\n\u003e\n\u003e And don't worry about the `\u0026\u0026 true` in the output, minifier will remove it.\n\n\u003ctable\u003e\u003ctbody\u003e\u003ctr\u003e\u003ctd width=\"500px\"\u003e Input \u003c/td\u003e\u003ctd width=\"500px\"\u003e Output \u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n```js\nfunction f(value) {\n  if (value) console.log(`${value} is truthy`);\n}\nf(1);\nf(0);\n\nfunction g(t1, t2) {\n  if (t1 \u0026\u0026 t2) console.log(2);\n  else if (t1 || t2) console.log(1);\n  else console.log(0);\n}\ng(true, true);\ng(false, false);\n```\n\n\u003c/td\u003e\u003ctd valign=\"top\"\u003e\n\n```js\nfunction f() {\n  {\n    console.log(\"1 is truthy\");\n  }\n}\nf();\n\nfunction g(t1) {\n  if (t1 \u0026\u0026 true) console.log(2);\n  else {\n    console.log(0);\n  }\n}\ng(true);\ng(false);\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n### Object Property Mangling\n\n\u003e This is beyond the scope of tree-shaking, we need a new name for this project 😇.\n\n\u003ctable\u003e\u003ctbody\u003e\u003ctr\u003e\u003ctd width=\"500px\"\u003e Input \u003c/td\u003e\u003ctd width=\"500px\"\u003e Output \u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n```js\nexport function main() {\n  const obj = {\n    foo: v1,\n    [t1 ? \"bar\" : \"baz\"]: v2,\n  };\n  const key = t2 ? \"foo\" : \"bar\";\n  console.log(obj[key]);\n}\n```\n\n\u003c/td\u003e\u003ctd valign=\"top\"\u003e\n\n```js\nexport function main() {\n  const obj = {\n    a: v1,\n    [t1 ? \"b\" : \"c\"]: v2,\n  };\n  const key = t2 ? \"a\" : \"b\";\n  console.log(obj[key]);\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n### Class Tree Shaking\n\n\u003e One of the hardest but the coolest.\n\n\u003ctable\u003e\u003ctbody\u003e\u003ctr\u003e\u003ctd width=\"500px\"\u003e Input \u003c/td\u003e\u003ctd width=\"500px\"\u003e Output \u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n```js\nclass A {\n  method(x) {\n    console.log(\"A\", x);\n  }\n  static static_prop = unknown;\n}\nclass B extends A {\n  method(x) {\n    console.log(\"B\", x);\n  }\n  unused() {\n    console.log(\"unused\");\n  }\n}\nnew B().method(A.static_prop);\n```\n\n\u003c/td\u003e\u003ctd valign=\"top\"\u003e\n\n```js\nclass A {\n  static a = unknown;\n}\nclass B extends A {\n  a(x) {\n    console.log(\"B\", x);\n  }\n}\nnew B().a(A.a);\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n### JSX\n\n\u003e `createElement` also works, if it is directly imported from `react`.\n\n\u003ctable\u003e\u003ctbody\u003e\u003ctr\u003e\u003ctd width=\"500px\"\u003e Input \u003c/td\u003e\u003ctd width=\"500px\"\u003e Output \u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n```jsx\nfunction Name({ name, info }) {\n  return (\n    \u003cspan\u003e\n      {name}\n      {info \u0026\u0026 \u003csub\u003e Lots of things never rendered \u003c/sub\u003e}\n    \u003c/span\u003e\n  );\n}\nexport function Main() {\n  return \u003cName name={\"world\"} /\u003e;\n}\n```\n\n\u003c/td\u003e\u003ctd valign=\"top\"\u003e\n\n```jsx\nfunction Name() {\n  return (\n    \u003cspan\u003e\n      {\"world\"}\n      {}\n    \u003c/span\u003e\n  );\n}\nexport function Main() {\n  return \u003cName /\u003e;\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n### React.js\n\n\u003e We also have special handling for some React.js APIs. For example, React Context, `memo`, `forwardRef`, `useMemo`, etc.\n\n\u003ctable\u003e\u003ctbody\u003e\u003ctr\u003e\u003ctd width=\"500px\"\u003e Input \u003c/td\u003e\u003ctd width=\"500px\"\u003e Output \u003c/td\u003e\u003c/tr\u003e\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\n\n```jsx\nimport React from \"react\";\nconst MyContext = React.createContext(\"default\");\nfunction Inner() {\n  const value = React.useContext(MyContext);\n  return \u003cdiv\u003e{value}\u003c/div\u003e;\n}\nexport function main() {\n  return (\n    \u003cMyContext.Provider value=\"hello\"\u003e\n      \u003cInner /\u003e\n    \u003c/MyContext.Provider\u003e\n  );\n}\n```\n\n\u003c/td\u003e\u003ctd valign=\"top\"\u003e\n\n```jsx\nimport React from \"react\";\nconst MyContext = React.createContext();\nfunction Inner() {\n  return \u003cdiv\u003e{\"hello\"}\u003c/div\u003e;\n}\nexport function main() {\n  return (\n    \u003cMyContext.Provider\u003e\n      \u003cInner /\u003e\n    \u003c/MyContext.Provider\u003e\n  );\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\n## Comparison\n\n- **Rollup**: Rollup tree-shakes the code in a multi-module context, and it has information about the side effects of the modules. This project does a more fine-grained tree-shaking, and it can be used as a post-processor for Rollup, and is expected to produce smaller code.\n- **Closure Compiler**/**swc**: they support both minification and dead code elimination, while this project is focused on tree-shaking (difference below). You can expect a size reduction when using tree-shaker on their output, and vice versa.\n\n### What's Tree Shaking?\n\nHere is a simple comparison:\n\n- Minification: Removing whitespace, renaming variables, syntax-level optimizations, etc.\n- Dead code elimination: Removing code that is never executed, by using a set of rules, for example, \"`if(false) { ... }` can be removed\".\n- Tree shaking: Removing code that is never executed, by simulating the runtime behavior of the code. For example, \"`if (x) { ... }` can only be preserved if `...` is reachable and has side effects\".\n\n## Todo\n\n- Performance!\n- Type narrowing\n- Pure annotation\n- Complete JS Builtins metadata\n- Test against fixtures from other tree shakers like Rollup\n- Rollup-like try-scope optimization/de-optimization\n- Reuse code with oxc_minifier for JS computation logics\n\n## Basic Approach\n\n1. Parse the code via `oxc_parser`.\n2. Build the semantic information via `oxc_semantic`.\n3. Tree shake the code.\n   - Emulate the runtime behavior of the code. (Control flow, Side effects, ...)\n   - Analyze the possible runtime values of the variables.\n   - Remove the dead code.\n4. Minify the code via `oxc_minifier`. (Optional)\n\n### Concepts\n\n- `Entity`: Represents the analyzed information of a JS value.\n- `Consumable`: Entity or AST Nodes or some other things that the runtime value of `Entity` depends on.\n- Scopes:\n  - Call Scope: Function call scope.\n  - Cf Scope: Control flow scope.\n  - Variable Scope: Variable scope.\n  - Try Scope: Try statement or function.\n\n## Run Locally\n\n1. Clone the repo.\n2. Run `cargo run ./path/to/bundled.js`\n3. The output files will be in `./output/...`\n4. (Optional) You can open the optimized file in VSCode and run the Open Diff command from the \"Auto Diff Opener\" extension to see the diff.\n\nNote that Rollup is recommended for bundling, because it has information about the side effects of the modules, and it produces much cleaner bundles.\n\nIf you encounter any problems, please open an issue with the minimal reproduction. Thanks!\n\n## Soundiness Statement\n\nThis project has been done in the spirit of soundiness. When building practical program analyses, it is often necessary to cut corners. In order to be open about language features that we do not support or support only partially, we are attaching this soundiness statement.\n\nOur analysis does not have a fully sound handling of the following features:\n\n- eval\n- implicit conversions (==, valueOf, toString)\n- exceptions and flow related to that\n- prototype semantics\n\nWe have determined that the unsoundness in our handling of these features has minimal effect on analysis output and the validity of our experimental evaluation. To the best of our knowledge, our analysis has a sound handling of all language features other than those listed above.\n\nThis statement has been produced with the Soundiness Statement Generator from http://soundiness.org.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKermanX%2Ftree-shaker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FKermanX%2Ftree-shaker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKermanX%2Ftree-shaker/lists"}