[2.0 rc.13] Compiled SSR without a spread writes `style=""` / `class=""` for a nullish `style` / `class`
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
- In an empty directory:
npm i [email protected] @solidjs/[email protected] @solidjs/[email protected] @solidjs/[email protected] @babel/core - Save this as
repro.mjsin the same directory and runnode 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/compilerand@solidjs/babel-pluginfrom npm. Same output onnextat e44b2e4 with a locally built Babel plugin (I didn't build the Oxc binary there). The cited files are unchanged on the currentnext(4738c6d).
Additional context
- A possible fix: when the
style/classexpression isn't a literal (so it may be nullish), emit the whole attribute as one hole, asserverComponentsdoes withssrElementAttribute(key, x), which skips nullish (server.ts#L4637) and prints the samefilledoutput above. Object literals keep their inlined path. It touches both compilers and the parity fixtures. serverComponents: trueisn't a workaround, since it also stops inlining object literals. A spread on the element is one: the attribute then goes throughssrElementand is left out.- A static
class={undefined}rendersclass=""too, though #3382 assumed the compiler omits it (serverComponentsdoes). - Not part of this:
false,""and an all-nullish object givestyle=""in every mode, spread andserverComponentsincluded, per the server rule that only nullish means unset. The client also writesclass=""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
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de solidjs/solid
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 55/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 58/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
Los mantenedores suelen responder en 1 día
Todos los issues de solidjs/solid
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
umbraco/Umbraco-CMS-MCP-Dev#512 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
wimpysworld/sidra#290 ·
Los mantenedores suelen responder en 1 día
-
defuFn invokes function values for inherited default propertiesPosiblemente ocupada @xiehuanyi la tomó hoy. Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
-
feature request good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
TabularisDB/tabularis#853 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 Menos de una hora Aptitud para principiantes 85/100
capricorn86/happy-dom#2474 ·
Los mantenedores suelen responder en 2 días