Skip to content

[loadable-components] Skipping transform for ssr: false breaks SSR with every published @loadable/component #655

Description

@Themezv

Summary

Since #592 ("fix(loadable-components): skip transformation when ssr: false is specified"), @swc/plugin-loadable-components no longer transforms loadable(fn, { ssr: false }) call sites. This breaks server-side rendering at runtime for every published version of @loadable/component (checked 5.15.3 and latest 5.16.7): the server throws

Invariant Violation: loadable: SSR requires `@loadable/babel-plugin`, please install it

for any ssr: false component rendered under a ChunkExtractor, turning whole pages into HTTP 500s.

The skip is present in all releases containing #592, including the current latest 11.19.0.

Root cause

@loadable/component's runtime checks the transform metadata before it looks at options.ssr. From dist/cjs/loadable.cjs.js (identical in 5.15.3 and 5.16.7):

function InnerLoadable(props) {
  // ...
  invariant(!props.__chunkExtractor || ctor.requireSync,
    'SSR requires `@loadable/babel-plugin`, please install it'); // <-- fires first

  if (props.__chunkExtractor) {
    // This module has been marked with no SSR
    if (options.ssr === false) {
      return _assertThisInitialized(_this);   // <-- ssr:false handled only after the invariant
    }
    // ...
  }
}

So under SSR (__chunkExtractor present), an untransformed ssr: false call site hits the invariant and throws. ssr: false is a runtime rendering decision (render the fallback on the server); it does not mean the call site may be left untransformed.

The reference implementation agrees: @loadable/babel-plugin (5.16.0, latest) transforms these call sites unconditionally — its source does not mention ssr at all. The client-side runtime also uses the metadata (isReady/chunkName) that the transform provides.

Reproduction

Input:

import loadable from '@loadable/component';
const A = loadable(() => import('./a'), { ssr: false });
export default A;

Transform with @swc/plugin-loadable-components >= the release containing #592:

  • Actual: the call is left untouched (no requireAsync/requireSync/chunkName object). Rendering <A /> inside ChunkExtractor.collectChunks() then throws Invariant Violation: loadable: SSR requires @loadable/babel-plugin → 500.
  • Expected (matches @loadable/babel-plugin and this plugin before fix(loadable-components): skip transformation when ssr: false is specified #592): the first argument is replaced with the metadata object; at runtime ssr: false makes the server render the fallback, as documented.

The same input through @loadable/babel-plugin@5.16.0 produces the full transform.

Note on #465

#592 was made to fix #465 ("loadable-components plugin does not support ssr false"), but the linked docs feature ("Disable SSR on a specific loadable") does not require skipping the transform — with @loadable/babel-plugin the call site is transformed and the runtime handles ssr: false by rendering the fallback on the server. Skipping the transform is what actually breaks ssr: false support: before #592 the plugin handled ssr: false exactly like babel does; after #592 it crashes SSR.

Suggested fix

Revert #592 (i.e. remove has_ssr_false and the early return in transform_import_expr). If skipping is ever desired as an optimization, it can only be done together with a runtime that checks options.ssr === false before the invariant — no published @loadable/component does that.

Happy to send a PR with the revert.

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