Repository navigation
Fix dev-server and test-runner bugs; move formatter options; Rollup-style externals - #49
Merged
Merged
Conversation
Dev server and watcher: - Compare file contents before rebuilding, so a file rewritten with identical content (LiveView colocated hooks on every code reload) no longer reloads the page in a loop. - Resolve relative watch_ignored patterns from the project directory as well as each watch root. - Convert local CommonJS/UMD files such as vendor/topbar.js to ES modules in development. - Append the browser hash to relative imports inside served pre-bundles, so a package reached from application code and from another pre-bundle is a single module instance. Vue was loaded twice before. Build: - Skip tsconfig path mappings that only point at declaration files. - Keep external modules as imports in :esm and :cjs output, as Rollup does. Only IIFE output reads them from globals. (Breaking.) Formatter: - Read formatter options from the :volt key of .formatter.exs instead of config :volt, :format, which now only holds the build output format. A keyword list there raises with the options to move. The installer, guides, examples and Volt's own config follow. (Breaking.)
A second watcher with different options, such as a Mix.Tasks.Volt.Dev entry in the endpoint's :watchers next to plug Volt.DevServer, made every request answer a bare 503. The response and an error log now name the conflict and how to resolve it.
Phoenix initializes plugs on every request in development, so the dev server logged "Pre-bundled N vendor package(s)" per request even when the cache was fresh. The log moves to where bundling happens.
Chunks already read their external globals inside the IIFE. A second copy was prepended at the top level of each script, where two chunks importing the same name would clash.
During application shutdown a watcher can terminate after Volt.Tailwind.Registry has stopped, and releasing its context raised "unknown registry". There is nothing left to release then.
Source lines were matched to collected tests by position with Enum.zip, which truncates to the shorter list. The line scan only recognized plain test() and it() calls, so a five-case table followed by two tests was collected as two tests. Each case now maps to the line of its table, and describe.each repeats its inner tests per case. When the source shows a different number of tests than the runtime collected, such as tests registered in a loop, all tests are kept without a line.
The client types only declared import.meta.hot, so TypeScript projects had to declare ImportMeta.env themselves. The installer adds env.d.ts to configurations that list declaration files explicitly.
They were not in the dev server's compilable extensions, so the private extension list in Volt.JS.CommonJS could never match .cjs. Both are CommonJS by extension and go through the same conversion as CommonJS .js files; only .js is inspected. The list comes from Volt.JS.Extensions.
The watcher supervisor started before the Tailwind registry and worker supervisor, so on shutdown the Tailwind processes stopped first and every watcher with Tailwind enabled crashed in terminate/2 with "unknown registry: Volt.Tailwind.Registry". Start the Tailwind processes first. This replaces the Process.whereis guard in Volt.Tailwind.Supervisor.release/1, which hid the ordering problem.
Volt.Test.Lines goes back to OXC.postwalk/3 instead of a hand-rolled traversal. Post-order reaches the tests in a describe.each body before the describe.each call, which folds them into one repeated group. Line numbers were found by counting newlines from the start of the source for every test. On a 110 KB file with 1,200 tests that took about 140 ms of 214 ms; a single pass over the newline offsets brings the whole function to about 81 ms, most of it OXC.parse.
mix lint failed on Elixir 1.20 / OTP 29, the job that runs it: - Credo: two single-item list appends. - Architecture policy: Volt.JS.CommonJS had no layer, and Volt.Config (model) called Volt.JS.Format (logic), closing a layer cycle. The check for formatter options left under config :volt, :format moves to Volt.Config, which Volt.JS.Format calls. - Smells: a case returning booleans in the dev server, and a member expression shape repeated across the callee matchers in Volt.Test.Lines, now one function.
Volt's .formatter.exs held the formatter options but did not list Volt.Formatter, so mix format never exercised the plugin on Volt's own sources. Add the plugin and the priv/ts sources that mix volt.js.format already covers.
mix format loads a project's own formatter plugin from the existing build and compiles only when the module cannot be loaded. CI restores a cached _build, so it formatted Volt's TypeScript with the previous Volt.Formatter, which did not read options from .formatter.exs, and reported every file as unformatted.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes a batch of issues found while running Phoenix apps (including the PhoenixVapor demo) on Volt 0.19.4, plus two breaking config changes. Every change has a line in the Unreleased section of the changelog.
Breaking changes
Formatter options move to
.formatter.exs. They are read from the:voltkey, wheremix formatplugins keep their options.config :volt, :formatnow only holds the build output format, so both can be set. A keyword list under the old key raises anArgumentErrorthat prints the options to move. The formatting-only:root,:sourcesand:ignoreoverrides move to the same key. The installer, guides, examples and Volt's own config follow.Externals stay as imports in
:esmand:cjsoutput, as in Rollup, Rolldown, esbuild and Bun. Only IIFE output reads them from globals, and the global names inexternal: %{"phoenix" => "Phoenix"}apply to IIFE only.import { Socket } from "phoenix":iifeconst { Socket } = Phoenix;(unchanged):esmimport { Socket } from "phoenix";:cjsrequire("phoenix")Dev server and watcher
watch_ignoredpatterns from the project directory as well as each watch root.vendor/topbar.jsto ES modules in development..cjsand.ctsfiles, which the dev server did not serve, are converted the same way.?v=hash, so Vue was loaded twice and component libraries failed to render.Mix.Tasks.Volt.Devwatcher runs next toplug Volt.DevServer.Pre-bundled N vendor package(s)only when packages are bundled, not on every request.Build
tsconfig.jsonpath mappings that only point at declaration files.Test runner and types
test.eachanddescribe.each. Lines were zipped onto tests by position, which dropped tests without a detected line. Line lookup now counts newlines once instead of once per test (about 214 ms to 81 ms on a 1,200-test file).import.meta.envin the client types.Verification
mix test: 752 passed, 5 skipped, 39 excluded by tag.mix format --check-formattedandmix volt.js.checkpass.env.d.tstype-checks withtscalongside the other client declarations.Application.stop(:volt); it stops cleanly after the reorder.Not included
import.meta.globis still not declared in the client types.