Volt.Builder reads config :volt, :format as the bundle format, so its value is an atom such as :esm. Volt.JS.Format and Volt.JS.Helpers read the same key as formatter options and expect a keyword list.
The result is that a project which asks the bundler for ESM cannot use Volt.Formatter or mix volt.js.check at all. Both fail immediately, before they look at a single file.
Environment
:volt 0.17.11
:oxc 0.17.8
- Elixir 1.20.4, Erlang/OTP 29
- Debian 13, linux aarch64
Reproduction
# config/config.exs
config :volt,
entry: "assets/js/app.js",
root: "assets",
outdir: "priv/static/assets",
format: :esm
What happens
There are two independent failures, in two different functions.
Volt.JS.Format.load_config/0 matches only nil or a list, so the atom falls through the case:
** (CaseClauseError) no case clause matching:
:esm
(volt 0.17.11) lib/volt/js/format.ex:30: Volt.JS.Format.load_config/0
Volt.JS.Helpers.discovery_config/1 returns the atom unchanged, and discover_files/1 then calls Keyword.get/3 on it. This is the failure behind mix volt.js.check --type-aware:
** (FunctionClauseError) no function clause matching in Keyword.get/3
# 1
:esm
Volt.Formatter runs into the first failure as soon as mix format reaches a JavaScript file:
mix format failed for file: assets/js/hook-dom.js
** (CaseClauseError) no case clause matching:
:esm
(volt 0.17.11) lib/volt/js/format.ex:30: Volt.JS.Format.load_config/0
(volt 0.17.11) lib/volt/formatter.ex:34: Volt.Formatter.format/2
Why the obvious workaround does not apply
Removing format: :esm is not an option here. shared_entries?/3 at builder.ex:83 shares modules between entry points only when the bundle is ESM, and the page loads that bundle with <script type="module">.
Suggested fix
Both functions should treat the key as formatter options only when it actually holds a keyword list, and fall back to the JSON configuration otherwise. That leaves the public API untouched, introduces no new configuration key, and keeps every configuration that works today working.
I am happy to open it as a pull request.
Workaround in the meantime
Volt.JS.Format.load_json_config/0 is public and does not read the application environment:
OXC.Format.run!(source, "hook.js", Volt.JS.Format.load_json_config())
That covers formatting. There is no equivalent for mix volt.js.check, which fails before it starts.
Volt.Builderreadsconfig :volt, :formatas the bundle format, so its value is an atom such as:esm.Volt.JS.FormatandVolt.JS.Helpersread the same key as formatter options and expect a keyword list.The result is that a project which asks the bundler for ESM cannot use
Volt.Formatterormix volt.js.checkat all. Both fail immediately, before they look at a single file.Environment
:volt0.17.11:oxc0.17.8Reproduction
What happens
There are two independent failures, in two different functions.
Volt.JS.Format.load_config/0matches onlynilor a list, so the atom falls through thecase:Volt.JS.Helpers.discovery_config/1returns the atom unchanged, anddiscover_files/1then callsKeyword.get/3on it. This is the failure behindmix volt.js.check --type-aware:Volt.Formatterruns into the first failure as soon asmix formatreaches a JavaScript file:Why the obvious workaround does not apply
Removing
format: :esmis not an option here.shared_entries?/3atbuilder.ex:83shares modules between entry points only when the bundle is ESM, and the page loads that bundle with<script type="module">.Suggested fix
Both functions should treat the key as formatter options only when it actually holds a keyword list, and fall back to the JSON configuration otherwise. That leaves the public API untouched, introduces no new configuration key, and keeps every configuration that works today working.
I am happy to open it as a pull request.
Workaround in the meantime
Volt.JS.Format.load_json_config/0is public and does not read the application environment:That covers formatting. There is no equivalent for
mix volt.js.check, which fails before it starts.