@omnicajs/vue-remote
Version:
Proxy renderer for Vue.js based on @remote-ui
788 lines (787 loc) • 25 kB
JavaScript
import { createRenderer, h, defineComponent } from "vue";
import "@remote-ui/rpc";
import { K as KIND_COMPONENT, e as KIND_TEXT, p as KIND_FRAGMENT, b as addMethod, q as ACTION_INVOKE, c as KIND_ROOT, l as ACTION_INSERT_CHILD, r as capture, m as ACTION_REMOVE_CHILD, f as KIND_COMMENT, o as ACTION_UPDATE_TEXT, v as visitArray, s as isObject, t as visitObject, j as isFunction, k as keysOf, n as ACTION_UPDATE_PROPERTIES, R as ROOT_ID, u as arraify, A as ACTION_MOUNT, H as HTMLTagList, M as MathMLTagList, S as SVGTagList, g as REMOTE_SLOT } from "./dom-Duh2pvkf.js";
function isRemoteComponent(value) {
return value != null && value.kind === KIND_COMPONENT;
}
function isRemoteFragment(value) {
return value != null && value.kind === KIND_FRAGMENT;
}
function isRemoteText(value) {
return value != null && value.kind === KIND_TEXT;
}
const normalizeChild = (child, root) => typeof child === "string" ? root.createText(child) : child;
const normalizeChildren = (children, root) => children.map((child) => normalizeChild(child, root));
const traverse = (element, each) => {
const _traverse = (element2) => {
if ("children" in element2) {
for (const child of element2.children) {
each(child);
_traverse(child);
}
}
};
_traverse(element);
};
function attach(context, parent, node) {
const { progenitors, parents } = context;
const progenitor = parent.kind === KIND_ROOT ? parent : parent.progenitor;
if (progenitor) {
progenitors.set(node, progenitor);
}
parents.set(node, parent);
attachFragments(context, node);
traverse(node, (descendant) => {
if (progenitor) {
progenitors.set(descendant, progenitor);
}
attachFragments(context, descendant);
});
}
function attachFragments(context, node) {
if (node.kind === KIND_COMPONENT) {
Object.values(node.properties).forEach((prop) => {
if (isRemoteFragment(prop)) {
attach(context, node, prop);
}
});
}
}
function detach(context, node) {
const { progenitors, parents } = context;
progenitors.delete(node);
parents.delete(node);
traverse(node, (descendant) => {
progenitors.delete(descendant);
detachFragments(context, descendant);
});
detachFragments(context, node);
}
function detachFragments(context, node) {
if (node.kind !== KIND_COMPONENT) {
return;
}
const properties = node.properties;
for (const key of Object.keys(properties)) {
const p = properties[key];
if (isRemoteFragment(p)) {
detach(context, p);
}
}
}
const update = (context, node, remote, local) => {
var _a;
if (context.mounted && (node.kind === KIND_ROOT || ((_a = node.progenitor) == null ? void 0 : _a.kind) === KIND_ROOT)) {
remote(context.channel);
}
local();
};
function dataOf(context, parent) {
switch (parent == null ? void 0 : parent.kind) {
case KIND_COMPONENT:
return context.components.get(parent);
case KIND_FRAGMENT:
return context.fragments.get(parent);
case KIND_ROOT:
return context;
}
}
const insertToArray = (target, el, before) => {
if (before == null) {
target.push(el);
} else {
target.splice(target.indexOf(before), 0, el);
}
return target;
};
const remoteFromArray = (target, index) => {
const result = [...target];
result.splice(index, 1);
return result;
};
const insert = (context, parent, child, before = null) => {
const currentParent = child.parent;
const currentIndex = (currentParent == null ? void 0 : currentParent.children.indexOf(child)) ?? -1;
attach(context, parent, child);
let newChildren;
const parentData = dataOf(context, parent);
if (currentParent) {
const currentParentData = dataOf(context, currentParent);
const currentChildren = remoteFromArray(currentParentData.children, currentIndex);
if (currentParent === parent) {
newChildren = currentChildren;
} else {
currentParentData.children = capture(currentChildren, context.strict);
newChildren = [...parentData.children];
}
} else {
newChildren = [...parentData.children];
}
parentData.children = capture(insertToArray(newChildren, child, before), context.strict);
};
const appendChild = (context, parent, child) => {
if (!context.nodes.has(child)) {
throw new Error("Cannot append a node that was not created by this remote root");
}
const currentParent = child.parent;
const currentIndex = (currentParent == null ? void 0 : currentParent.children.indexOf(child)) ?? -1;
return update(context, parent, (channel) => {
channel(
ACTION_INSERT_CHILD,
parent.id,
currentIndex < 0 ? parent.children.length : parent.children.length - 1,
child.serialize(),
currentParent ? currentParent.id : false
);
}, () => insert(context, parent, child));
};
const insertBefore = (context, parent, child, before) => {
if (!context.nodes.has(child)) {
throw new Error("Cannot insert a node that was not created by this remote root");
}
if (before && before.id === child.id) return;
if (before && !parent.children.includes(before)) {
throw new DOMException(
"Cannot add a child before an element that is not a child of the target parent.",
"HierarchyRequestError"
);
}
const oldIndex = parent.children.indexOf(child) ?? -1;
const oldParent = child.parent;
const beforeIndex = before ? parent.children.indexOf(before) : -1;
return update(context, parent, (channel) => channel(
ACTION_INSERT_CHILD,
parent.id,
beforeIndex < 0 ? parent.children.length : oldIndex < 0 || oldIndex > beforeIndex ? beforeIndex : beforeIndex - 1,
child.serialize(),
oldParent ? oldParent.id : false
), () => insert(context, parent, child, before));
};
const removeChild = (context, parent, child) => {
const data = dataOf(context, parent);
return update(context, parent, (channel) => channel(
ACTION_REMOVE_CHILD,
parent.id,
parent.children.indexOf(child)
), () => {
detach(context, child);
data.children = capture(remoteFromArray(
data.children,
data.children.indexOf(child)
), context.strict);
});
};
const addAttachMethod = (context) => addMethod(context, "attach", (parent, node) => attach(context, parent, node));
const addDetachMethod = (context) => addMethod(context, "detach", (node) => detach(context, node));
const addAppendMethod$1 = (context) => addMethod(context, "append", (parent, children) => {
for (const child of children) {
appendChild(context, parent, child);
}
});
const addUpdateMethod = (context) => addMethod(context, "update", (node, remote, local) => update(context, node, remote, local));
const addReplaceMethod$1 = (context) => addMethod(context, "replace", (parent, children) => {
for (const child of parent.children) {
removeChild(context, parent, child);
}
for (const child of children) {
appendChild(context, parent, child);
}
});
const addCollectMethod = (context) => addMethod(context, "collect", (node) => {
if (context.nodes.has(node)) {
return;
}
context.nodes.add(node);
Object.defineProperty(node, "parent", {
get: () => context.parents.get(node) ?? null,
configurable: true,
enumerable: true
});
Object.defineProperty(node, "progenitor", {
get: () => context.progenitors.get(node) ?? null,
configurable: true,
enumerable: true
});
});
const addInvokeMethod = (context) => addMethod(context, "invoke", (node, method, payload) => {
if (!context.nodes.has(node)) {
throw new Error("Cannot invoke method for a node that was not created by this remote root");
}
return new Promise((resolve, reject) => {
context.channel(
ACTION_INVOKE,
node.id,
method,
payload,
resolve,
reject
);
});
});
const createRemoteRootData = (channel, { components, strict = true } = {}) => {
if (strict) {
Object.freeze(components);
}
return {
strict,
mounted: false,
channel,
children: [],
nodes: /* @__PURE__ */ new WeakSet(),
parents: /* @__PURE__ */ new WeakMap(),
progenitors: /* @__PURE__ */ new WeakMap(),
components: /* @__PURE__ */ new WeakMap(),
fragments: /* @__PURE__ */ new WeakMap()
};
};
const createTreeContext = (channel, options = {}) => {
const context = createRemoteRootData(channel, options);
let lastId = 0;
addMethod(context, "nextId", () => `${++lastId}`);
addCollectMethod(context);
addAttachMethod(context);
addDetachMethod(context);
addAppendMethod$1(context);
addUpdateMethod(context);
addMethod(context, "insert", (parent, child, before) => insertBefore(context, parent, child, before));
addMethod(context, "removeChild", (parent, child) => removeChild(context, parent, child));
addReplaceMethod$1(context);
addInvokeMethod(context);
return context;
};
const createRemoteComment = (content, root, context) => {
const id = context.nextId();
const data = { text: content };
const node = {
kind: KIND_COMMENT,
get id() {
return id;
},
get root() {
return root;
},
get text() {
return data.text;
},
update: (text) => context.update(
node,
(channel) => channel(ACTION_UPDATE_TEXT, node.id, text),
() => data.text = text
),
serialize: () => ({ id, kind: KIND_COMMENT, text: data.text }),
remove: () => {
var _a;
return (_a = node.parent) == null ? void 0 : _a.removeChild(node);
},
print: () => `Comment(${data.text})`
};
context.collect(node);
return node;
};
const isProxy = (value) => {
return value instanceof Function && "__current" in value;
};
const toProxy = (value) => {
const _proxy = (...args) => {
return _proxy.__current ? _proxy.__current(...args) : void 0;
};
Object.defineProperty(_proxy, "__current", {
value,
configurable: false,
enumerable: false,
writable: true
});
return _proxy;
};
function proxyFunctionsIn(value, visited = /* @__PURE__ */ new Map()) {
if (visited.has(value)) {
return visited.get(value);
}
if (isRemoteFragment(value)) {
visited.set(value, value);
return value;
}
if (Array.isArray(value)) {
return visitArray(value, visited, proxyFunctionsIn);
}
if (isObject(value)) {
return visitObject(value, visited, proxyFunctionsIn);
}
if (isFunction(value)) {
const proxy = toProxy(value);
visited.set(value, proxy);
return proxy;
}
visited.set(value, value);
return value;
}
const collectProxies = (value, visited = /* @__PURE__ */ new Set()) => {
if (visited.has(value)) {
return void 0;
}
visited.add(value);
if (Array.isArray(value)) {
return value.reduce((all, element) => {
const nested = collectProxies(element, visited);
return nested ? [...all, ...nested] : all;
}, []);
}
if (isObject(value)) {
return keysOf(value).reduce((all, key) => {
const nested = collectProxies(value[key], visited);
return nested ? [...all, ...nested] : all;
}, []);
}
return isProxy(value) ? [value] : void 0;
};
const prepareProxiesUnset = (value) => {
var _a;
return ((_a = collectProxies(value)) == null ? void 0 : _a.map((p) => [p, void 0])) ?? [];
};
function prepareProxies(oldValue, newValue, visited = /* @__PURE__ */ new Set()) {
if (visited.has(oldValue)) {
return [oldValue, [], true];
}
visited.add(oldValue);
if (isProxy(oldValue)) {
return isFunction(newValue) ? [oldValue, [[oldValue, newValue]], true] : [proxyFunctionsIn(newValue), [], false];
}
if (Array.isArray(oldValue)) {
return !Array.isArray(newValue) ? [proxyFunctionsIn(newValue), prepareProxiesUnset(oldValue), false] : prepareProxiesInArray(oldValue, newValue, visited);
}
if (isObject(oldValue) && !isRemoteFragment(oldValue)) {
return !isObject(newValue) ? [proxyFunctionsIn(newValue), prepareProxiesUnset(oldValue), false] : prepareProxiesInObject(oldValue, newValue, visited);
}
return [newValue, [], oldValue === newValue];
}
function prepareProxiesInObject(oldValue, newValue, visited) {
const normalized = {};
const records = [];
let changed = false;
for (const key of keysOf(oldValue)) {
const oldEl = oldValue[key];
if (!(key in newValue)) {
records.push(...prepareProxiesUnset(oldEl));
changed = true;
}
const newEl = newValue[key];
const [updated, record, skip] = prepareProxies(oldEl, newEl, visited);
records.push(...record);
if (!skip) {
normalized[key] = updated;
changed = true;
}
}
for (const key of keysOf(newValue)) {
if (!(key in normalized)) {
normalized[key] = proxyFunctionsIn(newValue[key]);
changed = true;
}
}
return [normalized, records, !changed];
}
function prepareProxiesInArray(oldValue, newValue, visited) {
const normalized = [];
const records = [];
let changed = false;
for (let i = 0; i < Math.max(oldValue.length, newValue.length); i++) {
const oldEl = oldValue[i];
const newEl = newValue[i];
if (i >= newValue.length) {
records.push(...prepareProxiesUnset(oldEl));
changed = true;
continue;
}
if (i >= oldValue.length) {
normalized[i] = proxyFunctionsIn(newEl);
changed = true;
continue;
}
const [updated, record, skip] = prepareProxies(oldEl, newEl, visited);
records.push(...record);
normalized[i] = skip ? oldEl : updated;
changed = !skip || changed;
}
return [normalized, records, !changed];
}
const updateProxies = (records) => {
for (const [fn, current] of records) {
if (fn.__current !== current) {
fn.__current = current;
}
}
};
function createRemoteComponent(type, properties, children, root, context) {
const id = context.nextId();
const descriptor = typeof type === "object" && "type" in type ? type : null;
const data = createRemoteComponentData(properties, children, root, context);
const node = {
kind: KIND_COMPONENT,
get id() {
return id;
},
get type() {
return descriptor ? descriptor.type : type;
},
get root() {
return root;
},
get children() {
return data.children;
},
get properties() {
return data.properties.original;
},
append: (...children2) => context.append(
node,
normalizeChildren(children2, root)
),
insertBefore: (child, before) => context.insert(
node,
normalizeChild(child, root),
before
),
updateProperties: (properties2) => updateProperties(
context,
node,
properties2
),
replace: (...children2) => context.replace(
node,
normalizeChildren(children2, root)
),
removeChild: (child) => context.removeChild(node, child),
remove: () => node.parent ? context.removeChild(node.parent, node) : null,
invoke: (method, ...payload) => !descriptor || (descriptor == null ? void 0 : descriptor.hasMethod(method)) ? context.invoke(node, method, payload) : Promise.reject(`Method ${method} is not supported`),
serialize: () => ({
id,
kind: KIND_COMPONENT,
type,
properties: data.properties.serializable,
children: data.children.map((c) => c.serialize())
}),
print: () => _print(id, type, data.properties.original, data.children)
};
context.collect(node);
context.components.set(node, data);
data.children.forEach((c) => context.attach(node, c));
return node;
}
const RESERVED = ["children"];
const notReserved = (name) => !RESERVED.includes(name);
function createRemoteComponentData(properties, children, root, context) {
const original = properties ?? {};
const serializable = {};
for (const key of keysOf(original).filter(notReserved)) {
serializable[key] = proxyFunctionsIn(serializeProperty(original[key]));
}
return {
properties: {
original: capture(original, context.strict),
serializable
},
children: capture(normalizeChildren(children, root), context.strict)
};
}
function updateProperties(context, component, properties) {
const componentData = context.components.get(component);
const normalized = {};
const records = [];
let changed = false;
for (const key of keysOf(properties).filter(notReserved)) {
const oldOriginal = componentData.properties.original[key];
const newOriginal = properties[key];
const oldSerializable = componentData.properties.serializable[key];
const newSerializable = serializeProperty(newOriginal);
if (oldSerializable === newSerializable && (newSerializable == null || typeof newSerializable !== "object")) {
continue;
}
const [value, record, skip] = prepareProxies(oldSerializable, newSerializable);
records.push(...record);
if (!skip) {
normalized[key] = value;
changed = true;
if (isRemoteFragment(oldOriginal)) {
context.detach(oldOriginal);
}
if (isRemoteFragment(newOriginal)) {
context.attach(component, newOriginal);
}
}
}
return context.update(component, (channel) => {
if (changed) {
channel(ACTION_UPDATE_PROPERTIES, component.id, normalized);
}
}, () => {
componentData.properties.original = capture({ ...componentData.properties.original, ...properties }, context.strict);
componentData.properties.serializable = { ...componentData.properties.serializable, ...normalized };
updateProxies(records);
});
}
function serializeProperty(property) {
return isRemoteFragment(property) ? property.serialize() : property;
}
function _print(id, type, _properties, children) {
const _head = `${typeof type === "string" ? type : type.type}:${id}`;
const _children = children.map((c) => typeof c === "string" ? c : c.print());
const _body = _children.length > 0 ? `
${_indent(_children.join(",\n"))}
` : "";
return `${_head}[${_body}]`;
}
function _indent(text) {
return text.split("\n").map((line) => ` ${line}`).join("\n");
}
const createRemoteFragment = (root, context) => {
const id = context.nextId();
const data = { children: capture([], context.strict) };
const fragment = {
kind: KIND_FRAGMENT,
get id() {
return id;
},
get root() {
return root;
},
get children() {
return data.children;
},
append: (...children) => context.append(fragment, normalizeChildren(children, root)),
insertBefore: (child, before) => context.insert(
fragment,
normalizeChild(child, root),
before
),
replace: (...children) => context.replace(fragment, normalizeChildren(children, root)),
removeChild: (child) => context.removeChild(fragment, child),
serialize: () => ({
id,
kind: KIND_FRAGMENT,
children: data.children.map((c) => c.serialize())
})
};
context.collect(fragment);
context.fragments.set(fragment, data);
return fragment;
};
const createRemoteText = (content, root, context) => {
const id = context.nextId();
const data = { text: content };
const node = {
kind: KIND_TEXT,
get id() {
return id;
},
get root() {
return root;
},
get text() {
return data.text;
},
update: (text) => context.update(
node,
(channel) => channel(ACTION_UPDATE_TEXT, node.id, text),
() => data.text = text
),
serialize: () => ({ id, kind: KIND_TEXT, text: data.text }),
remove: () => {
var _a;
return (_a = node.parent) == null ? void 0 : _a.removeChild(node);
},
print: () => `Text(${data.text})`
};
context.collect(node);
return node;
};
function createRemoteRoot$1(channel, {
components,
strict = true
} = {}) {
const context = createTreeContext(channel, {
components,
strict
});
const root = {
kind: KIND_ROOT,
options: capture({ strict, components }, strict),
get id() {
return ROOT_ID;
},
get children() {
return context.children;
},
removeChild: (child) => context.removeChild(root, child)
};
addCreateCommentMethod(root, context);
addCreateComponentMethod(root, context);
addCreateFragmentMethod(root, context);
addCreateTextMethod(root, context);
addMountMethod(root, context);
addAppendMethod(root, context);
addInsertMethod(root, context);
addReplaceMethod(root, context);
return root;
}
function addCreateCommentMethod(root, context) {
addMethod(root, "createComment", (text = "") => {
return createRemoteComment(text, root, context);
});
}
function addCreateComponentMethod(root, context) {
addMethod(root, "createComponent", (type, ...rest) => {
const components = root.options.components;
if (components && !components.some((c) => c === type || c.type === type)) {
throw new Error(`Unsupported component: ${type}`);
}
const _type = (components == null ? void 0 : components.find((c) => c === type || c.type === type)) ?? type;
const [properties, children, ...restChildren] = rest;
return createRemoteComponent(_type, properties, [
...arraify(children ?? []),
...restChildren
], root, context);
});
}
function addCreateFragmentMethod(root, context) {
addMethod(root, "createFragment", () => createRemoteFragment(root, context));
}
function addCreateTextMethod(root, context) {
addMethod(root, "createText", (text = "") => {
return createRemoteText(text, root, context);
});
}
function addMountMethod(root, context) {
addMethod(root, "mount", () => {
if (context.mounted) {
return Promise.resolve();
}
context.mounted = true;
return Promise.resolve(context.channel(
ACTION_MOUNT,
context.children.map((c) => c.serialize())
));
});
}
function addAppendMethod(root, context) {
addMethod(root, "append", (...children) => {
context.append(root, normalizeChildren(children, root));
});
}
function addInsertMethod(root, context) {
addMethod(root, "insertBefore", (child, before) => {
context.insert(root, normalizeChild(child, root), before);
});
}
function addReplaceMethod(root, context) {
addMethod(root, "replace", (...children) => {
context.replace(root, normalizeChildren(children, root));
});
}
const nextSibling = (node) => {
const { parent } = node;
if (parent == null) {
return null;
}
const { children } = parent;
return children[children.indexOf(node) + 1] ?? null;
};
const setElementText = (element, text) => {
const [node] = element.children;
if (node && isRemoteText(node)) {
node.update(text);
} else {
element.replace(text);
}
};
const setText = (node, text) => {
if (isRemoteText(node)) {
node.update(text);
} else {
setElementText(node, text);
}
};
const createRemoteRenderer = (root) => createRenderer({
patchProp(element, key, _, next) {
if (!isRemoteComponent(element)) {
throw new Error("Unexpected: Attempt to patch props on a root node");
}
element.updateProperties({ [key]: next });
},
insert: (child, parent, anchor) => parent.insertBefore(child, anchor),
remove: (node) => {
var _a;
return (_a = node.parent) == null ? void 0 : _a.removeChild(node);
},
createElement: (type) => root.createComponent(type),
createText: (text) => root.createText(text),
createComment: (text) => root.createComment(text),
parentNode: (node) => node.parent,
nextSibling,
setText,
setElementText
});
const createRemoteRoot = (channel, options = {}) => createRemoteRoot$1(channel, {
...options,
components: [
...HTMLTagList,
...MathMLTagList,
...SVGTagList,
REMOTE_SLOT,
...options.components ?? []
]
});
const toRemoteSlots = (named, slots) => {
const actual = named.filter((slotName) => slotName in slots);
if (actual.length === 0) {
return slots;
}
return {
default: () => {
var _a;
return [
..."default" in slots ? [(_a = slots.default) == null ? void 0 : _a.call(slots)] : [],
...actual.map((slotName) => h(REMOTE_SLOT, { name: slotName }, {
default: slots[slotName]
}))
];
}
};
};
const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
const fallthroughEvents = (emits, emit) => {
if (emits === void 0) {
return {};
}
const events = Array.isArray(emits) ? emits : Object.keys(emits);
return events.reduce((processed, event) => {
processed["on" + capitalize(event)] = (...args) => emit(event, ...args);
return processed;
}, {});
};
const defineRemoteComponent = (type, emits = void 0, slots = []) => defineComponent({
name: type,
inheritAttrs: false,
...emits ? { emits } : {},
setup(_, { attrs, emit, slots: internalSlots }) {
return () => h(type, {
...attrs,
...fallthroughEvents(emits, emit)
}, toRemoteSlots(slots, internalSlots));
}
});
export {
ACTION_INSERT_CHILD,
ACTION_MOUNT,
ACTION_REMOVE_CHILD,
ACTION_UPDATE_PROPERTIES,
ACTION_UPDATE_TEXT,
KIND_COMMENT,
KIND_COMPONENT,
KIND_FRAGMENT,
KIND_ROOT,
KIND_TEXT,
ROOT_ID,
createRemoteRenderer,
createRemoteRoot,
defineRemoteComponent
};