Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

[2.0 rc.13] Compiled SSR without a spread writes `style=""` / `class=""` for a nullish `style` / `class`

Cerrado
#3,773 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
3/5
Tiempo estimado
1-2 días
Aptitud para principiantes
45/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
rust, typescript
Área
backend

Línea de trabajo

Start by examining the SSR template generation in packages/babel-plugin/src/ssr/element.ts around lines 754-755 and packages/compiler/src/ssr/transform.rs around lines 2233-2241, where static style="" and class="" attributes are emitted. Compare with ssrElementAttribute in packages/web/src/server.ts around line 4637, which correctly omits nullish values. The fix should make the non-spread path skip nullish style/class values to match the spread path behavior. Update parity fixtures if they exist, and run the reproduction script from the issue to verify.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Describe the bug

Low priority. On an intrinsic element without a spread, a dynamic style or class that is undefined or null renders as style="" / class="", while title with the same value is omitted. With serverComponents: true both are omitted as well.

This is the template path of #3382. #3393 fixed ssrElement (the spread path) and says "Neither compiler emits these attributes; this is runtime-only". Without a spread they do: both compilers put style=" / class=" and the closing quote in the static template (element.ts#L754-L755, transform.rs#L2233-L2241), and ssrStyle / ssrClassName fill the gap with "" for a nullish value (server.ts#L4281, #L4257). Since the quotes are template bytes, changing only those two helpers can't drop the attribute.

In Chromium 153 with style-src 'self' this shows up as a style-src-attr violation (sample ""; the console offers the hash of the empty string). Chromium reports any style="" that way, a bare <div style=""> included, and class="" doesn't trip it, so the repro skips the browser. Hydration keeps the attribute: style leaves the DOM alone while hydrating (client.ts#L860).

Your Example Website or App

Self-contained Node script under Steps (published packages, no bundler).

Steps to Reproduce the Bug or Issue
  1. In an empty directory: npm i [email protected] @solidjs/[email protected] @solidjs/[email protected] @solidjs/[email protected] @babel/core
  2. Save this as repro.mjs in the same directory and run node repro.mjs:
import { writeFileSync } from "node:fs";
import { transformSync } from "@babel/core";
import { transform } from "@solidjs/compiler";
import { renderToString } from "@solidjs/web";

const source = `export const App = p => <div style={p.style} class={p.class} title={p.title} />;`;
const compilers = {
  oxc: options => transform(source, { filename: "app.jsx", ...options }).code,
  babel: options =>
    transformSync(source, {
      filename: "app.jsx",
      babelrc: false,
      configFile: false,
      plugins: [["@solidjs/babel-plugin", options]]
    }).code
};
const cases = {
  undefined: { style: undefined, class: undefined, title: undefined },
  null: { style: null, class: null, title: null },
  filled: { style: { color: "red" }, class: "a", title: "t" }
};

for (const serverComponents of [false, true]) {
  const results = {};
  for (const [name, compile] of Object.entries(compilers)) {
    const code = compile({ moduleName: "@solidjs/web", generate: "ssr", hydratable: true, serverComponents });
    const file = new URL(`app-${name}-${serverComponents}.mjs`, import.meta.url);
    writeFileSync(file, code);
    const { App } = await import(file.href);
    const template = JSON.parse(code.match(/_tmpl\$ = (\[[\s\S]*?\]);/)[1]);
    const lines = [`  template:  ${JSON.stringify(template)}`];
    for (const [label, props] of Object.entries(cases)) {
      lines.push(`  ${label.padEnd(10)} ${renderToString(() => App(props))}`);
    }
    results[name] = lines.join("\n");
  }
  const same = results.oxc === results.babel;
  console.log(`\nserverComponents: ${serverComponents} (oxc and babel ${same ? "print the same" : "differ"})`);
  console.log(same ? results.oxc : `oxc:\n${results.oxc}\nbabel:\n${results.babel}`);
}

Output:

serverComponents: false (oxc and babel print the same)
  template:  ["<div"," style=\"","\" class=\"","\"","></div>"]
  undefined  <div _hk=0 style="" class=""></div>
  null       <div _hk=0 style="" class=""></div>
  filled     <div _hk=0 style="color:red" class="a" title="t"></div>

serverComponents: true (oxc and babel print the same)
  template:  ["<div","","","","></div>"]
  undefined  <div _hk=0></div>
  null       <div _hk=0></div>
  filled     <div _hk=0 style="color:red" class="a" title="t"></div>
Expected behavior

A nullish dynamic style or class is left out of the SSR output, like title, the spread path since #3393 and a client render.

Screenshots or Videos

N/A.

Platform
  • OS: macOS 27.0
  • Browser: Chromium 153.0.8010.12 (Playwright 1.63.0), CSP check only
  • Node.js: v24.21.0
  • Version: 2.0.0-rc.13 of solid-js, @solidjs/web, @solidjs/compiler and @solidjs/babel-plugin from npm. Same output on next at e44b2e4 with a locally built Babel plugin (I didn't build the Oxc binary there). The cited files are unchanged on the current next (4738c6d).
Additional context
  • A possible fix: when the style / class expression isn't a literal (so it may be nullish), emit the whole attribute as one hole, as serverComponents does with ssrElementAttribute(key, x), which skips nullish (server.ts#L4637) and prints the same filled output above. Object literals keep their inlined path. It touches both compilers and the parity fixtures.
  • serverComponents: true isn't a workaround, since it also stops inlining object literals. A spread on the element is one: the attribute then goes through ssrElement and is left out.
  • A static class={undefined} renders class="" too, though #3382 assumed the compiler omits it (serverComponents does).
  • Not part of this: false, "" and an all-nullish object give style="" in every mode, spread and serverComponents included, per the server rule that only nullish means unset. The client also writes class="" for a dynamic empty-string class (client.ts#L777-L780).
  • inlineStyles: false (the answer in solidjs/solid-start#1996) doesn't change the SSR template.
Lenguaje dominante
TypeScript
Estrellas
36.1k
Forks
1.1k
Merge medio
10 h 7 min
PR fusionados (30 d)
289

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de solidjs/solid

Todos los issues de solidjs/solid

Issues similares

Más issues de TypeScript

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.