8000 GitHub - elixir-volt/oxc_ex: Elixir bindings for the OXC JavaScript toolchain — parse, transform, and minify JS/TS via Rust NIFs · GitHub
[go: up one dir, main page]

Skip to content

elixir-volt/oxc_ex

Repository files navigation

OXC

Elixir bindings for the OXC JavaScript toolchain via Rust NIFs.

Parse, transform, and minify JavaScript/TypeScript at native speed.

Features

  • Parse JS/TS/JSX/TSX into ESTree AST (maps with atom keys)
  • Transform TypeScript → JavaScript, JSX → createElement/jsx calls
  • Minify with dead code elimination, constant folding, and variable mangling
  • Bundle multiple TS/JS modules into a single IIFE with dependency resolution
  • Walk/Collect helpers for AST traversal and node filtering
  • Postwalk with accumulator for AST-based source patching (like Macro.postwalk/3)
  • Patch string — apply byte-offset patches to source (like Sourceror.patch_string/2)
  • Import extraction — fast NIF-level import specifier extraction

Installation

def deps do
  [
    {:oxc, "~> 0.4.0"}
  ]
end

Requires a Rust toolchain (rustup recommended). The NIF compiles automatically on mix compile.

Usage

Parse

{:ok, ast} = OXC.parse("const x = 1 + 2", "test.js")
ast.type
# "Program"

[stmt] = ast.body
stmt.expression
# %{type: "BinaryExpression", operator: "+", left: %{value: 1}, right: %{value: 2}}

File extension determines the dialect — .js, .jsx, .ts, .tsx:

{:ok, ast} = OXC.parse("const x: number = 42", "test.ts")
{:ok, ast} = OXC.parse("<App />", "component.tsx")

Transform

Strip TypeScript types and transform JSX:

{:ok, js} = OXC.transform("const x: number = 42", "test.ts")
# "const x = 42;\n"

{:ok, js} = OXC.transform("<App />", "app.tsx")
# Uses automatic JSX runtime by default

{:ok, js} = OXC.transform("<App />", "app.jsx", jsx: :classic)
# Uses React.createElement

With source maps:

{:ok, %{code: js, sourcemap: map}} = OXC.transform(code, "app.ts", sourcemap: true)

Target specific environments:

{:ok, js} = OXC.transform("const x = a ?? b", "test.js", target: "es2019")
# Nullish coalescing lowered to ternary

Custom JSX import source (Vue, Preact, etc.):

{:ok, js} = OXC.transform("<div />", "app.jsx", import_source: "vue")
# Imports from vue/jsx-runtime instead of react/jsx-runtime

Minify

{:ok, min} = OXC.minify("const x = 1 + 2; console.log(x);", "test.js")
# Constants folded, whitespace removed, variables mangled

{:ok, min} = OXC.minify(code, "test.js", mangle: false)
# Compress without renaming variables

Import Extraction

Fast NIF-level extraction of import specifiers — skips full AST serialization:

{:ok, imports} = OXC.imports("import { ref } from 'vue'\nimport { h } from 'preact'", "test.ts")
# ["vue", "preact"]

Type-only imports are excluded automatically:

{:ok, imports} = OXC.imports("import type { Ref } from 'vue'\nimport { ref } from 'vue'", "test.ts")
# ["vue"]

Validate

Fast syntax check without building an AST:

OXC.valid?("const x = 1", "test.js")
# true

OXC.valid?("const = ;", "bad.js")
# false

AST Traversal

{:ok, ast} = OXC.parse("import a from 'a'; import b from 'b'; const x = 1;", "test.js")

# Walk every node
OXC.walk(ast, fn
  %{type: "Identifier", name: name} -> IO.puts(name)
  _ -> :ok
end)

# Collect specific nodes
imports = OXC.collect(ast, fn
  %{type: "ImportDeclaration"} = node -> {:keep, node}
  _ -> :skip
end)
# [%{type: "ImportDeclaration", source: %{value: "a"}, ...}, ...]

AST Postwalk and Source Patching

Rewrite source code by walking the AST and collecting byte-offset patches:

source = "import { ref } from 'vue'\nimport { h } from 'preact'"
{:ok, ast} = OXC.parse(source, "test.ts")

{_ast, patches} =
  OXC.postwalk(ast, [], fn
    %{type: "ImportDeclaration", source: %{value: "vue", start: s, end: e}}, acc ->
      {nil, [%{start: s, end: e, change: "'/@vendor/vue.js'"} | acc]}
    node, acc ->
      {node, acc}
  end)

rewritten = OXC.patch_string(source, patches)
# "import { ref } from '/@vendor/vue.js'\nimport { h } from 'preact'"

postwalk/2 visits nodes depth-first (children before parent), like Macro.postwalk/2. postwalk/3 adds an accumulator for collecting data during traversal. patch_string/2 applies patches in reverse offset order so positions stay valid.

Bundle

Bundle multiple TypeScript/JavaScript modules into a single IIFE script. Resolves import dependencies, topologically sorts, strips import/export syntax:

files = [
  {"event.ts", "export class Event { type: string; constructor(t: string) { this.type = t } }"},
  {"target.ts", "import { Event } from './event'\nexport class Target extends Event {}"}
]

{:ok, js} = OXC.bundle(files)
# (() => { class Event { ... } class Target extends Event { } })();

Options:

# Minify with tree-shaking and variable mangling
{:ok, js} = OXC.bundle(files, minify: true)

# Compile-time replacements (like esbuild/Bun define)
{:ok, js} = OXC.bundle(files, define: %{"process.env.NODE_ENV" => ~s("production")})

# Source maps
{:ok, %{code: js, sourcemap: map}} = OXC.bundle(files, sourcemap: true)

# Remove console.* calls
{:ok, js} = OXC.bundle(files, minify: true, drop_console: true)

# Target-specific downleveling
{:ok, js} = OXC.bundle(files, target: "es2020")

# Banner and footer
{:ok, js} = OXC.bundle(files, banner: "/* MIT */", footer: "/* v1.0 */")

Bang Variants

All functions have bang variants that raise on error:

ast = OXC.parse!("const x = 1", "test.js")
js = OXC.transform!("const x: number = 42", "test.ts")
min = OXC.minify!("const x = 1 + 2;", "test.js")
imports = OXC.imports!("import { ref } from 'vue'", "test.ts")

How It Works

OXC is a collection of high-performance JavaScript tools written in Rust. This library wraps oxc_parser, oxc_transformer, oxc_minifier, oxc_transformer_plugins, and oxc_codegen via Rustler NIFs.

All NIF calls run on the dirty CPU scheduler so they don't block the BEAM. The parser produces JSON via OXC's ESTree serializer, which is then converted to atom-keyed Elixir maps in the NIF — no JSON library needed on the Elixir side.

License

MIT

About

Elixir bindings for the OXC JavaScript toolchain — parse, transform, and minify JS/TS via Rust NIFs

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

0