swc-project / swc-project/plugins

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

Open Beginner friendly
#655 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
389
Forks
106
Avg merge
10m
Merged PRs (30d)
1

Description

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 #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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in the SWC plugin code at transform_import_expr and the has_ssr_false handling introduced by #592; compare it with the pre-#592 behavior and the @loadable/babel-plugin reference. Reproduce the supplied ssr:false case under ChunkExtractor, then verify the call is transformed with metadata and no SSR invariant is raised.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, rust
Domain
compilers, tooling
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.