Repository navigation
v0.20.0
Breaking changes
-
Formatter options moved from
config :volt, :formatto the:voltkey of.formatter.exs, wheremix formatplugins keep their options.config :volt, :formatnow only holds the build output format (:iife,:esm, or:cjs), so both can be set. A keyword list underconfig :volt, :formatraises anArgumentErrorwith the options to move:# .formatter.exs [ plugins: [Volt.Formatter], volt: [semi: false, single_quote: true] ]The formatting-only
:root,:sources, and:ignoreoverrides formix volt.js.formatandmix volt.js.checkmove to the same key..oxfmtrc.jsonand.prettierrc.jsonare still read when there is no:voltkey.mix igniter.install voltwrites the options to.formatter.exs. -
External modules stay as imports in
:esmand:cjsoutput, as in Rollup, Rolldown, esbuild and Bun. Withformat: :esm, external: ["phoenix"],import { Socket } from "phoenix"used to becomeconst { Socket } = Phoenix;; it is now left for the browser or host to resolve, for example through an import map.:cjsoutput usesrequire("phoenix"). IIFE output, the default, still reads externals from globals. The global names inexternal: %{"phoenix" => "Phoenix"}apply to IIFE output only. ESM builds that relied on page globals need an import map orformat: :iife. -
Volt.JS.Format.load_config/1takes the.formatter.exsoptions instead of reading the application environment.
Compatibility
- Require vize 0.17. Volt's use of vize is unchanged: 0.17 replaces
Vize.vapor_split/1withVize.split_template/2, which Volt doesn't call. Projects that depend on vize themselves need 0.17 alongside this release.
Added
- Changes in
reload_dirsreload only the pages whose HTML changed. The watcher broadcasts adocumentupdate for them instead of afullone. Pages carry an entity tag of their rendered HTML; on a change the dev client revalidates withIf-None-Matchand reloads unless the server answers304 Not Modified. Editing one post no longer reloads every open tab, and a save that does not change a page's output reloads nothing. This covers every successful HTML response that passes throughVolt.DevServer, including pages that already load the dev client, so site generators built on Volt need no change.Volt.HMR.document_update/2sends the same request from other packages, andVolt.HMR.Documentdescribes the contract for servers that send HTML some other way. config :volt, :server, morph: truepatches a changed page in place instead of reloading it, so scroll position, focus and JavaScript state survive edits to text and classes. The client reloads as before when scripts or stylesheets differ.morph: [preserve: selector]names elements owned by client code, such as mounted components, which are left alone. The client dispatchesvolt:document-updatedafter a patch.- Stylesheet updates no longer flash unstyled content. The dev client adds a
<link>for the updated stylesheet and removes the previous one once the new one has loaded, instead of changinghrefin place. - The dev client reloads the page once when several updates ask for a reload at the same time, such as a template that is also a Tailwind source.
- The dev server converts local CommonJS and UMD files, such as Phoenix's vendored
topbar.js, to ES modules, soimport topbar from "../vendor/topbar"works in development as it does in production builds..cjsand.ctsfiles, which the dev server did not serve, are converted the same way. - Relative
watch_ignoredpatterns also resolve from the project directory, so_build/**matches a watched directory inside_build. - Volt's client types declare
import.meta.env, so TypeScript projects no longer need their ownImportMetadeclaration forMODE,DEV,PRODand exposed variables.mix igniter.install voltaddsenv.d.tsto configurations that list declaration files explicitly.
Fixed
- Collect every test generated by
test.eachanddescribe.each. Source lines were matched to tests by position, and tests without a detected line were dropped: a five-case table followed by two tests collected as two tests. Each case now maps to the line of its table, and tests registered in ways the source does not show, such as in a loop, are kept without a line. - Stop the dev server reloading pages in a loop when a watched file is rewritten with identical content, as Phoenix LiveView does for colocated hooks on every code reload. The watcher now compares file contents before rebuilding.
- Load a single instance of each pre-bundled dependency in development. Pre-bundles imported their siblings and shared chunks without the
?v=hash that application modules use, so browsers loaded packages such as Vue twice and component libraries built on them failed to render. - Report why a development session is unavailable. A second watcher with different options, such as a
Mix.Tasks.Volt.Deventry in the endpoint's:watchersnext toplug Volt.DevServer, answered every request with a bare 503; the response and the log now name the conflict and how to resolve it. - Log
Pre-bundled N vendor package(s)only when packages are bundled. Phoenix initializes plugs on every request in development, so it was logged per request. - Read external globals once in code-split IIFE output. Each chunk also declared them at the top level of the script, where two chunks importing the same name would clash.
- Stop watchers before the Tailwind processes they release their contexts to. Volt's supervisor stopped the Tailwind registry first, so every watcher with Tailwind enabled crashed with
unknown registry: Volt.Tailwind.Registryon shutdown. - Skip
tsconfig.jsonpath mappings that only point at declaration files. A types-only mapping such as"topbar": ["./types/topbar.d.ts"]became a bundler alias and bundled the.d.tsfile in place of the package.