Elixir bindings for the Vize Vue.js toolchain — compile, lint, and analyze Vue SFCs via Rust NIFs https://vizejs.dev
  • Rust 54.1%
  • Elixir 45.9%
Find a file
2026-07-20 23:56:56 +02:00
.cargo Add Rustler precompiled NIF support 2026-03-12 09:18:57 +03:00
.github/workflows Grant native release workflow permissions 2026-07-20 22:54:09 +02:00
codegen/vize/codegen refactor: derive source locations with RustQ Native 2026-07-16 15:17:50 +02:00
lib Add Windows precompiled target 2026-07-17 17:02:15 +02:00
native/vize_ex_nif refactor: derive source locations with RustQ Native 2026-07-16 15:17:50 +02:00
test build: use RustQ 1.0.0-rc.3 2026-07-17 15:19:45 +02:00
.credo.exs Initial Vize bindings: compile, SSR, Vapor IR, lint 2026-03-11 18:30:24 +03:00
.formatter.exs refactor: derive source locations with RustQ Native 2026-07-16 15:17:50 +02:00
.gitignore chore: ignore local audit output 2026-06-11 13:52:37 +03:00
Cargo.lock Upgrade Vize and add native Sass compilation 2026-07-13 13:49:08 +02:00
Cargo.toml Initial Vize bindings: compile, SSR, Vapor IR, lint 2026-03-11 18:30:24 +03:00
CHANGELOG.md Prepare 0.14.1 release 2026-07-20 16:33:15 +02:00
checksum-Elixir.Vize.Native.exs Update precompiled NIF checksums for v0.14.1 2026-07-20 23:56:56 +02:00
LICENSE Initial Vize bindings: compile, SSR, Vapor IR, lint 2026-03-11 18:30:24 +03:00
mix.exs Prepare 0.14.1 release 2026-07-20 16:33:15 +02:00
mix.lock build: use RustQ 1.0.0-rc.3 2026-07-17 15:19:45 +02:00
README.md Upgrade Vize and add native Sass compilation 2026-07-13 13:49:08 +02:00
rustq.exs refactor: derive source locations with RustQ Native 2026-07-16 15:17:50 +02:00

Vize

Elixir bindings for the Vize Vue.js toolchain via Rust NIFs.

Compile, lint, and analyze Vue Single File Components at native speed — including Vapor mode IR for BEAM-native SSR.

Features

  • Compile Vue SFCs to JavaScript + CSS (DOM, Vapor, SSR modes)
  • Template compilation — standalone template → render function
  • Vapor IR — get the intermediate representation as Elixir maps for BEAM-native rendering
  • SSR — server-side rendering compilation with _push() codegen
  • Lint Vue SFCs with built-in rules
  • Content hashes — template, script, and style hashes for HMR change detection
  • CSS compilation — standalone LightningCSS pipeline with autoprefixing, minification, and Vue scoped styles
  • CSS AST tooling — parse, traverse, transform, and print LightningCSS-backed ASTs

Installation

def deps do
  [
    {:vize, "~> 0.11.0"}
  ]
end

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

Usage

Compile SFC

{:ok, result} = Vize.compile_sfc("""
<template>
  <button @click="count++">{{ count }}</button>
</template>

<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<style scoped>
button { color: blue; }
</style>
""")

result.code     # Generated JavaScript
result.css      # Compiled CSS
result.errors   # []

Pass a filename for stable scoped CSS data-v-xxxx attributes and content hashes:

{:ok, result} = Vize.compile_sfc(source, filename: "App.vue")

result.template_hash  # "de5ddf78a0f8d31a"
result.style_hash     # "3efafd39ec9747f9"
result.script_hash    # "1a8dae0fef50c189"

Vapor Mode

{:ok, result} = Vize.compile_vapor("<div>{{ msg }}</div>")
result.code       # Vapor JS (no virtual DOM)
result.templates  # Static HTML templates

Structured diagnostics are available when requested:

{:ok, %Vize.Vapor.Result{diagnostics: diagnostics}} =
  Vize.compile_vapor(~s(<div id="a" id="b">x</div>), diagnostics: true)

[%Vize.Diagnostic{code: "DuplicateAttribute", recoverable?: true}] = diagnostics

Vapor IR (for BEAM-native SSR)

{:ok, ir} = Vize.vapor_ir("<div :class=\"cls\">{{ msg }}</div>")

ir.templates             # ["<div> </div>"]
ir.element_template_map  # [{0, 0}]  — element ID → template index
ir.block                 # %{operations: [...], effects: [...], returns: [...]}

The IR exposes every Vue construct as Elixir maps with :kind atoms:

Kind Vue Feature
:set_text {{ expr }}
:set_prop :attr="expr"
:set_html v-html
:set_dynamic_props v-bind="obj"
:set_event @event="handler"
:if_node v-if / v-else-if / v-else
:for_node v-for
:create_component <Component />
:directive v-show, v-model, custom

Static expressions are tagged as {:static, "value"} tuples, dynamic expressions are plain strings.

Vapor Split (for Phoenix LiveView)

{:ok, split} = Vize.vapor_split("<div :class=\"cls\"><p>{{ msg }}</p></div>")

split.statics  # ["<div class=\"", "\"><p>", "</p></div>"]
split.slots    # [%{kind: :set_prop, values: ["cls"]}, %{kind: :set_text, values: ["msg"]}]

Produces a statics/slots split ready for %Phoenix.LiveView.Rendered{}. All HTML manipulation (tag tree parsing, marker injection, splitting) happens in the NIF. Sub-blocks for v-if / v-for are recursively split.

Used by PhoenixVapor to render Vue templates as native LiveView output.

SSR Compilation

{:ok, result} = Vize.compile_ssr("<div>{{ msg }}</div>")
result.code      # JS with _push() calls
result.preamble  # Import statements

Template Compilation

{:ok, result} = Vize.compile_template("<div v-if=\"show\">{{ msg }}</div>")
result.code     # Render function
result.helpers  # ["createElementVNode", "toDisplayString", ...]

Lint

{:ok, diagnostics} = Vize.lint("<template><img></template>", "App.vue")

Parse and Analyze SFC

{:ok, descriptor} = Vize.parse_sfc(source)
descriptor.template      # %{content: "...", lang: nil, ...}
descriptor.script_setup  # %{content: "...", setup: true, ...}
descriptor.styles        # [%{content: "...", scoped: true, ...}]

Semantic analysis returns a %Vize.Croquis{} summary:

{:ok, croquis} = Vize.analyze_sfc(source)
croquis.bindings
croquis.used_components
croquis.component_usages
croquis.template_expressions

CSS Compilation

Standalone CSS compilation via LightningCSS — parse, autoprefix, and minify CSS independently of SFC compilation:

{:ok, result} = Vize.CSS.compile(".foo { color: red; user-select: none }", minify: true)
result.code
# ".foo{color:red;-webkit-user-select:none;user-select:none}"

With Vue scoped styles:

{:ok, result} = Vize.CSS.compile(".foo { color: red }", scoped: true, scope_id: "data-v-abc123")
result.code
# ".foo[data-v-abc123] { color: red }"

Browser targeting:

{:ok, result} = Vize.CSS.compile(css, targets: %{chrome: 80, firefox: 78, safari: 14})

Native Sass and SCSS compilation via the Rust grass compiler:

{:ok, result} =
  Vize.CSS.compile_sass("$brand: #639; .button { color: $brand; &:hover { opacity: .8 } }")

result.code
# ".button { ... }\n.button:hover { ... }"

Use syntax: :sass for indented syntax, filename: for relative imports, and load_paths: for additional import directories.

Parser-backed CSS URL rewriting without AST print round-trips:

{:ok, urls} = Vize.CSS.collect_urls(".logo { background: url('./logo.svg') }")
[%Vize.CSS.URL{url: "./logo.svg"}] = urls

{:ok, css} =
  Vize.CSS.rewrite_urls(".logo { background: url('./logo.svg') }", fn
    "./logo.svg" -> {:rewrite, "/assets/logo.svg"}
    _ -> :keep
  end)

Parser-backed CSS AST tooling:

{:ok, parsed} = Vize.CSS.parse_ast(".foo { background: url('./logo.svg') }")

ast =
  Vize.CSS.postwalk(parsed.ast, fn
    %{"url" => "./logo.svg"} = node -> %{node | "url" => "/assets/logo.svg"}
    node -> node
  end)

{:ok, result} = Vize.CSS.print_ast(ast)

Part of Elixir Volt

vize compiles and analyzes Vue single-file components from Elixir, including Vapor-mode IR for BEAM-native rendering.

It is part of a frontend stack that runs inside the BEAM — builds, JS runtimes, icons, and Vue-to-LiveView compilation as supervised parts of the application instead of external toolchain processes. See the Elixir Volt organization for the rest, and Building Blocks for the Future Web for the thesis, architecture, and roadmap that tie them together.

License

MIT