Skip to content

feat: expose module identifier - #32

Merged
chenjiahan merged 2 commits into
rstackjs:mainfrom
OskarLebuda:feat/expose-module-identifier-and-ssr-hmr
Sep 4, 2026
Merged

feat: expose module identifier#32
chenjiahan merged 2 commits into
rstackjs:mainfrom
OskarLebuda:feat/expose-module-identifier-and-ssr-hmr

Conversation

@OskarLebuda

@OskarLebuda OskarLebuda commented Sep 3, 2026

Copy link
Copy Markdown

An opt-in option for SSR setups. It is a no-op unless you pass it, so nothing changes for existing users.

exposeModuleIdentifier

Attaches a stable identifier to each component, so an SSR runtime can map the components it rendered back to their chunks and preload the right CSS/JS.

exposeModuleIdentifier?:
  | boolean   // -> internal hash() of the module request
  | ((request: string, ctx: { resourcePath: string; rootContext: string }) => string)

v15 did this itself via componentNormalizer.js#L56-L57. It was dropped in v16 and never replaced (vue-loader#1887, open since 2021, confirmed as "planned but not yet implemented"). Two PRs tried to restore it and are still unmerged: #2079 and #2083.

Both register into ssrContext._registeredComponents with a fixed hash. This PR only exposes the value instead, because consumers disagree on both parts:

  • The sink: nuxt/nuxt#27819 already replaced _registeredComponents with ssrContext.modules.
  • The value: Nuxt keys by relative path, vue-server-renderer by hash-sum of the request, #2083 by sha256, and it flags the clash itself.
  • Everyone already patches around it: Nuxt ships a second loader just to append this property. We maintain a fork for the same reason.

The name __moduleIdentifier matches v15 and Nuxt's mixin, so existing consumer code works unchanged. The value goes through propsToAttach like __file, and the |<hash> suffix from inline match resources is stripped so it lines up with Module.identifier().

Registering into ssrContext on top of this is a few lines consumer-side. Happy to move it into the loader instead if you prefer.

@chenjiahan chenjiahan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! The module identifier option looks good to me.

I noticed two issues with the server HMR changes:

  • SSR builds import ssrRender, but the generated template update callback still calls api.rerender(id, render). I reproduced a ReferenceError: render is not defined when applying a template update.
  • The imported-type watch hook only invalidates clientCache, while server builds use serverCache. Changes to imported prop types can therefore leave the server using stale compiled results.

Would you be open to moving the server HMR changes into a separate PR? That would let us review the update behavior and add tests for actual SSR template and type updates independently.

@OskarLebuda
OskarLebuda force-pushed the feat/expose-module-identifier-and-ssr-hmr branch from 67afd6a to 7e715e0 Compare September 4, 2026 09:23
@OskarLebuda OskarLebuda changed the title feat: expose module identifier and allow HMR in server builds feat: expose module identifier Sep 4, 2026
Add an opt-in `exposeModuleIdentifier` option that attaches a stable
`__moduleIdentifier` to each component, so an SSR runtime can map the
components it rendered back to their chunks.
@OskarLebuda
OskarLebuda force-pushed the feat/expose-module-identifier-and-ssr-hmr branch from 7e715e0 to 85696c6 Compare September 4, 2026 09:24
@OskarLebuda

Copy link
Copy Markdown
Author

Thanks for working on this! The module identifier option looks good to me.

I noticed two issues with the server HMR changes:

  • SSR builds import ssrRender, but the generated template update callback still calls api.rerender(id, render). I reproduced a ReferenceError: render is not defined when applying a template update.
  • The imported-type watch hook only invalidates clientCache, while server builds use serverCache. Changes to imported prop types can therefore leave the server using stale compiled results.

Would you be open to moving the server HMR changes into a separate PR? That would let us review the update behavior and add tests for actual SSR template and type updates independently.

@chenjiahan Oh, I didn't notice that! Of course - I've removed the HMR code.

I'll investigate both issues and bring them back in a separate PR. exposeModuleIdentifier stays in this one.

@chenjiahan chenjiahan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@chenjiahan
chenjiahan merged commit 850339a into rstackjs:main Sep 4, 2026
3 checks passed
chenjiahan pushed a commit that referenced this pull request Sep 8, 2026
`hotReload: true` now opts a server build into hot reload as well, so a dev
server that renders on the server can hot swap modules instead of restarting
the whole process. Server builds stay opt-in: without the flag nothing
changes, and `hotReload: false` still wins.

Addresses both issues raised in the review of #32:

- A server build imports `ssrRender`, but the generated template update
  callback called `api.rerender(id, render)`, which threw
  `ReferenceError: render is not defined`. `api.rerender` cannot handle
  `ssrRender` either - it assigns whatever it is given to `render` and
  re-renders the mounted instances, which a server build does not have. The
  new function is written back to the component definition instead, which is
  the object parent modules hold on to.
- The imported-type watch hook only invalidated `clientCache`, so a server
  build kept rendering with the stale compiled script after an imported prop
  type changed. Both caches are invalidated now.

Tests drive a real watching server build: they render through
`vue/server-renderer`, edit a template or an imported type, apply the hot
update the way a dev server would, and render again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants