Skip to content

Commit d4de268

Browse files
committed
fix(ssr): inject qwikloader only where allowed
1 parent 44ea824 commit d4de268

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

packages/qwik/src/core/tests/render-api.spec.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,43 @@ describe('render api', () => {
437437
'(window.qwikevents||(window.qwikevents=[]))'
438438
);
439439
});
440+
it('should not render inside template', async () => {
441+
const bigText = 'hello world '.repeat(3000); // ~30kB of text
442+
const result = await renderToStringAndSetPlatform(
443+
<div>
444+
<Counter />
445+
<table>
446+
<tbody>
447+
<tr>
448+
<td>
449+
<template>
450+
<div>{bigText}</div>
451+
{bigText}
452+
</template>
453+
</td>
454+
</tr>
455+
<tr>
456+
<td>Before here is safe</td>
457+
</tr>
458+
</tbody>
459+
</table>
460+
</div>,
461+
{
462+
containerTagName: 'div',
463+
qwikLoader: 'inline',
464+
}
465+
);
466+
const document = createDocument({ html: result.html });
467+
expect(document.querySelectorAll('script[id=qwikloader]')).toHaveLength(1);
468+
const notQwikLoaderScriptElement = document.body.firstChild?.lastChild
469+
?.previousSibling as HTMLElement;
470+
expect(notQwikLoaderScriptElement?.id).not.toEqual('qwikloader');
471+
// qwik events should still be the last script of body
472+
const eventsScriptElement = document.body.lastChild as HTMLElement;
473+
expect(eventsScriptElement.textContent).toContain(
474+
'(window.qwikevents||(window.qwikevents=[]))'
475+
);
476+
});
440477
});
441478
it('should support never render', async () => {
442479
const result = await renderToStringAndSetPlatform(<Counter />, {

packages/qwik/src/server/ssr-container.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,16 +380,24 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
380380
return this.closeElement();
381381
}
382382

383+
private $noScriptHere$ = 0;
384+
383385
/** Renders opening tag for DOM element */
384386
openElement(
385387
elementName: string,
386388
varAttrs: SsrAttrs | null,
387389
constAttrs?: SsrAttrs | null,
388390
currentFile?: string | null
389391
): string | undefined {
390-
if (this.qlInclude === QwikLoaderInclude.Inline && this.size > 30 * 1024) {
391-
// We waited long enough, on slow connections the page is already partially visible
392-
this.emitQwikLoaderInline();
392+
if (this.qlInclude === QwikLoaderInclude.Inline) {
393+
if (this.$noScriptHere$ === 0 && this.size > 30 * 1024) {
394+
// We waited long enough, on slow connections the page is already partially visible
395+
this.emitQwikLoaderInline();
396+
}
397+
// keep track of noscript and template
398+
else if (elementName === 'noscript' || elementName === 'template') {
399+
this.$noScriptHere$++;
400+
}
393401
}
394402

395403
let innerHTML: string | undefined = undefined;
@@ -482,6 +490,12 @@ class SSRContainer extends _SharedContainer implements ISSRContainer {
482490
this.write('>');
483491
}
484492
this.lastNode = null;
493+
if (this.qlInclude === QwikLoaderInclude.Inline) {
494+
// keep track of noscript and template
495+
if (elementName === 'noscript' || elementName === 'template') {
496+
this.$noScriptHere$--;
497+
}
498+
}
485499
}
486500

487501
/** Writes opening data to vNodeData for fragment boundaries */

packages/qwik/src/server/tag-nesting.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ function isInTable(text: string): TagNesting {
223223
case 'tbody':
224224
case 'tfoot':
225225
return TagNesting.TABLE_BODY;
226+
case 'script':
227+
return TagNesting.TEXT;
226228
default:
227229
return TagNesting.NOT_ALLOWED;
228230
}
@@ -232,6 +234,8 @@ function isInTableBody(text: string): TagNesting {
232234
switch (text) {
233235
case 'tr':
234236
return TagNesting.TABLE_ROW;
237+
case 'script':
238+
return TagNesting.TEXT;
235239
default:
236240
return TagNesting.NOT_ALLOWED;
237241
}
@@ -242,6 +246,8 @@ function isInTableRow(text: string): TagNesting {
242246
case 'td':
243247
case 'th':
244248
return TagNesting.ANYTHING;
249+
case 'script':
250+
return TagNesting.TEXT;
245251
default:
246252
return TagNesting.NOT_ALLOWED;
247253
}
@@ -251,6 +257,8 @@ function isInTableColGroup(text: string): TagNesting {
251257
switch (text) {
252258
case 'col':
253259
return TagNesting.EMPTY;
260+
case 'script':
261+
return TagNesting.TEXT;
254262
default:
255263
return TagNesting.NOT_ALLOWED;
256264
}
@@ -262,6 +270,8 @@ function isInPicture(text: string): TagNesting {
262270
return TagNesting.EMPTY;
263271
case 'img':
264272
return TagNesting.EMPTY;
273+
case 'script':
274+
return TagNesting.TEXT;
265275
default:
266276
return TagNesting.NOT_ALLOWED;
267277
}
@@ -331,7 +341,6 @@ function isInPhrasing(text: string, allowInput: boolean): TagNesting {
331341
case 'ruby':
332342
case 's':
333343
case 'samp':
334-
case 'script':
335344
case 'select':
336345
case 'slot':
337346
case 'small':
@@ -346,6 +355,7 @@ function isInPhrasing(text: string, allowInput: boolean): TagNesting {
346355
case 'video':
347356
case 'wbr':
348357
return allowInput ? TagNesting.PHRASING_ANY : TagNesting.PHRASING_INSIDE_INPUT;
358+
case 'script':
349359
case 'style':
350360
return TagNesting.TEXT;
351361
case 'picture':

0 commit comments

Comments
 (0)