Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 52 additions & 53 deletions packages/qwik/src/core/client/dom-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
QScopedStyle,
QStyle,
QStyleSelector,
QStylesAllSelector,
Q_PROPS_SEPARATOR,
USE_ON_LOCAL_SEQ_IDX,
getQFuncs,
Expand All @@ -47,23 +48,22 @@ import {
} from './types';
import { mapArray_get, mapArray_has, mapArray_set } from './util-mapArray';
import {
VNodeJournalOpCode,
vnode_applyJournal,
vnode_createErrorDiv,
vnode_getDomParent,
vnode_getProps,
vnode_getProp,
vnode_insertBefore,
vnode_isElementVNode,
vnode_isVNode,
vnode_isVirtualVNode,
vnode_locate,
vnode_newUnMaterializedElement,
vnode_setProp,
type VNodeJournal,
} from './vnode';
import type { ElementVNode, VNode, VirtualVNode } from './vnode-impl';
import type { ElementVNode } from '../shared/vnode/element-vnode';
import type { VNode } from '../shared/vnode/vnode';
import type { VirtualVNode } from '../shared/vnode/virtual-vnode';

/** @public */
export function getDomContainer(element: Element | VNode): IClientContainer {
export function getDomContainer(element: Element): IClientContainer {
const qContainerElement = _getQContainerElement(element);
if (!qContainerElement) {
throw qError(QError.containerNotFound);
Expand All @@ -73,19 +73,12 @@ export function getDomContainer(element: Element | VNode): IClientContainer {

export function getDomContainerFromQContainerElement(qContainerElement: Element): IClientContainer {
const qElement = qContainerElement as ContainerElement;
let container = qElement.qContainer;
if (!container) {
container = new DomContainer(qElement);
}
return container;
return (qElement.qContainer ||= new DomContainer(qElement));
}

/** @internal */
export function _getQContainerElement(element: Element | VNode): Element | null {
const qContainerElement: Element | null = vnode_isVNode(element)
? (vnode_getDomParent(element, true) as Element)
: element;
return qContainerElement.closest(QContainerSelector);
export function _getQContainerElement(element: Element): Element | null {
return element.closest(QContainerSelector);
}

export const isDomContainer = (container: any): container is DomContainer => {
Expand All @@ -99,7 +92,6 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
public qManifestHash: string;
public rootVNode: ElementVNode;
public document: QDocument;
public $journal$: VNodeJournal;
public $rawStateData$: unknown[];
public $storeProxyMap$: ObjToProxyMap = new WeakMap();
public $qFuncs$: Array<(...args: unknown[]) => unknown>;
Expand All @@ -111,29 +103,14 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
private $styleIds$: Set<string> | null = null;

constructor(element: ContainerElement) {
super(
() => {
this.$flushEpoch$++;
vnode_applyJournal(this.$journal$);
},
{},
element.getAttribute(QLocaleAttr)!
);
super({}, element.getAttribute(QLocaleAttr)!);
this.qContainer = element.getAttribute(QContainerAttr)!;
if (!this.qContainer) {
throw qError(QError.elementWithoutContainer);
}
this.$journal$ = [
// The first time we render we need to hoist the styles.
// (Meaning we need to move all styles from component inline to <head>)
// We bulk move all of the styles, because the expensive part is
// for the browser to recompute the styles, (not the actual DOM manipulation.)
// By moving all of them at once we can minimize the reflow.
VNodeJournalOpCode.HoistStyles,
element.ownerDocument,
];
this.document = element.ownerDocument as QDocument;
this.element = element;
this.$hoistStyles$();
this.$buildBase$ = element.getAttribute(QBaseAttr)!;
this.$instanceHash$ = element.getAttribute(QInstanceAttr)!;
this.qManifestHash = element.getAttribute(QManifestHashAttr)!;
Expand All @@ -160,7 +137,24 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
}
}

$setRawState$(id: number, vParent: ElementVNode | VirtualVNode): void {
/**
* The first time we render we need to hoist the styles. (Meaning we need to move all styles from
* component inline to <head>)
*
* We bulk move all of the styles, because the expensive part is for the browser to recompute the
* styles, (not the actual DOM manipulation.) By moving all of them at once we can minimize the
* reflow.
*/
$hoistStyles$(): void {
const document = this.element.ownerDocument;
const head = document.head;
const styles = document.querySelectorAll(QStylesAllSelector);
for (let i = 0; i < styles.length; i++) {
head.appendChild(styles[i]);
}
}

$setRawState$(id: number, vParent: VNode): void {
this.$stateData$[id] = vParent;
}

Expand All @@ -171,17 +165,21 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
handleError(err: any, host: VNode | null): void {
if (qDev && host) {
if (typeof document !== 'undefined') {
const vHost = host as VirtualVNode;
const journal: VNodeJournal = [];
const vHost = host;
const vHostParent = vHost.parent;
const vHostNextSibling = vHost.nextSibling as VNode | null;
const vErrorDiv = vnode_createErrorDiv(document, vHost, err, journal);
const journal: VNodeJournal = [];
const vErrorDiv = vnode_createErrorDiv(journal, document, vHost, err);
// If the host is an element node, we need to insert the error div into its parent.
const insertHost = vnode_isElementVNode(vHost) ? vHostParent || vHost : vHost;
// If the host is different then we need to insert errored-host in the same position as the host.
const insertBefore = insertHost === vHost ? null : vHostNextSibling;
vnode_insertBefore(journal, insertHost, vErrorDiv, insertBefore);
vnode_applyJournal(journal);
vnode_insertBefore(
journal,
insertHost as ElementVNode | VirtualVNode,
vErrorDiv,
insertBefore
);
}

if (err && err instanceof Error) {
Expand Down Expand Up @@ -223,7 +221,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
let vNode: VNode | null = host.parent;
while (vNode) {
if (vnode_isVirtualVNode(vNode)) {
if (vNode.getProp(OnRenderProp, null) !== null) {
if (vnode_getProp(vNode, OnRenderProp, null) !== null) {
return vNode;
}
vNode =
Expand All @@ -239,7 +237,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {

setHostProp<T>(host: HostElement, name: string, value: T): void {
const vNode: VirtualVNode = host as any;
vNode.setProp(name, value);
vnode_setProp(vNode, name, value);
}

getHostProp<T>(host: HostElement, name: string): T | null {
Expand All @@ -258,20 +256,21 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
getObjectById = parseInt;
break;
}
return vNode.getProp(name, getObjectById);
return vnode_getProp(vNode, name, getObjectById);
}

ensureProjectionResolved(vNode: VirtualVNode): void {
if ((vNode.flags & VNodeFlags.Resolved) === 0) {
vNode.flags |= VNodeFlags.Resolved;
const props = vnode_getProps(vNode);
for (let i = 0; i < props.length; i = i + 2) {
const prop = props[i] as string;
if (isSlotProp(prop)) {
const value = props[i + 1];
if (typeof value == 'string') {
const projection = this.vNodeLocate(value);
props[i + 1] = projection;
const props = vNode.props;
if (props) {
for (const prop of Object.keys(props)) {
if (isSlotProp(prop)) {
const value = props[prop];
if (typeof value == 'string') {
const projection = this.vNodeLocate(value);
props[prop] = projection;
}
}
}
}
Expand Down Expand Up @@ -307,7 +306,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
const styleElement = this.document.createElement('style');
styleElement.setAttribute(QStyle, styleId);
styleElement.textContent = content;
this.$journal$.push(VNodeJournalOpCode.Insert, this.document.head, null, styleElement);
this.document.head.appendChild(styleElement);
}
}

Expand Down
13 changes: 8 additions & 5 deletions packages/qwik/src/core/client/dom-render.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { FunctionComponent, JSXNode, JSXOutput } from '../shared/jsx/types/jsx-node';
import type { FunctionComponent, JSXOutput } from '../shared/jsx/types/jsx-node';
import { isDocument, isElement } from '../shared/utils/element';
import { ChoreType } from '../shared/util-chore-type';
import { QContainerValue } from '../shared/types';
import { DomContainer, getDomContainer } from './dom-container';
import { cleanup } from './vnode-diff';
import { QContainerAttr } from '../shared/utils/markers';
import { NODE_DIFF_DATA_KEY, QContainerAttr } from '../shared/utils/markers';
import type { RenderOptions, RenderResult } from './types';
import { qDev } from '../shared/utils/qdev';
import { QError, qError } from '../shared/error/error';
import { vnode_setProp } from './vnode';
import { markVNodeDirty } from '../shared/vnode/vnode-dirty';
import { ChoreBits } from '../shared/vnode/enums/chore-bits.enum';

/**
* Render JSX.
Expand Down Expand Up @@ -42,8 +44,9 @@ export const render = async (
const container = getDomContainer(parent as HTMLElement) as DomContainer;
container.$serverData$ = opts.serverData || {};
const host = container.rootVNode;
container.$scheduler$(ChoreType.NODE_DIFF, host, host, jsxNode as JSXNode);
await container.$scheduler$(ChoreType.WAIT_FOR_QUEUE).$returnValue$;
vnode_setProp(host, NODE_DIFF_DATA_KEY, jsxNode);
markVNodeDirty(container, host, ChoreBits.NODE_DIFF);
await container.$renderPromise$;
return {
cleanup: () => {
cleanup(container, container.rootVNode);
Expand Down
27 changes: 8 additions & 19 deletions packages/qwik/src/core/client/run-qrl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { QError, qError } from '../shared/error/error';
import type { QRLInternal } from '../shared/qrl/qrl-class';
import { getChorePromise } from '../shared/scheduler';
import { ChoreType } from '../shared/util-chore-type';
import { retryOnPromise } from '../shared/utils/promises';
import type { ValueOrPromise } from '../shared/utils/types';
import { getInvokeContext } from '../use/use-core';
import { useLexicalScope } from '../use/use-lexical-scope.public';
import { getDomContainer } from './dom-container';
import { VNodeFlags } from './types';

/**
* This is called by qwik-loader to run a QRL. It has to be synchronous.
Expand All @@ -17,20 +15,11 @@ export const _run = (...args: unknown[]): ValueOrPromise<unknown> => {
const [runQrl] = useLexicalScope<[QRLInternal<(...args: unknown[]) => unknown>]>();
const context = getInvokeContext();
const hostElement = context.$hostElement$;

if (!hostElement) {
// silently ignore if there is no host element, the element might have been removed
return;
if (hostElement) {
return retryOnPromise(() => {
if (!(hostElement.flags & VNodeFlags.Deleted)) {
return runQrl(...args);
}
});
}

const container = getDomContainer(context.$element$!);

const scheduler = container.$scheduler$;
if (!scheduler) {
throw qError(QError.schedulerNotFound);
}

// We don't return anything, the scheduler is in charge now
const chore = scheduler(ChoreType.RUN_QRL, hostElement, runQrl, args);
return getChorePromise(chore);
};
42 changes: 21 additions & 21 deletions packages/qwik/src/core/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import type { QRL } from '../shared/qrl/qrl.public';
import type { Container } from '../shared/types';
import type { VNodeJournal } from './vnode';
import type { ElementVNode, VirtualVNode } from './vnode-impl';
import type { ElementVNode } from '../shared/vnode/element-vnode';
import type { VirtualVNode } from '../shared/vnode/virtual-vnode';

export type ClientAttrKey = string;
export type ClientAttrValue = string | null;
Expand All @@ -17,9 +17,7 @@ export interface ClientContainer extends Container {
$locale$: string;
qManifestHash: string;
rootVNode: ElementVNode;
$journal$: VNodeJournal;
$forwardRefs$: Array<number> | null;
$flushEpoch$: number;
parseQRL<T = unknown>(qrl: string): QRL<T>;
$setRawState$(id: number, vParent: ElementVNode | VirtualVNode): void;
}
Expand Down Expand Up @@ -74,30 +72,32 @@ export interface QDocument extends Document {
* @internal
*/
export const enum VNodeFlags {
Element /* ****************** */ = 0b00_000001,
Virtual /* ****************** */ = 0b00_000010,
ELEMENT_OR_VIRTUAL_MASK /* ** */ = 0b00_000011,
Text /* ********************* */ = 0b00_000100,
ELEMENT_OR_TEXT_MASK /* ***** */ = 0b00_000101,
TYPE_MASK /* **************** */ = 0b00_000111,
INFLATED_TYPE_MASK /* ******* */ = 0b00_001111,
Element /* ****************** */ = 0b00_0000001,
Virtual /* ****************** */ = 0b00_0000010,
ELEMENT_OR_VIRTUAL_MASK /* ** */ = 0b00_0000011,
Text /* ********************* */ = 0b00_0000100,
ELEMENT_OR_TEXT_MASK /* ***** */ = 0b00_0000101,
TYPE_MASK /* **************** */ = 0b00_0000111,
INFLATED_TYPE_MASK /* ******* */ = 0b00_0001111,
/// Extra flag which marks if a node needs to be inflated.
Inflated /* ***************** */ = 0b00_001000,
Inflated /* ***************** */ = 0b00_0001000,
/// Marks if the `ensureProjectionResolved` has been called on the node.
Resolved /* ***************** */ = 0b00_010000,
Resolved /* ***************** */ = 0b00_0010000,
/// Marks if the vnode is deleted.
Deleted /* ****************** */ = 0b00_100000,
Deleted /* ****************** */ = 0b00_0100000,
/// Marks if the vnode is a cursor (has priority set).
Cursor /* ******************* */ = 0b00_1000000,
/// Flags for Namespace
NAMESPACE_MASK /* *********** */ = 0b11_000000,
NEGATED_NAMESPACE_MASK /* ** */ = ~0b11_000000,
NS_html /* ****************** */ = 0b00_000000, // http://www.w3.org/1999/xhtml
NS_svg /* ******************* */ = 0b01_000000, // http://www.w3.org/2000/svg
NS_math /* ****************** */ = 0b10_000000, // http://www.w3.org/1998/Math/MathML
NAMESPACE_MASK /* *********** */ = 0b11_0000000,
NEGATED_NAMESPACE_MASK /* ** */ = ~0b11_0000000,
NS_html /* ****************** */ = 0b00_0000000, // http://www.w3.org/1999/xhtml
NS_svg /* ******************* */ = 0b01_0000000, // http://www.w3.org/2000/svg
NS_math /* ****************** */ = 0b10_0000000, // http://www.w3.org/1998/Math/MathML
}

export const enum VNodeFlagsIndex {
mask /* ************** */ = 0b11_111111,
shift /* ************* */ = 8,
mask /* ************** */ = 0b11_1111111,
shift /* ************* */ = 9,
}

export const enum VNodeProps {
Expand Down
Loading