You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds a Volt plugin hook for dev vendor prebundling externals.
This lets framework/integration plugins declare bare imports that should remain external while third-party vendor packages are prebundled, then rewrites those preserved imports back to Volt vendor URLs.
Use case
In a Phoenix + Volt React app, we use:
React
TanStack Router / Query / DB
Base UI via shadcn
lucide-react
Volt currently prebundles each vendor package independently in dev. Packages like @base-ui/react, @tanstack/react-query, and lucide-react may inline their own React copy during vendor prebundling. That produced duplicate React singletons and runtime errors such as:
Invalid hook call. Hooks can only be called inside of the body of a function component.
Cannot read properties of null (reading 'useRef')
Cannot read properties of null (reading 'useEffect')
Cannot read properties of null (reading 'useContext')
The app already had a plugin that canonicalized React-family imports to one /@vendor/react.js module, but vendor prebundling had no way to keep React external inside other prebundled vendor packages.
What changed
Adds optional Volt.Plugin.prebundle_externals/0
Adds Volt.PluginRunner.prebundle_externals/1
Passes plugin-provided externals to OXC.bundle/2 during dev vendor prebundling
Rewrites preserved external imports to canonical Volt vendor URLs using prebundle_alias/1
Avoids self-externalizing the canonical prebundle entry itself
Includes prebundle externals in the vendor browser/cache signature
Tried before this
Before adding this hook, we tried app-side workarounds:
Aliasing React-related packages through app config
Canonicalizing React-family imports with prebundle_alias/1
Creating a synthetic React vendor entry via prebundle_entry/1
Those helped top-level app imports, but did not prevent third-party vendor bundles from inlining React internally. Replacing Base UI/shadcn primitives with native React elements would have avoided the symptom, but was not acceptable because the goal is to support real Base UI/shadcn usage.
Verification
Verified in the downstream app:
@base-ui/react, TanStack, Floating UI, and lucide-react vendor bundles now import React from the singleton /@vendor/react.js?...
Page renders with Base UI/shadcn components
No React invalid-hook-call runtime errors
Browser console has 0 errors / 0 warnings
Verified in this repo:
mix compile --warnings-as-errors
MIX_ENV=test mix test
AI usage disclosure
This patch was developed with AI assistance. The AI helped investigate the duplicate React runtime issue, compare attempted solutions, draft the implementation, and run validation steps. The code and behavior were manually reviewed and verified with the test commands above. But I'm not very familiar with how Volt works, so I cannot take full responsibility for it.
Thanks for digging into this — the hook is the right shape, but I think this needs one more pass before merge. The new callback isn’t wired into Volt.Plugin.React, so the React duplicate-singleton case described here still won’t be fixed for normal Volt React apps unless users add a custom plugin. Could you add React’s common entrypoints to prebundle_externals/0 in the built-in React plugin, plus tests that assert a third-party vendor importing react is preserved and rewritten to the canonical /@vendor/react.js?v=... URL? Also, please avoid the string-replacement fallback in rewrite_external_import_reference_literals/2; Volt tries to keep source transformations parser-backed, and this can rewrite comments/string literals while still missing valid import syntax with whitespace/newlines. Since Volt.JS.Transforms.Imports.rewrite/3 already handles imports via OXC AST positions, we should rely on that path or fail/skip the rewrite when parsing fails.
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
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.
Summary
Adds a Volt plugin hook for dev vendor prebundling externals.
This lets framework/integration plugins declare bare imports that should remain external while third-party vendor packages are prebundled, then rewrites those preserved imports back to Volt vendor URLs.
Use case
In a Phoenix + Volt React app, we use:
Volt currently prebundles each vendor package independently in dev. Packages like
@base-ui/react,@tanstack/react-query, andlucide-reactmay inline their own React copy during vendor prebundling. That produced duplicate React singletons and runtime errors such as:Invalid hook call. Hooks can only be called inside of the body of a function component.Cannot read properties of null (reading 'useRef')Cannot read properties of null (reading 'useEffect')Cannot read properties of null (reading 'useContext')The app already had a plugin that canonicalized React-family imports to one
/@vendor/react.jsmodule, but vendor prebundling had no way to keep React external inside other prebundled vendor packages.What changed
Volt.Plugin.prebundle_externals/0Volt.PluginRunner.prebundle_externals/1OXC.bundle/2during dev vendor prebundlingprebundle_alias/1Tried before this
Before adding this hook, we tried app-side workarounds:
prebundle_alias/1prebundle_entry/1Those helped top-level app imports, but did not prevent third-party vendor bundles from inlining React internally. Replacing Base UI/shadcn primitives with native React elements would have avoided the symptom, but was not acceptable because the goal is to support real Base UI/shadcn usage.
Verification
Verified in the downstream app:
@base-ui/react, TanStack, Floating UI, andlucide-reactvendor bundles now import React from the singleton/@vendor/react.js?...Verified in this repo:
mix compile --warnings-as-errorsMIX_ENV=test mix testAI usage disclosure
This patch was developed with AI assistance. The AI helped investigate the duplicate React runtime issue, compare attempted solutions, draft the implementation, and run validation steps. The code and behavior were manually reviewed and verified with the test commands above. But I'm not very familiar with how Volt works, so I cannot take full responsibility for it.