Skip to content

Releases: elixir-volt/volt

v0.6.5

17 Apr 12:27

Choose a tag to compare

Added

  • Strip TypeScript types from Vue SFCs with <script lang="ts"> after Vize compilation
  • Resolve import './types.js' to ./types.ts when the .js file doesn't exist (standard TS convention)
  • Resolve bare specifiers in directories without package.json (e.g. Phoenix colocated hooks via resolve_dirs: [Mix.Project.build_path()])

Fixed

  • Fix Vue SFC compiler-injected vue imports being externalized in SSR bundles — bare specifiers introduced by Vize are now resolved via a global fallback map
  • Fix JSON module imports crashing OXC.bundle — .json labels are renamed to .json.js so Rolldown treats the export default wrapper as JavaScript
  • Skip JSON files in import extraction (they have no imports)

v0.6.4

16 Apr 21:11

Choose a tag to compare

Added

  • loaders option for overriding file type parsing (e.g. loaders: %{".js" => "jsx"} for React projects that use JSX in .js files)
  • CJS require() calls are now collected as imports during dependency walking

Fixed

  • Fix bare specifier subpath resolution when package has no exports field (e.g. iframe-resizer/js/iframeResizer) — falls back to direct file path instead of returning the package main entry
  • Skip .d.ts type declaration imports instead of raising not-found errors
  • Skip CSS imports (import './app.css', @fontsource/inter, etc.) during JS bundling — CSS files are no longer collected, resolved, or passed to OXC.bundle
  • Fix files from resolve_dirs getting absolute path labels that break import rewriting

Performance

  • Use OXC.collect_imports (Rust NIF) for 98% of modules instead of parse + postwalk JSON round-trip — 2.5x faster collection
  • Use OXC.transform_many (rayon thread pool) for parallel module compilation — 3x faster on large projects
  • Livebook (2045 modules): 9s → 1.8s; Plausible Analytics dashboard: 5s → 1.2s

v0.6.3

16 Apr 09:37

Choose a tag to compare

Bug Fixes

  • Fix bundling packages with internal relative imports (reka-ui, @internationalized/date, etc.) — labels now preserve directory structure relative to node_modules, and import rewriting uses per-file specifier maps instead of a global map that conflated identical relative specifiers from different importers
  • Fix CaseClauseError when an alias resolves to a missing file — NPM.PackageResolver.try_resolve returning bare :error is now wrapped into {:error, {:not_found, path}}
  • Bump oxc to 0.7.1 (fixes parse/2 hitting serde_json recursion limit on large ASTs)

v0.6.2

15 Apr 18:18

Choose a tag to compare

Bug Fixes

  • Fix infinite label dedup loop when multiple modules import the same
    dependency (e.g. @vue/shared imported by both @vue/runtime-core
    and @vue/reactivity) — the second import no longer triggers label
    disambiguation, preventing mangled paths like dist/dist/@vue/shared_2

v0.6.1

15 Apr 16:42

Choose a tag to compare

Bug Fixes

  • Fix plugin content_type being ignored — when a plugin returned
    {:ok, code, "application/javascript"} for a .vue file, Pipeline
    still ran Vue SFC compilation on the already-compiled JS
  • Fix virtual modules (resolve"virtual:...") failing with
    :enoent — Collector now calls plugin load before File.read
  • Fix duplicate label crash when multiple files share the same basename
    (e.g. a/index.js and b/index.js) — labels are disambiguated with
    parent directory prefix and recursive _2 suffix fallback
  • Thread plugin content_type through Collector so import extraction
    dispatches consistently with Pipeline

v0.6.0

15 Apr 13:40

Choose a tag to compare

Per-Module ESM Dev Server with HMR

The dev server now serves individual ESM modules instead of opaque compiled
files. Each .ts, .vue, .jsx file gets its own URL, and import specifiers
are rewritten so the browser resolves the full module graph natively:

  • Relative imports (./utils) → /assets/utils.ts
  • Bare imports (vue) → /@vendor/vue.js (pre-bundled)
  • Alias imports (@/utils) → resolved via tsconfig paths or config aliases

Each JS module is injected with an import.meta.hot preamble for granular HMR:

if (import.meta.hot) {
  import.meta.hot.dispose(() => clearInterval(timer));
  import.meta.hot.accept();
}

On file change, the watcher walks the dependency graph upward to find the
nearest import.meta.hot.accept() boundary. Only that module is re-imported
via import("/@assets/Button.tsx?t=123") — no full page reload. Accept
callbacks receive the new module exports. Falls back to location.reload()
when no boundary is found.

TypeScript assets (HMR client, console forwarder, error overlay) are now
compiled to JS via OXC before serving to the browser.

Production Source Maps

Source maps are now fully usable in production builds:

  • sourcemap: true — write .map files and append //# sourceMappingURL (default)
  • sourcemap: :hidden — write .map files without the URL comment (for Sentry, Datadog)
  • sourcemap: false — no source maps
  • Chunked builds now generate source maps (previously discarded)
  • CLI: --sourcemap hidden

tsconfig.json Paths

Volt automatically reads compilerOptions.paths from tsconfig.json in the
project root and merges them into aliases. Explicit aliases take precedence.
Supports baseUrl for path resolution.

Manual Chunk Splitting

Control chunk boundaries via config:

config :volt,
  chunks: %{
    "vendor" => ["vue", "vue-router", "pinia"],
    "ui" => ["assets/src/components"]
  }

Bare specifiers match package names in node_modules. Path patterns match by
directory prefix. Manual chunks work alongside automatic dynamic-import splitting.

Bug Fixes

  • Fix alias-imported Vue SFCs silently dropping bare npm imports from the bundle

Internal

  • Reorganize internal modules into Volt.JS.*, Volt.CSS.*, Volt.Dev.* namespaces
  • Add Playwright browser integration tests (mix test --include integration)

v0.5.0

15 Apr 07:52

Choose a tag to compare

Tailwind Plugin & Config Support

Volt now resolves and bundles any Tailwind plugin or config on the fly — no vendored JS needed.

@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "./my-plugin.js";
@config "./tailwind.config.js";

Local plugins with require() dependency graphs are prebundled automatically via OXC.

Bundling Fix

Fixed a bug where bundling multiple npm packages (e.g. vue + @vue-flow/core) could produce SyntaxError: Identifier has already been declared due to scope collisions in the output.

Other Fixes

  • Preload.tags/2 now correctly generates modulepreload tags from manifests
  • File watcher detects create/close events, not just modifications
  • ETS cache tables are properly supervised (no more startup race)

Dependency Upgrades

  • oxc ~> 0.7.0, quickbeam ~> 0.10.0, npm ~> 0.5.3

Full changelog →

Release 0.4.3

25 Mar 05:38

Choose a tag to compare

Bump quickbeam to 0.8.1, remove stale package.json and vendoring tasks.

Release 0.4.2

24 Mar 20:53

Choose a tag to compare

  • Fix fresh installs for Tailwind support by removing the generated priv/tailwind.js workflow
  • Assemble the Tailwind runtime on first use from the tailwindcss package in the npm_ex cache
  • Bump QuickBEAM to 0.8.0 and npm_ex to 0.5.1

Release 0.4.1

24 Mar 06:54

Choose a tag to compare

TypeScript Assets

Browser JavaScript (HMR client, error overlay, dev console forwarder) moved from inline Elixir heredocs to separate TypeScript files in priv/ts/.

Maintainer Tooling

  • mix volt.js.check — oxfmt format check and oxlint via npx
  • mix volt.js.fmt — format TypeScript assets via npx
  • mix volt.npm — install JS tooling deps via npm_ex
  • mix volt.vendor.tailwind — regenerate priv/tailwind.js from installed tailwindcss

Tailwind Vendoring

priv/tailwind.js is now generated from the tailwindcss npm package by mix volt.vendor.tailwind instead of being maintained by hand.

Build Improvements

  • Structured manifest entries with file, src, assets, and css fields
  • Standalone CSS entries in the manifest
  • Worker entry groundwork
  • Hardened package resolution with browser/import/default/require and CJS support
  • Dev console forwarding from browser to terminal