Skip to content

lazy() / $$moduleUrl pipeline skipped for plain .ts/.js modules #406

Description

@ryansolid

Summary

On next, transform() in src/index.ts returns early for any file that is not .jsx / .tsx / .tsrx (or a custom extensions entry):

if (!(/\.[mc]?[tj]sx$/i.test(id) || isTsrx || allExtensions.includes(currentFileExtension))) {
  // Not a JSX module. The one pass that still applies is primitive
  // naming — `createSignal` lives in `.ts`/`.js` as much as in
  // components — and it runs alone: no lazy/refresh/JSX work.
  if (namePrimitives && PRIMITIVES_ONLY_MODULE.test(id) && !DECLARATION_MODULE.test(id)) {
    // transformSourceNames, then return
  }
  return null;
}

PRIMITIVES_ONLY_MODULE is /\.[mc]?[jt]s$/i (.ts / .js and the m/c variants, not .d.ts). On that path the only pass is transformSourceNames. The hook then returns null. It never runs transformLazy, resolveLazyModuleUrls, solid-refresh, or the SSR injectSsrModuleId that appends export const $$moduleUrl.

The same module written as .tsx goes through all of those after primitive naming.

routes.ts

A routes.ts full of lazy(() => import(...)) therefore gets no module URL, and the lazy targets never get $$moduleUrl. SSR cannot resolve assets or preload those modules from the route table.

This is the plugin half of solidjs/solid#3908. lazyModule landed in solid (solidjs/solid#3941). The plugin still does not annotate non-JSX files, so a route module that is plain .ts still cannot participate. #3908 is the solid-side feature; this issue is the gate in the plugin.

@solidjs/compiler's transformLazy already accepts .ts / .js when it is given a filename. The parser dialect comes from the path, and the pass is a no-op only when filename is omitted. The options tests pass filename: "src/a.js". The gate is the plugin, not the compiler.

Distinct from #390 (a leading slash on a hand-written moduleUrl never resolves) and #299 (query strings dropped from the SSR lazy lookup). Those assume the annotation was emitted.

"use server" should be on this path

transformDirectives also lives in @solidjs/compiler and accepts plain .ts / .js. This transform() never calls it. Vite runs it from a second plugin, serverFunctions() in src/server-functions/index.ts, whose default include is already src/**/*.{jsx,tsx,tsrx,ts,js,mjs,cjs}. So "use server" is the only one of these passes that escaped the JSX gate, and it did so by living in a different plugin. That split is weird.

A non-JSX module should get primitive names, the lazy / $$moduleUrl pipeline, refresh, and "use server" from the same plugin. Do not leave "use server" as the only pass that runs on .ts / .js because a second plugin happens to include those extensions.

Expected

One non-JSX pipeline, in this plugin:

  • primitive names (transformSourceNames)
  • lazy / $$moduleUrl (transformLazy, resolveLazyModuleUrls, SSR injectSsrModuleId)
  • refresh
  • "use server" (transformDirectives)

Activity

  1. ryansolid commented on Oct 9, 2026

    @ryansolid
    MemberAuthor

    Fixed in #407.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions