Skip to content

Existing Project issues #27

Description

@andr-ec

Hi I was evaluating volt and wanted to see how it would fair with an existing project, what would break and what would require workarounds.

I had claude take a stab at it and found a few small issues with workarounds. And just to confirm they were issues, created a reproduction repo. I'll include all the issues here but can split them into separate issues if you'd prefer.

Repro repo: https://github.com/andr-ec/volt-repro-bugs (pinned to {:volt, "== 0.14.12"}, Elixir 1.20)

Each bug is an independent Volt profile, reproduced two ways: with mix volt.build <profile> (production) and against a live Volt.DevServer over HTTP (dev_server_check.exs). Source-line references are against 0.14.12.


1. CSS bundler can't resolve a bare (package) @import

@import "csslib/theme.css"; where csslib is a package (with an exports map for ./theme.css). Vite/LightningCSS resolve this into node_modules.

  • Expected: built CSS contains the imported rule.
  • Prod (mix volt.build css_import): Build failed: ["CSS bundle error: Error { kind: ResolverError(Os { code: 2, kind: NotFound ... }), loc: ... styles.css, line: 2 ...]
  • Dev (GET /assets/src/css_import/styles.css): 500 compilation error.
  • Cause: Volt.Builder.Writer (lib/volt/builder/writer.ex:100) calls Vize.CSS.compile(css, minify: ...) with only a minify flag — no resolver, no conditions — and lib/volt/css/dependencies.ex only accepts relative @import. A bare specifier is never resolved against resolve_dirs.
  • Workaround: pre-bundle package CSS with esbuild (--conditions=style) and <link> the flat output.

2. Resolver ignores a subpath directory's package.json "main"/"module"

Importing dirlib/sub, where dirlib/sub/ is a directory containing only a package.json ("main": "../dist/sub.js", "module": "...", no index.js) and the parent has no exports map. Node, esbuild, and Vite all follow that nested package.json.

  • Expected: the module resolves to dist/sub.js.
  • Prod (mix volt.build dir_pkg): build succeeds silently but emits (function(e){console.log(e.SUB)})(dirlib_sub); — the unresolved specifier is externalized into an undefined global dirlib_sub, so it throws at runtime and the real export never lands. The silent-externalize-on-unresolved behaviour is the dangerous part.
  • Dev (GET /assets/src/dir_pkg/app.js): import rewritten to /@vendor/dirlib__slash__sub.js, which returns 404 vendor module not found: dirlib/sub.
  • Cause: NPM.Resolution.PackageResolver.resolve_without_exports/5 (deps/npm/lib/npm/resolution/package_resolver.ex:313, subpath clause :323) only probes <subpath>.<ext> and <subpath>/index.<ext>; it never reads a subpath directory's package.json main/module.
  • Real-world trigger: react-remove-scroll-bar/constants (a Radix transitive dep). Workaround: an explicit alias.

3. ?raw is ignored for .md

import md from "./note.md?raw". Vite honours ?raw for any extension.

  • Expected: md is the file contents as a string.
  • Prod (mix volt.build md_raw): Build failed: [%{message: "Invalid Character ..."}] — the .md is compiled as JavaScript.
  • Dev (GET /assets/src/md_raw/note.md?raw): 500, no module served.
  • Cause: Volt.MIME lists .md in @non_asset_exts (lib/volt/mime.ex:16), so asset?/1 returns false (:49) and the ?raw branch — gated on asset? — never fires.
  • Workaround: rename raw-imported .md files to .txt.

4. Vue SFC <style> is dropped in development (production is fine)

An SFC with a <style> block. Run mix run --no-start repro_vue_dev_style.exs, or fetch it from the dev server.

  • Expected: the component is styled in dev.
  • Dev (GET /assets/src/vue_style/Widget.vue): 200, but the served module is the script + render function only — it has no self-import of the style (no ?vue&type=style, no .css). Vize.compile_sfc puts the CSS in result.css, but nothing references it, so the browser never requests it and the component renders unstyled.
  • Cause: Volt.Plugin.Vue.compile/3 returns css: result.css separately (lib/volt/plugin/vue.ex:36) and embedded_modules/3 returns only the scripts (:62-64); nothing appends a style self-import, and Volt.DevServer.code_for_request/4's normal-module clause serves result.code as-is. Vite's Vue plugin injects that self-import for exactly this reason.
  • Production is unaffected: mix volt.build vue_style collects result.css into the entry CSS and records it under the manifest entry's css[]. (Minor: Volt.Preload.tags/2 only emits modulepreload for .js, so a consumer must read manifest[entry]["css"] itself to <link> those styles.)
  • Workaround: move SFC styles into a global stylesheet.

Happy to split any of these into their own issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions