diff --git a/packages/qwik/src/core/tests/render-api.spec.tsx b/packages/qwik/src/core/tests/render-api.spec.tsx index fc48a7fe972..e6ffe3ad3af 100644 --- a/packages/qwik/src/core/tests/render-api.spec.tsx +++ b/packages/qwik/src/core/tests/render-api.spec.tsx @@ -437,6 +437,43 @@ describe('render api', () => { '(window.qwikevents||(window.qwikevents=[]))' ); }); + it('should not render inside template', async () => { + const bigText = 'hello world '.repeat(3000); // ~30kB of text + const result = await renderToStringAndSetPlatform( +
+ + + + + + + + + + +
+ +
Before here is safe
+
, + { + containerTagName: 'div', + qwikLoader: 'inline', + } + ); + const document = createDocument({ html: result.html }); + expect(document.querySelectorAll('script[id=qwikloader]')).toHaveLength(1); + const notQwikLoaderScriptElement = document.body.firstChild?.lastChild + ?.previousSibling as HTMLElement; + expect(notQwikLoaderScriptElement?.id).not.toEqual('qwikloader'); + // qwik events should still be the last script of body + const eventsScriptElement = document.body.lastChild as HTMLElement; + expect(eventsScriptElement.textContent).toContain( + '(window.qwikevents||(window.qwikevents=[]))' + ); + }); }); it('should support never render', async () => { const result = await renderToStringAndSetPlatform(, { diff --git a/packages/qwik/src/server/ssr-container.ts b/packages/qwik/src/server/ssr-container.ts index 0e2bb3b5f2a..730806853cd 100644 --- a/packages/qwik/src/server/ssr-container.ts +++ b/packages/qwik/src/server/ssr-container.ts @@ -380,6 +380,8 @@ class SSRContainer extends _SharedContainer implements ISSRContainer { return this.closeElement(); } + private $noScriptHere$ = 0; + /** Renders opening tag for DOM element */ openElement( elementName: string, @@ -387,9 +389,15 @@ class SSRContainer extends _SharedContainer implements ISSRContainer { constAttrs?: SsrAttrs | null, currentFile?: string | null ): string | undefined { - if (this.qlInclude === QwikLoaderInclude.Inline && this.size > 30 * 1024) { - // We waited long enough, on slow connections the page is already partially visible - this.emitQwikLoaderInline(); + if (this.qlInclude === QwikLoaderInclude.Inline) { + if (this.$noScriptHere$ === 0 && this.size > 30 * 1024) { + // We waited long enough, on slow connections the page is already partially visible + this.emitQwikLoaderInline(); + } + // keep track of noscript and template + else if (elementName === 'noscript' || elementName === 'template') { + this.$noScriptHere$++; + } } let innerHTML: string | undefined = undefined; @@ -482,6 +490,12 @@ class SSRContainer extends _SharedContainer implements ISSRContainer { this.write('>'); } this.lastNode = null; + if (this.qlInclude === QwikLoaderInclude.Inline) { + // keep track of noscript and template + if (elementName === 'noscript' || elementName === 'template') { + this.$noScriptHere$--; + } + } } /** Writes opening data to vNodeData for fragment boundaries */ diff --git a/packages/qwik/src/server/tag-nesting.ts b/packages/qwik/src/server/tag-nesting.ts index dde7787fb90..b1a35036682 100644 --- a/packages/qwik/src/server/tag-nesting.ts +++ b/packages/qwik/src/server/tag-nesting.ts @@ -223,6 +223,8 @@ function isInTable(text: string): TagNesting { case 'tbody': case 'tfoot': return TagNesting.TABLE_BODY; + case 'script': + return TagNesting.TEXT; default: return TagNesting.NOT_ALLOWED; } @@ -232,6 +234,8 @@ function isInTableBody(text: string): TagNesting { switch (text) { case 'tr': return TagNesting.TABLE_ROW; + case 'script': + return TagNesting.TEXT; default: return TagNesting.NOT_ALLOWED; } @@ -242,6 +246,8 @@ function isInTableRow(text: string): TagNesting { case 'td': case 'th': return TagNesting.ANYTHING; + case 'script': + return TagNesting.TEXT; default: return TagNesting.NOT_ALLOWED; } @@ -251,6 +257,8 @@ function isInTableColGroup(text: string): TagNesting { switch (text) { case 'col': return TagNesting.EMPTY; + case 'script': + return TagNesting.TEXT; default: return TagNesting.NOT_ALLOWED; } @@ -262,6 +270,8 @@ function isInPicture(text: string): TagNesting { return TagNesting.EMPTY; case 'img': return TagNesting.EMPTY; + case 'script': + return TagNesting.TEXT; default: return TagNesting.NOT_ALLOWED; } @@ -331,7 +341,6 @@ function isInPhrasing(text: string, allowInput: boolean): TagNesting { case 'ruby': case 's': case 'samp': - case 'script': case 'select': case 'slot': case 'small': @@ -346,6 +355,7 @@ function isInPhrasing(text: string, allowInput: boolean): TagNesting { case 'video': case 'wbr': return allowInput ? TagNesting.PHRASING_ANY : TagNesting.PHRASING_INSIDE_INPUT; + case 'script': case 'style': return TagNesting.TEXT; case 'picture':