Skip to content

Commit 20beda4

Browse files
authored
fix: <head> rerendering (#819)
fixes #816 fixes #815
1 parent fdd526a commit 20beda4

File tree

2 files changed

+40
-16
lines changed

2 files changed

+40
-16
lines changed

packages/qwik/src/core/render/cursor.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { getDocument } from '../util/dom';
3737
import { directGetAttribute, directSetAttribute } from './fast-calls';
3838
import { HOST_TYPE, SKIP_RENDER_TYPE } from './jsx/jsx-runtime';
3939
import { assertQrl } from '../import/qrl-class';
40+
import { isElement } from '../util/element';
4041

4142
export const SVG_NS = 'http://www.w3.org/2000/svg';
4243

@@ -60,7 +61,7 @@ export interface RenderOperation {
6061
$fn$: () => void;
6162
}
6263

63-
export type ChildrenMode = 'root' | 'slot' | 'fallback' | 'default';
64+
export type ChildrenMode = 'root' | 'slot' | 'fallback' | 'default' | 'head';
6465

6566
/**
6667
* @alpha
@@ -97,6 +98,10 @@ export const smartUpdateChildren = (
9798
}
9899
ch = ch[0].$children$;
99100
}
101+
const isHead = elm.nodeName === 'HEAD';
102+
if (isHead) {
103+
mode = 'head';
104+
}
100105
const oldCh = getChildren(elm, mode);
101106
if (qDev) {
102107
if (elm.nodeType === 9) {
@@ -110,9 +115,9 @@ export const smartUpdateChildren = (
110115
}
111116
}
112117
if (oldCh.length > 0 && ch.length > 0) {
113-
return updateChildren(ctx, elm, oldCh, ch, isSvg);
118+
return updateChildren(ctx, elm, oldCh, ch, isSvg, isHead);
114119
} else if (ch.length > 0) {
115-
return addVnodes(ctx, elm, null, ch, 0, ch.length - 1, isSvg);
120+
return addVnodes(ctx, elm, null, ch, 0, ch.length - 1, isSvg, isHead);
116121
} else if (oldCh.length > 0) {
117122
return removeVnodes(ctx, oldCh, 0, oldCh.length - 1);
118123
}
@@ -123,7 +128,8 @@ export const updateChildren = (
123128
parentElm: Node,
124129
oldCh: Node[],
125130
newCh: ProcessedJSXNode[],
126-
isSvg: boolean
131+
isSvg: boolean,
132+
isHead: boolean
127133
): ValueOrPromise<void> => {
128134
let oldStartIdx = 0;
129135
let newStartIdx = 0;
@@ -176,7 +182,7 @@ export const updateChildren = (
176182
idxInOld = oldKeyToIdx[newStartVnode.$key$ as string];
177183
if (idxInOld === undefined) {
178184
// New element
179-
const newElm = createElm(ctx, newStartVnode, isSvg);
185+
const newElm = createElm(ctx, newStartVnode, isSvg, isHead);
180186
results.push(
181187
then(newElm, (newElm) => {
182188
insertBefore(ctx, parentElm, newElm, oldStartVnode);
@@ -185,7 +191,7 @@ export const updateChildren = (
185191
} else {
186192
elmToMove = oldCh[idxInOld];
187193
if (!isTagName(elmToMove, newStartVnode.$type$)) {
188-
const newElm = createElm(ctx, newStartVnode, isSvg);
194+
const newElm = createElm(ctx, newStartVnode, isSvg, isHead);
189195
results.push(
190196
then(newElm, (newElm) => {
191197
insertBefore(ctx, parentElm, newElm, oldStartVnode);
@@ -203,7 +209,7 @@ export const updateChildren = (
203209

204210
if (newStartIdx <= newEndIdx) {
205211
const before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].$elm$;
206-
results.push(addVnodes(ctx, parentElm, before, newCh, newStartIdx, newEndIdx, isSvg));
212+
results.push(addVnodes(ctx, parentElm, before, newCh, newStartIdx, newEndIdx, isSvg, isHead));
207213
}
208214

209215
let wait = promiseAll(results) as any;
@@ -236,6 +242,8 @@ export const getChildren = (elm: Node, mode: ChildrenMode): Node[] => {
236242
return getCh(elm, isChildComponent);
237243
case 'fallback':
238244
return getCh(elm, isFallback);
245+
case 'head':
246+
return getCh(elm, isHeadChildren);
239247
}
240248
};
241249
export const isNode = (elm: Node): boolean => {
@@ -247,6 +255,10 @@ const isFallback = (node: Node): boolean => {
247255
return node.nodeName === 'Q:FALLBACK';
248256
};
249257

258+
const isHeadChildren = (node: Node): boolean => {
259+
return isElement(node) && (node.hasAttribute('q:head') || node.nodeName === 'TITLE');
260+
};
261+
250262
const isChildSlot = (node: Node): boolean => {
251263
return isNode(node) && node.nodeName !== 'Q:FALLBACK' && node.nodeName !== 'Q:TEMPLATE';
252264
};
@@ -370,13 +382,14 @@ const addVnodes = (
370382
vnodes: ProcessedJSXNode[],
371383
startIdx: number,
372384
endIdx: number,
373-
isSvg: boolean
385+
isSvg: boolean,
386+
isHead: boolean
374387
): ValueOrPromise<void> => {
375388
const promises = [];
376389
for (; startIdx <= endIdx; ++startIdx) {
377390
const ch = vnodes[startIdx];
378391
assertDefined(ch, 'render: node must be defined at index', startIdx, vnodes);
379-
promises.push(createElm(ctx, ch, isSvg));
392+
promises.push(createElm(ctx, ch, isSvg, isHead));
380393
}
381394
return then(promiseAll(promises), (children) => {
382395
for (const child of children) {
@@ -501,7 +514,8 @@ const getSlotName = (node: ProcessedJSXNode): string => {
501514
const createElm = (
502515
rctx: RenderContext,
503516
vnode: ProcessedJSXNode,
504-
isSvg: boolean
517+
isSvg: boolean,
518+
isHead: boolean
505519
): ValueOrPromise<Node> => {
506520
rctx.$perf$.$visited$++;
507521
const tag = vnode.$type$;
@@ -523,6 +537,9 @@ const createElm = (
523537
const ctx = getContext(elm);
524538
setKey(elm, vnode.$key$);
525539
updateProperties(rctx, ctx, props, isSvg, false);
540+
if (isHead) {
541+
directSetAttribute(elm as Element, 'q:head', '');
542+
}
526543

527544
if (isSvg && tag === 'foreignObject') {
528545
isSvg = false;
@@ -565,7 +582,7 @@ const createElm = (
565582
const slotRctx = copyRenderContext(rctx);
566583
slotRctx.$contexts$.push(ctx);
567584
const slotMap = isComponent ? getSlots(ctx.$component$, elm) : undefined;
568-
const promises = children.map((ch) => createElm(slotRctx, ch, isSvg));
585+
const promises = children.map((ch) => createElm(slotRctx, ch, isSvg, false));
569586
return then(promiseAll(promises), () => {
570587
let parent = elm;
571588
for (const node of children) {
@@ -845,12 +862,15 @@ const insertBefore = <T extends Node>(
845862
export const appendStyle = (ctx: RenderContext, hostElement: Element, styleTask: StyleAppend) => {
846863
const fn = () => {
847864
const containerEl = ctx.$containerEl$;
848-
const stylesParent =
849-
ctx.$doc$.documentElement === containerEl ? ctx.$doc$.head ?? containerEl : containerEl;
865+
const isDoc = ctx.$doc$.documentElement === containerEl && !!ctx.$doc$.head;
850866
const style = ctx.$doc$.createElement('style');
851867
directSetAttribute(style, 'q:style', styleTask.styleId);
852868
style.textContent = styleTask.content;
853-
stylesParent.insertBefore(style, stylesParent.firstChild);
869+
if (isDoc) {
870+
ctx.$doc$.head.appendChild(style);
871+
} else {
872+
containerEl.insertBefore(style, containerEl.firstChild);
873+
}
854874
};
855875
ctx.$operations$.push({
856876
$el$: hostElement,

packages/qwik/src/core/render/render.public.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,15 @@ const renderRoot = async (
9090
};
9191
export const injectQwikSlotCSS = (docOrElm: Document | Element) => {
9292
const doc = getDocument(docOrElm);
93-
const element = isDocument(docOrElm) ? docOrElm.head : docOrElm;
93+
const isDoc = isDocument(docOrElm);
9494
const style = doc.createElement('style');
9595
directSetAttribute(style, 'id', 'qwik/base-styles');
9696
style.textContent = `q\\:slot{display:contents}q\\:fallback,q\\:template{display:none}q\\:fallback:last-child{display:contents}`;
97-
element.insertBefore(style, element.firstChild);
97+
if (isDoc) {
98+
docOrElm.head.appendChild(style);
99+
} else {
100+
docOrElm.insertBefore(style, docOrElm.firstChild);
101+
}
98102
};
99103

100104
export const getElement = (docOrElm: Document | Element): Element => {

0 commit comments

Comments
 (0)