Repository navigation
Fix issues #56–#60: dev-server paths, runtime helpers, pre-bundling, Vue script blocks and their tsconfig - #61
Merged
Merged
Conversation
Volt.Plugin.Vue returned every <script> block as a script module and named any lang it did not know .js. A block in another language, such as PhoenixVapor's <script lang="elixir">, became a virtual .js module, so mix volt.js.check linted and type-checked Elixir as JavaScript. Only blocks without a lang, or with js, jsx, ts or tsx, are script modules now, and jsx blocks are named .jsx. Fixes #57
With a :target below ES2022, OXC lowers a class field into an import of a helper from @oxc-project/runtime. The dev server rewrote that import to /@vendor/, which resolved the package from node_modules and answered 404 when it was not installed. The importing module then failed to load, and every module importing it, with nothing in the page or the log. The production build already leaves these imports to OXC.bundle/2, which provides the helpers itself. Vendor bundling now does the same through an entry that only names the helper. The check for such specifiers moves to Volt.JS.Specifier, shared by both. Fixes #58
The working directory belongs to the whole VM, and Mix changes it while it compiles a dependency. Phoenix.CodeReloader does that for a reloadable path dependency during a request, so a vendor request served at the same time resolved the relative cache directory under the dependency: the bundle was written into the dependency's _build, or the write failed with enoent and the page's module graph did not load. Volt now remembers the working directory when it starts, and the dev server resolves from it: the vendor cache, the asset root, watched and reload directories, the public directory and the Tailwind input. Build tasks, which run with the project as the working directory, are unchanged. Fixes #56
Phoenix runs a plug's init/1 on every request in development, and Volt.DevServer.init/1 pre-bundles vendor packages. Two things made that expensive and unsafe: A bare import that resolves to no package, such as one the host page provides, is never written to the cache, so it never counted as fresh and every call bundled all packages again. Concurrent requests then replaced cache files that other requests were reading, which fits the module graph failing to load right after a restart. Such imports are no longer part of the freshness check. Even with a fresh cache, each request scanned every source file for imports. With a watcher, the dev server now pre-bundles once and again only after the watcher sees a source file change, and requests with the same options take turns. Without a watcher nothing says when sources change, so it scans on every init as before. Fixes #59
The script of a .vue or .svelte file goes to tsgolint as a virtual module in its source overrides. tsgolint assigns a file to a project by the nearest tsconfig.json that includes it, and expands include by listing directories on disk. Its overlay for the overrides answers "does this file exist" and "read it", but directory listings still come from disk, so a virtual module is never included and lands in an inferred project: no paths, and none of the project's declaration files. tsgolint reads a tsconfig.json through the same overlay. So the nearest tsconfig.json is overridden with one that extends the original, carried unchanged under another name beside it, and names the virtual modules in files. The original's own files are repeated, since files replaces the extended config's, and the default include is spelled out when the original relied on it. Fixes #60
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 #56, fixes #57, fixes #58, fixes #59, fixes #60. One commit per issue.
#56 — vendor cache written relative to the working directory
Phoenix.CodeReloaderchanges the VM's working directory while it compiles a reloadable path dependency. A vendor request served in that window resolved the relative cache directory under the dependency: the bundle landed in the dependency's_build, or the write failed withcould not write to file.Volt now remembers the working directory when it starts (
Volt.Paths.root/0), and the dev server resolves from it: the vendor cache, the asset root, watched and reload directories, the public directory and the Tailwind input.Build tasks, which run with the project as the working directory, are unchanged. Paths resolved deeper in the Tailwind compiler and for
tsconfig.jsonand aliases still use the current working directory; they are shared with the build, and I left them alone here.#57 —
<script lang="elixir">treated as JavaScriptVolt.Plugin.Vuereturned every<script>block as a script module and named any unknownlang.js, somix volt.js.checklinted and type-checked Elixir as JavaScript. Only blocks without alang, or withjs,jsx,tsortsx, are script modules now, andjsxblocks are named.jsx.#58 — lowered class fields import helpers
/@vendor/cannot serveWith a
:targetbelow ES2022, OXC lowers a class field into an import of an@oxc-project/runtimehelper./@vendor/answered 404 unless the npm package was installed, so the importing module, and every module importing it, failed to load silently.The production build already leaves these imports to
OXC.bundle/2, which provides the helpers itself. Vendor bundling now does the same through an entry that only names the helper. The specifier check moves toVolt.JS.Specifier, shared by the builder and the dev server.#59 — pre-bundling on every request
Phoenix runs
init/1per request in development, andVolt.DevServer.init/1pre-bundles vendor packages. Two things made that expensive and unsafe:I did not make it run strictly once: packages added later would then be bundled one by one on demand, each with its own copy of shared dependencies, which an existing test for shared chunks caught.
The second failure in #59,
a Volt watcher is already running for this asset root with different optionsafter a restart, fits #56: a request resolving the asset root during the code reloader's directory change gets a different root. That is addressed by the #56 commit, but I have not reproduced it.The rest of
init/1(reading config,tsconfig.jsonand.envfiles) still runs per request.#60 — component scripts type-checked without the project's tsconfig
A
.vueor.sveltescript goes to tsgolint as a virtual module. tsgolint (v0.22.1,cmd/tsgolint/overlayfs.go) overlays source overrides for "does this file exist" and "read it", but directory listings still come from disk. A tsconfig'sincludeis expanded by listing directories, so a virtual module is never included and lands in an inferred project: nopaths, none of the project's declaration files.tsgolint reads a
tsconfig.jsonthrough the same overlay. So the nearesttsconfig.jsonis overridden with one that extends the original, carried unchanged under another name beside it, and names the virtual modules infiles. The original's ownfilesare repeated, becausefilesreplaces the extended config's, and the defaultincludeis spelled out when the original relied on it. Nothing is written to disk.Known limit: an original that gets its
includeonly through its ownextendschain is treated as having one; if that chain has none either, the default include is lost for that run.Verification
mix test: 768 passed.mix lintpasses. Browser tests (--include integration): 30 passed.master:Changelog: three entries under Unreleased. #54 and #55 also add an Unreleased section, so later merges need their changelog entries rebased.