diff --git a/packages/docs/src/entry.ssr.tsx b/packages/docs/src/entry.ssr.tsx index 74385c84d9c..7840ad731d3 100644 --- a/packages/docs/src/entry.ssr.tsx +++ b/packages/docs/src/entry.ssr.tsx @@ -5,9 +5,6 @@ import { Root } from './root'; export function render(opts: RenderToStringOptions) { return renderToString(, { manifest, - qwikLoader: { - include: 'body', - }, ...opts, }); } diff --git a/packages/qwik/src/server/api.md b/packages/qwik/src/server/api.md index b425fda5d9f..22999df59ba 100644 --- a/packages/qwik/src/server/api.md +++ b/packages/qwik/src/server/api.md @@ -131,7 +131,7 @@ export interface RenderToStringOptions extends SerializeDocumentOptions { prefetchStrategy?: PrefetchStrategy | null; qwikLoader?: { events?: string[]; - include?: boolean | 'body' | 'head'; + include?: boolean | 'top' | 'bottom'; }; snapshot?: boolean; } diff --git a/packages/qwik/src/server/prefetch-implementation.ts b/packages/qwik/src/server/prefetch-implementation.ts index d491d32c1dc..94a999011dd 100644 --- a/packages/qwik/src/server/prefetch-implementation.ts +++ b/packages/qwik/src/server/prefetch-implementation.ts @@ -7,6 +7,7 @@ import type { export function applyPrefetchImplementation( doc: QwikDocument, + parentElm: Element, opts: RenderToStringOptions, prefetchResources: PrefetchResource[] ) { @@ -19,21 +20,22 @@ export function applyPrefetchImplementation( prefetchImpl === 'link-preload-html' || prefetchImpl === 'link-modulepreload-html' ) { - linkHtmlImplementation(doc, prefetchResources, prefetchImpl); + linkHtmlImplementation(doc, parentElm, prefetchResources, prefetchImpl); } else if ( prefetchImpl === 'link-prefetch' || prefetchImpl === 'link-preload' || prefetchImpl === 'link-modulepreload' ) { - linkJsImplementation(doc, prefetchResources, prefetchImpl); + linkJsImplementation(doc, parentElm, prefetchResources, prefetchImpl); } else if (prefetchImpl === 'worker-fetch') { - workerFetchImplementation(doc, prefetchResources); + workerFetchImplementation(doc, parentElm, prefetchResources); } } } function linkHtmlImplementation( doc: QwikDocument, + parentElm: Element, prefetchResources: PrefetchResource[], prefetchImpl: PrefetchImplementation ) { @@ -57,12 +59,13 @@ function linkHtmlImplementation( } } - doc.body.appendChild(linkElm); + parentElm.appendChild(linkElm); } } function linkJsImplementation( doc: QwikDocument, + parentElm: Element, prefetchResources: PrefetchResource[], prefetchImpl: PrefetchImplementation ) { @@ -103,7 +106,7 @@ function linkJsImplementation( const script = doc.createElement('script'); script.setAttribute('type', 'module'); script.innerHTML = s; - doc.body.appendChild(script); + parentElm.appendChild(script); } function workerFetchScript() { @@ -122,14 +125,18 @@ function workerFetchScript() { return s; } -function workerFetchImplementation(doc: QwikDocument, prefetchResources: PrefetchResource[]) { +function workerFetchImplementation( + doc: QwikDocument, + parentElm: Element, + prefetchResources: PrefetchResource[] +) { let s = `const u=${JSON.stringify(flattenPrefetchResources(prefetchResources))};`; s += workerFetchScript(); const script = doc.createElement('script'); script.setAttribute('type', 'module'); script.innerHTML = s; - doc.body.appendChild(script); + parentElm.appendChild(script); } export function flattenPrefetchResources(prefetchResources: PrefetchResource[]) { diff --git a/packages/qwik/src/server/render.ts b/packages/qwik/src/server/render.ts index 840844d4a54..6c054699ce3 100644 --- a/packages/qwik/src/server/render.ts +++ b/packages/qwik/src/server/render.ts @@ -11,6 +11,7 @@ import { getPrefetchResources } from './prefetch-strategy'; import { _createDocument } from './document'; import type { SymbolMapper } from '../optimizer/src/types'; import { getCanonicalSymbol } from '../core/import/qrl-class'; +import { isDocument } from '../core/util/element'; /** * Creates a server-side `document`, renders to root node to the document, @@ -19,43 +20,45 @@ import { getCanonicalSymbol } from '../core/import/qrl-class'; */ export async function renderToString(rootNode: any, opts: RenderToStringOptions = {}) { const createDocTimer = createTimer(); - const doc = _createDocument(opts); + const doc = _createDocument(opts) as Document; const createDocTime = createDocTimer(); - const renderDocTimer = createTimer(); - let rootEl: Element | Document = doc; + let root: Element | Document = doc; if (typeof opts.fragmentTagName === 'string') { if (opts.qwikLoader) { - opts.qwikLoader.include = false; + if (opts.qwikLoader.include === undefined) { + opts.qwikLoader.include = false; + } } else { opts.qwikLoader = { include: false }; } - rootEl = doc.createElement(opts.fragmentTagName); - doc.body.appendChild(rootEl); + root = doc.createElement(opts.fragmentTagName); + doc.body.appendChild(root); } + const isFullDocument = isDocument(root); const mapper = computeSymbolMapper(opts.manifest); - await setServerPlatform(doc, opts, mapper); - await render(doc, rootNode); + await render(root, rootNode); const renderDocTime = renderDocTimer(); const buildBase = getBuildBase(opts); - const containerEl = getElement(doc); + const containerEl = getElement(root); containerEl.setAttribute('q:base', buildBase); let snapshotResult: SnapshotResult | null = null; if (opts.snapshot !== false) { - snapshotResult = pauseContainer(doc); + snapshotResult = pauseContainer(root); } const prefetchResources = getPrefetchResources(snapshotResult, opts, mapper); + const parentElm = isFullDocument ? doc.body : containerEl; if (prefetchResources.length > 0) { - applyPrefetchImplementation(doc, opts, prefetchResources); + applyPrefetchImplementation(doc, parentElm, opts, prefetchResources); } const includeLoader = - !opts.qwikLoader || opts.qwikLoader.include === undefined ? 'head' : opts.qwikLoader.include; + !opts.qwikLoader || opts.qwikLoader.include === undefined ? 'bottom' : opts.qwikLoader.include; if (includeLoader) { const qwikLoaderScript = getQwikLoaderScript({ events: opts.qwikLoader?.events, @@ -64,10 +67,12 @@ export async function renderToString(rootNode: any, opts: RenderToStringOptions const scriptElm = doc.createElement('script') as HTMLScriptElement; scriptElm.setAttribute('id', 'qwikloader'); scriptElm.textContent = qwikLoaderScript; - if (includeLoader === 'body') { - doc.body.appendChild(scriptElm); - } else { + if (includeLoader === 'bottom') { + parentElm.appendChild(scriptElm); + } else if (isFullDocument) { doc.head.appendChild(scriptElm); + } else { + parentElm.insertBefore(scriptElm, parentElm.firstChild); } } @@ -76,7 +81,7 @@ export async function renderToString(rootNode: any, opts: RenderToStringOptions const result: RenderToStringResult = { prefetchResources, snapshotResult, - html: serializeDocument(rootEl, opts), + html: serializeDocument(root, opts), timing: { createDocument: createDocTime, render: renderDocTime, diff --git a/packages/qwik/src/server/types.ts b/packages/qwik/src/server/types.ts index 5444a475d1d..2879fb88933 100644 --- a/packages/qwik/src/server/types.ts +++ b/packages/qwik/src/server/types.ts @@ -91,7 +91,7 @@ export interface RenderToStringOptions extends SerializeDocumentOptions { /** * Specifies if the Qwik Loader script is added to the document or not. Defaults to `{ include: true }`. */ - qwikLoader?: { events?: string[]; include?: boolean | 'body' | 'head' }; + qwikLoader?: { events?: string[]; include?: boolean | 'top' | 'bottom' }; prefetchStrategy?: PrefetchStrategy | null; /** diff --git a/packages/qwik/src/testing/api.md b/packages/qwik/src/testing/api.md index b425fda5d9f..22999df59ba 100644 --- a/packages/qwik/src/testing/api.md +++ b/packages/qwik/src/testing/api.md @@ -131,7 +131,7 @@ export interface RenderToStringOptions extends SerializeDocumentOptions { prefetchStrategy?: PrefetchStrategy | null; qwikLoader?: { events?: string[]; - include?: boolean | 'body' | 'head'; + include?: boolean | 'top' | 'bottom'; }; snapshot?: boolean; } diff --git a/starters/apps/e2e/src/components/containers/container.tsx b/starters/apps/e2e/src/components/containers/container.tsx index f42ff821e03..b6812da1c79 100644 --- a/starters/apps/e2e/src/components/containers/container.tsx +++ b/starters/apps/e2e/src/components/containers/container.tsx @@ -15,7 +15,7 @@ export const Containers = component$(() => { ); }); -export const Container = component$((props: ContainerProps) => { +export const Container = component$(async (props: ContainerProps) => { useScopedStyles$(` .container { margin: 20px; @@ -36,10 +36,9 @@ export const Container = component$((props: ContainerProps) => { } `); const url = `http://localhost:3300${props.url}?fragment&loader=false`; - // const { default: fetch } = await import('node-fetch'); - // const res = await fetch(url); - // const html = await res.text(); - const html = ''; + const { default: fetch } = await import('node-fetch'); + const res = await fetch(url); + const html = await res.text(); return (
{url}
diff --git a/starters/apps/e2e/src/entry.ssr.tsx b/starters/apps/e2e/src/entry.ssr.tsx index 62ef5321479..75f51c5b245 100644 --- a/starters/apps/e2e/src/entry.ssr.tsx +++ b/starters/apps/e2e/src/entry.ssr.tsx @@ -47,7 +47,7 @@ export function render(opts: RenderToStringOptions) { , { debug: true, - fragmentTagName: 'div', + fragmentTagName: 'container', qwikLoader: { include: url.searchParams.get('loader') !== 'false', events: ['click'],