Skip to content

Commit

Permalink
WIPfix: don't put qwik in server (ok) or testing (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
wmertens committed Oct 18, 2024
1 parent 1d42b17 commit ac1acc1
Show file tree
Hide file tree
Showing 41 changed files with 577 additions and 521 deletions.
41 changes: 41 additions & 0 deletions packages/qwik/src/core/client/diffJsx-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { JSXNode } from '@builder.io/qwik';
import { Fragment, Slot } from '@builder.io/qwik';

export function tagToString(tag: any): string {
return tag === Fragment ? 'Fragment' : tag === Slot ? 'Slot' : String(tag);
}

export function attrsEqual(expectedValue: any, receivedValue: any) {
const isEqual =
typeof expectedValue == 'boolean'
? expectedValue
? receivedValue !== null
: receivedValue === null || receivedValue === 'false'
: expectedValue == receivedValue;
// console.log('attrsEqual', expectedValue, receivedValue, isEqual);
return isEqual;
}

export function getJSXChildren(jsx: JSXNode): JSXNode[] {
const children = jsx.children;
if (Array.isArray(children)) {
return children as any;
} else if (children != null) {
return [children] as any;
}
return [];
}

export function jsxToHTML(jsx: JSXNode, pad: string = ''): string {
const html: string[] = [];
if (jsx.type) {
html.push(pad, '<', tagToString(jsx.type), '>\n');
getJSXChildren(jsx).forEach((jsx) => {
html.push(jsxToHTML(jsx, pad + ' '));
});
html.push(pad, '</', tagToString(jsx.type), '>\n');
} else {
html.push(pad, JSON.stringify(jsx), '\n');
}
return html.join('');
}
172 changes: 172 additions & 0 deletions packages/qwik/src/core/client/diffJsxVNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import type { JSXNode, _ElementVNode, _TextVNode, _VNode } from '@builder.io/qwik';
import { Fragment } from '@builder.io/qwik';
import {
vnode_getAttr,
vnode_getAttrKeys,
vnode_getElementName,
vnode_getFirstChild,
vnode_getNextSibling,
vnode_getNode,
vnode_getText,
vnode_isElementVNode,
vnode_isTextVNode,
vnode_isVirtualVNode,
} from './vnode';
import { serializeBooleanOrNumberAttribute } from '../shared/utils/styles';
import { isHtmlAttributeAnEventName, isJsxPropertyAnEventName } from '../shared/utils/event-names';
import { Q_PROPS_SEPARATOR } from '../shared/utils/markers';
import { attrsEqual, getJSXChildren, jsxToHTML, tagToString } from './diffJsx-utils';

export function getVNodeChildren(vNode: _VNode): _VNode[] {
const children: _VNode[] = [];
let child = vnode_getFirstChild(vNode);
while (child) {
if (!shouldSkip(child)) {
children.push(child);
}
child = vnode_getNextSibling(child);
}
return children;
}

export function diffJsxVNode(
received: _VNode,
expected: JSXNode | string,
path: string[] = []
): string[] {
if (!received) {
return [path.join(' > ') + ' missing'];
}
const diffs: string[] = [];
if (typeof expected === 'string') {
const receivedText = vnode_isTextVNode(received) ? vnode_getText(received as _TextVNode) : null;
if (expected !== receivedText) {
diffs.push(path.join(' > '));
diffs.push('EXPECTED', JSON.stringify(expected));
diffs.push('RECEIVED:', JSON.stringify(receivedText));
}
} else {
path.push(tagToString(expected.type));
const receivedTag = vnode_isElementVNode(received)
? vnode_getElementName(received as _ElementVNode)
: vnode_isVirtualVNode(received)
? Fragment
: undefined;
const isTagSame = String(expected.type).toLowerCase() == String(receivedTag).toLowerCase();
if (!isTagSame) {
diffs.push(path.join(' > ') + ' expecting=' + expected.type + ' received=' + receivedTag);
}
const allProps: string[] = [];
expected.varProps && propsAdd(allProps, Object.keys(expected.varProps));
expected.constProps && propsAdd(allProps, Object.keys(expected.constProps));
const receivedElement = vnode_isElementVNode(received)
? (vnode_getNode(received) as Element)
: null;
propsAdd(allProps, vnode_isElementVNode(received) ? vnode_getAttrKeys(received).sort() : []);
receivedElement && propsAdd(allProps, constPropsFromElement(receivedElement));
allProps.sort();
allProps.forEach((prop) => {
if (isJsxPropertyAnEventName(prop) || isHtmlAttributeAnEventName(prop)) {
return;
}
// we need this, because Domino lowercases all attributes for `element.attributes`
const propLowerCased = prop.toLowerCase();
let receivedValue =
vnode_getAttr(received, prop) ||
vnode_getAttr(received, propLowerCased) ||
receivedElement?.getAttribute(prop) ||
receivedElement?.getAttribute(propLowerCased);
let expectedValue =
prop === 'key' || prop === 'q:key' ? (expected.key ?? receivedValue) : expected.props[prop];
if (typeof receivedValue === 'boolean' || typeof receivedValue === 'number') {
receivedValue = serializeBooleanOrNumberAttribute(receivedValue);
}
if (typeof expectedValue === 'number') {
expectedValue = serializeBooleanOrNumberAttribute(expectedValue);
}
if (!attrsEqual(expectedValue, receivedValue)) {
diffs.push(`${path.join(' > ')}: [${prop}]`);
diffs.push(' EXPECTED: ' + JSON.stringify(expectedValue));
diffs.push(' RECEIVED: ' + JSON.stringify(receivedValue));
}
});
const receivedChildren = getVNodeChildren(received);
const expectedChildren = getJSXChildren(expected);
if (receivedChildren.length === expectedChildren.length) {
for (let i = 0; i < receivedChildren.length; i++) {
const receivedChild = receivedChildren[i];
const expectedChild = expectedChildren[i];
diffs.push(...diffJsxVNode(receivedChild, expectedChild, path));
}
} else {
diffs.push(
`${path.join(' > ')} expecting ${expectedChildren.length} children but was ${receivedChildren.length}`
);
diffs.push('EXPECTED', jsxToHTML(expected, ' '));
diffs.push('RECEIVED:', vnodeToHTML(received, ' '));
}
path.pop();
}
return diffs;
}

function vnodeToHTML(vNode: _VNode | null, pad: string = ''): string {
const html: string[] = [];
while (vNode) {
html.push(
pad +
vNode
.toString()
.split('\n')
.join('\n' + pad)
);
while (shouldSkip((vNode = vnode_getNextSibling(vNode!)))) {
// skip
}
}
return html.join('');
}

function propsAdd(existing: string[], incoming: string[]) {
for (const prop of incoming) {
if (prop !== 'children') {
let found = false;
for (let i = 0; i < existing.length; i++) {
if (existing[i].toLowerCase() === prop.toLowerCase()) {
found = true;
break;
}
}
if (!found) {
existing.push(prop);
}
}
}
}

function constPropsFromElement(element: Element) {
const props: string[] = [];
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
if (attr.name !== '' && attr.name !== Q_PROPS_SEPARATOR) {
props.push(attr.name);
}
}
props.sort();
return props;
}

function shouldSkip(vNode: _VNode | null) {
if (vNode && vnode_isElementVNode(vNode)) {
const tag = vnode_getElementName(vNode);
if (
tag === 'script' &&
(vnode_getAttr(vNode, 'type') === 'qwik/vnode' ||
vnode_getAttr(vNode, 'type') === 'x-qwik/vnode' ||
vnode_getAttr(vNode, 'type') === 'qwik/state')
) {
return true;
}
}
return false;
}
3 changes: 1 addition & 2 deletions packages/qwik/src/core/client/dom-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ import {
} from './types';
import {
VNodeJournalOpCode,
mapArray_get,
mapArray_set,
vnode_applyJournal,
vnode_getDOMChildNodes,
vnode_getDomParent,
Expand All @@ -69,6 +67,7 @@ import {
vnode_setProp,
type VNodeJournal,
} from './vnode';
import { mapArray_get, mapArray_set } from './mapArray';
import { vnode_diff } from './vnode-diff';

/** @public */
Expand Down
70 changes: 70 additions & 0 deletions packages/qwik/src/core/client/mapArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { assertTrue } from '../shared/error/assert';

export const mapApp_findIndx = <T>(
elementVNode: (T | null)[],
key: string,
start: number
): number => {
assertTrue(start % 2 === 0, 'Expecting even number.');
let bottom = (start as number) >> 1;
let top = (elementVNode.length - 2) >> 1;
while (bottom <= top) {
const mid = bottom + ((top - bottom) >> 1);
const midKey = elementVNode[mid << 1] as string;
if (midKey === key) {
return mid << 1;
}
if (midKey < key) {
bottom = mid + 1;
} else {
top = mid - 1;
}
}
return (bottom << 1) ^ -1;
};

export const mapArray_set = <T>(
elementVNode: (T | null)[],
key: string,
value: T | null,
start: number
) => {
const indx = mapApp_findIndx(elementVNode, key, start);
if (indx >= 0) {
if (value == null) {
elementVNode.splice(indx, 2);
} else {
elementVNode[indx + 1] = value;
}
} else if (value != null) {
elementVNode.splice(indx ^ -1, 0, key as any, value);
}
};

export const mapApp_remove = <T>(
elementVNode: (T | null)[],
key: string,
start: number
): T | null => {
const indx = mapApp_findIndx(elementVNode, key, start);
let value: T | null = null;
if (indx >= 0) {
value = elementVNode[indx + 1];
elementVNode.splice(indx, 2);
return value;
}
return value;
};

export const mapArray_get = <T>(
elementVNode: (T | null)[],
key: string,
start: number
): T | null => {
const indx = mapApp_findIndx(elementVNode, key, start);
if (indx >= 0) {
return elementVNode[indx + 1] as T | null;
} else {
return null;
}
};
4 changes: 2 additions & 2 deletions packages/qwik/src/core/client/vnode-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ import {
type VirtualVNode,
} from './types';
import {
mapApp_findIndx,
mapArray_set,
vnode_ensureElementInflated,
vnode_getAttr,
vnode_getDomParentVNode,
Expand Down Expand Up @@ -90,6 +88,8 @@ import {
vnode_walkVNode,
type VNodeJournal,
} from './vnode';
import { mapApp_findIndx } from './mapArray';
import { mapArray_set } from './mapArray';
import { getNewElementNamespaceData } from './vnode-namespace';
import { WrappedSignal, EffectProperty, isSignal, EffectData } from '../signal/signal';
import type { Signal } from '../signal/signal.public';
Expand Down
Loading

0 comments on commit ac1acc1

Please sign in to comment.