Skip to content

[compiler] validate rejects document-shell templates (<html><head><body>) — fatal in client compiles since rc.5 made validate failures errors #3259

Description

@TylerRick

Summary

The validate pass round-trips each DOM template through a body-context fragment parse and
compares serializations. A template whose root is <html> (a root document: `…

…`) can never pass that comparison — body-context `innerHTML` parsing strips the ``/``/`` wrappers, so the "browser" serialization never matches the input, regardless of whether the markup is actually well-formed.

Through 2.0.0-rc.4 this printed the malformed-HTML warning and emitted anyway. Since 2.0.0-rc.5
(#3099 — the right call for genuine restructuring) it is a compile error, so a module containing
root-document JSX fails to compile at all in generate: 'dom', hydratable: false mode. Merely
importing the module is fatal; no template has to be instantiated.

The validator already handles context-sensitive fragments: <td>/<tr>/<col>/table components
are wrapped in a synthetic <table> before parsing. ^<html> needs the analogous treatment
(validate in the document context, or skip/downgrade for document shells).

Where this bites: apps whose root component owns the document shell — e.g. a TanStack Solid Start
root route — when that component is compiled in plain client mode, which is the natural
configuration for a jsdom/vitest component-test project. The route tree transitively imports the
root route, so the whole test import chain dies. (Dev/build pipelines using hydratable client
compiles are unaffected, which hides the problem until someone runs component tests.)

Two smaller observations while here:

  • The thrown error appends (This is an error on an internal node. Probably an internal error.)
    instead of a source code frame, so this configuration still loses the user-code location
    promised by [compiler] validate warns but still emits the broken template walk #3099.
  • hydratable: true client compiles do not hit the error for the same input, so the mode that
    actually hydrates real documents is already outside the check's reach.

Reproduction

// repro.mjs — pnpm add @babel/core @babel/plugin-syntax-jsx @solidjs/babel-plugin@2.0.0-rc.6
import { transformAsync } from '@babel/core';
import solid from '@solidjs/babel-plugin';
import jsx from '@babel/plugin-syntax-jsx';

const src = `
export function Root(props) {
	return (
		<html>
			<head><title>App</title></head>
			<body><div id="app">{props.children}</div></body>
		</html>
	);
}
`;

for (const opts of [
	{ generate: 'dom', hydratable: false }, // THROWS — the bug
	{ generate: 'dom', hydratable: true }, // OK
	{ generate: 'ssr', hydratable: true }, // OK
]) {
	try {
		await transformAsync(src, {
			filename: 'root.tsx',
			plugins: [jsx, [solid, { moduleName: '@solidjs/web', ...opts }]],
		});
		console.log(JSON.stringify(opts), '→ OK');
	} catch (e) {
		console.log(JSON.stringify(opts), '→ THROWS:', e.message.split('\n')[0]);
	}
}

Save as repro.mjs and run node repro.mjs. Tested on Linux with Node 24.19.0; this is a
compile-time issue, so no browser is involved.

Output at 2.0.0-rc.6:

{"generate":"dom","hydratable":false} → THROWS: /path/to/root.tsx: The HTML provided is malformed and will yield unexpected output when evaluated by a browser.
{"generate":"dom","hydratable":true} → OK
{"generate":"ssr","hydratable":true} → OK

At 2.0.0-rc.3/rc.4 the same input prints the diagnostic as a warning and compiles.

Expected behavior

A well-formed document-shell template validates (parsed in the document context, the way table
fragments are parsed in a synthetic table), or at minimum the document-shell case degrades to the
pre-#3099 warning instead of failing the compile.

Activity

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