Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bind-in-jsx #6989

Draft
wants to merge 4 commits into
base: build/v2
Choose a base branch
from
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
12 changes: 6 additions & 6 deletions packages/docs/src/repl/bundled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import terserJs from '../../node_modules/terser/dist/bundle.min.js?raw-source';
import qWasmCjs from '../../node_modules/@qwik.dev/core/bindings/qwik.wasm.cjs?raw-source';
import qWasmBinUrl from '../../node_modules/@qwik.dev/core/bindings/qwik_wasm_bg.wasm?raw-source';
import qBuild from '../../node_modules/@qwik.dev/core/dist/build/index.d.ts?raw-source';
import qCoreCjs from '../../node_modules/@qwik.dev/core/dist/core.cjs?raw-source';
import qCoreCjs from '../../node_modules/@qwik.dev/core/dist/core.qwik.cjs?raw-source';
import qCoreDts from '../../node_modules/@qwik.dev/core/dist/core.d.ts?raw-source';
import qCoreMinMjs from '../../node_modules/@qwik.dev/core/dist/core.min.mjs?raw-source';
import qCoreMjs from '../../node_modules/@qwik.dev/core/dist/core.mjs?raw-source';
import qCoreMinMjs from '../../node_modules/@qwik.dev/core/dist/core.min.qwik.mjs?raw-source';
import qCoreMjs from '../../node_modules/@qwik.dev/core/dist/core.qwik.mjs?raw-source';
import qOptimizerCjs from '../../node_modules/@qwik.dev/core/dist/optimizer.cjs?raw-source';
import qServerCjs from '../../node_modules/@qwik.dev/core/dist/server.cjs?raw-source';
import qServerDts from '../../node_modules/@qwik.dev/core/dist/server.d.ts?raw-source';
Expand Down Expand Up @@ -48,10 +48,10 @@ export const bundled: PkgUrls = {
[QWIK_PKG_NAME]: {
version: qwikVersion,
'/dist/build/index.d.ts': qBuild,
'/dist/core.cjs': qCoreCjs,
'/dist/core.qwik.cjs': qCoreCjs,
'/dist/core.d.ts': qCoreDts,
'/dist/core.min.mjs': qCoreMinMjs,
'/dist/core.mjs': qCoreMjs,
'/dist/core.min.qwik.mjs': qCoreMinMjs,
'/dist/core.qwik.mjs': qCoreMjs,
'/dist/optimizer.cjs': qOptimizerCjs,
'/dist/server.cjs': qServerCjs,
'/dist/server.d.ts': qServerDts,
Expand Down
7 changes: 4 additions & 3 deletions packages/docs/src/repl/repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,14 @@ const getDependencies = (input: ReplAppInput) => {
if (input.version !== 'bundled') {
const [M, m, p] = input.version.split('-')[0].split('.').map(Number);
const prefix = M > 1 || (M == 1 && (m > 7 || (m == 7 && p >= 2))) ? '/dist/' : '/';
const qwik = M > 1 || (M == 1 && m >= 9) ? '.qwik' : '';
out[QWIK_PKG_NAME] = {
version: input.version,
};
for (const p of [
`${prefix}core.cjs`,
`${prefix}core.mjs`,
`${prefix}core.min.mjs`,
`${prefix}core${qwik}.cjs`,
`${prefix}core${qwik}.mjs`,
`${prefix}core.min${qwik}.mjs`,
`${prefix}optimizer.cjs`,
`${prefix}server.cjs`,
`/bindings/qwik.wasm.cjs`,
Expand Down
7 changes: 5 additions & 2 deletions packages/docs/src/repl/worker/repl-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ let cache: Cache;
export const depResponse = async (pkgName: string, pkgPath: string) => {
if (pkgName === QWIK_PKG_NAME) {
const version = options.deps[pkgName].version;
const [M, m, p] = version.split('-')[0].split('.').map(Number);
if (!pkgPath.startsWith('/bindings')) {
const [M, m, p] = version.split('-')[0].split('.').map(Number);
if (M > 1 || (M == 1 && (m > 7 || (m == 7 && p >= 2)))) {
pkgPath = `/dist${pkgPath}`;
}
if (version < '2') {
pkgName = '@builder.io/qwik';
}
}
if (!(M > 2 || (M === 1 && (m > 8 || (m === 8 && version.includes('-dev')))))) {
pkgPath = pkgPath.replace('.qwik', '');
}
}
const url = options.deps[pkgName][pkgPath];
if (!url) {
Expand Down Expand Up @@ -76,7 +79,7 @@ const _loadDependencies = async (replOptions: ReplInputOptions) => {
}

if (!isSameQwikVersion(self.qwikCore?.version)) {
await exec(QWIK_PKG_NAME, '/core.cjs');
await exec(QWIK_PKG_NAME, '/core.qwik.cjs');
if (self.qwikCore) {
console.debug(`Loaded @qwik.dev/core: ${self.qwikCore.version}`);
} else {
Expand Down
8 changes: 4 additions & 4 deletions packages/docs/src/repl/worker/repl-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's
id === '@qwik.dev/core/jsx-runtime' ||
id === '@qwik.dev/core/jsx-dev-runtime'
) {
return '\0qwikCore';
return '/core.qwik.mjs';
}
if (id === '@builder.io/qwik/server' || id === '@qwik.dev/core/server') {
return '\0qwikServer';
Expand All @@ -48,22 +48,22 @@ export const replResolver = (options: ReplInputOptions, buildMode: 'client' | 's
return input.code;
}
if (buildMode === 'ssr') {
if (id === '\0qwikCore') {
if (id === '/core.qwik.mjs') {
return getRuntimeBundle('qwikCore');
}
if (id === '\0qwikServer') {
return getRuntimeBundle('qwikServer');
}
}
if (id === '\0qwikCore') {
if (id === '/core.qwik.mjs') {
if (options.buildMode === 'production') {
const rsp = await depResponse('@qwik.dev/core', '/core.min.mjs');
if (rsp) {
return rsp.text();
}
}

const rsp = await depResponse('@qwik.dev/core', '/core.mjs');
const rsp = await depResponse('@qwik.dev/core', '/core.qwik.mjs');
if (rsp) {
return rsp.text();
}
Expand Down
42 changes: 21 additions & 21 deletions packages/qwik/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@
".": {
"types": "./dist/core.d.ts",
"import": {
"development": "./dist/core.mjs",
"production": "./dist/core.prod.mjs",
"default": "./dist/core.prod.mjs",
"min": "./dist/core.min.mjs"
"development": "./dist/core.qwik.mjs",
"production": "./dist/core.prod.qwik.mjs",
"default": "./dist/core.prod.qwik.mjs",
"min": "./dist/core.min.qwik.mjs"
},
"require": {
"development": "./dist/core.cjs",
"production": "./dist/core.prod.cjs",
"default": "./dist/core.prod.cjs"
"development": "./dist/core.qwik.cjs",
"production": "./dist/core.prod.qwik.cjs",
"default": "./dist/core.prod.qwik.cjs"
}
},
"./cli": {
Expand All @@ -59,29 +59,29 @@
"./jsx-runtime": {
"types": "./dist/jsx-runtime.d.ts",
"import": {
"development": "./dist/core.mjs",
"production": "./dist/core.prod.mjs",
"default": "./dist/core.prod.mjs",
"min": "./dist/core.min.mjs"
"development": "./dist/core.qwik.mjs",
"production": "./dist/core.prod.qwik.mjs",
"default": "./dist/core.prod.qwik.mjs",
"min": "./dist/core.min.qwik.mjs"
},
"require": {
"development": "./dist/core.cjs",
"production": "./dist/core.prod.cjs",
"default": "./dist/core.prod.cjs"
"development": "./dist/core.qwik.cjs",
"production": "./dist/core.prod.qwik.cjs",
"default": "./dist/core.prod.qwik.cjs"
}
},
"./jsx-dev-runtime": {
"types": "./dist/jsx-runtime.d.ts",
"import": {
"development": "./dist/core.mjs",
"production": "./dist/core.prod.mjs",
"default": "./dist/core.mjs",
"min": "./dist/core.min.mjs"
"development": "./dist/core.qwik.mjs",
"production": "./dist/core.prod.qwik.mjs",
"default": "./dist/core.qwik.mjs",
"min": "./dist/core.min.qwik.mjs"
},
"require": {
"development": "./dist/core.cjs",
"production": "./dist/core.prod.cjs",
"default": "./dist/core.cjs"
"development": "./dist/core.qwik.cjs",
"production": "./dist/core.prod.qwik.cjs",
"default": "./dist/core.qwik.cjs"
}
},
"./build": {
Expand Down
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 '@qwik.dev/core';
import { Fragment, Slot } from '@qwik.dev/core';

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 '@qwik.dev/core';
import { Fragment } from '@qwik.dev/core';
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;
}
Loading
Loading