switch to a Document trait and introduce Script/Head/Style/Meta components (#2635)
* switch to a Document trait and introduce Script/Head/Style/Meta components * Fix desktop title * Insert special elements into the head during the inital SSR render * Make all head component attributes optional * hydrate head elements * improve the server streaming head warning * Document fullstack head hydration approach * deduplicate head elements by href * move Link into head::Link * document head components * add meta and title examples * Fix a few doc examples * fix formatting * Add title to playwright tests * serde is optional on web, but it is enabled by hydrate * remove leftover console log
This commit is contained in:
1 parent
a490a94ae5
commit
176e67e5b7
65 files changed
+1703
-495
No files matched your search
@@ -0,0 +1,3 @@
|
||||
# please dont accidentally run tsc and commit your js in this dir.
|
||||
*.js
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Handle communication between rust and evaluating javascript
|
||||
|
||||
export class Channel {
|
||||
pending: any[];
|
||||
waiting: ((data: any) => void)[];
|
||||
|
||||
constructor() {
|
||||
this.pending = [];
|
||||
this.waiting = [];
|
||||
}
|
||||
|
||||
send(data: any) {
|
||||
// If there's a waiting callback, call it
|
||||
if (this.waiting.length > 0) {
|
||||
this.waiting.shift()(data);
|
||||
return;
|
||||
}
|
||||
// Otherwise queue the data
|
||||
this.pending.push(data);
|
||||
}
|
||||
|
||||
async recv(): Promise<any> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
// If data already exists, resolve immediately
|
||||
if (this.pending.length > 0) {
|
||||
resolve(this.pending.shift());
|
||||
return;
|
||||
}
|
||||
// Otherwise queue the resolve callback
|
||||
this.waiting.push(resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class WeakDioxusChannel {
|
||||
inner: WeakRef<DioxusChannel>;
|
||||
|
||||
constructor(channel: DioxusChannel) {
|
||||
this.inner = new WeakRef(channel);
|
||||
}
|
||||
|
||||
// Send data from rust to javascript
|
||||
rustSend(data: any) {
|
||||
let channel = this.inner.deref();
|
||||
if (channel) {
|
||||
channel.rustSend(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Receive data sent from javascript in rust
|
||||
async rustRecv(): Promise<any> {
|
||||
let channel = this.inner.deref();
|
||||
if (channel) {
|
||||
return await channel.rustRecv();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class DioxusChannel {
|
||||
// Return a weak reference to this channel
|
||||
weak(): WeakDioxusChannel {
|
||||
return new WeakDioxusChannel(this);
|
||||
}
|
||||
|
||||
// Send data from rust to javascript
|
||||
abstract rustSend(data: any): void;
|
||||
|
||||
// Receive data sent from javascript in rust
|
||||
abstract rustRecv(): Promise<any>;
|
||||
}
|
||||
|
||||
export class WebDioxusChannel extends DioxusChannel {
|
||||
js_to_rust: Channel;
|
||||
rust_to_js: Channel;
|
||||
owner: any;
|
||||
|
||||
constructor(owner: any) {
|
||||
super();
|
||||
this.owner = owner;
|
||||
this.js_to_rust = new Channel();
|
||||
this.rust_to_js = new Channel();
|
||||
}
|
||||
|
||||
// Return a weak reference to this channel
|
||||
weak(): WeakDioxusChannel {
|
||||
return new WeakDioxusChannel(this);
|
||||
}
|
||||
|
||||
// Receive message from Rust
|
||||
async recv() {
|
||||
return await this.rust_to_js.recv();
|
||||
}
|
||||
|
||||
// Send message to rust.
|
||||
send(data: any) {
|
||||
this.js_to_rust.send(data);
|
||||
}
|
||||
|
||||
// Send data from rust to javascript
|
||||
rustSend(data: any) {
|
||||
this.rust_to_js.send(data);
|
||||
}
|
||||
|
||||
// Receive data sent from javascript in rust
|
||||
async rustRecv(): Promise<any> {
|
||||
return await this.js_to_rust.recv();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Helper functions for working with the document head
|
||||
|
||||
function createElementInHead(
|
||||
tag: string,
|
||||
attributes: [string, string][],
|
||||
children: string | null
|
||||
): void {
|
||||
const element = document.createElement(tag);
|
||||
for (const [key, value] of attributes) {
|
||||
element.setAttribute(key, value);
|
||||
}
|
||||
if (children) {
|
||||
element.appendChild(document.createTextNode(children));
|
||||
}
|
||||
document.head.appendChild(element);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.createElementInHead = createElementInHead;
|
||||
@@ -0,0 +1,86 @@
|
||||
import { Channel, DioxusChannel, WeakDioxusChannel } from "./eval";
|
||||
|
||||
// In dioxus desktop, eval needs to use the window object to store global state because we evaluate separate snippets of javascript in the browser
|
||||
declare global {
|
||||
interface Window {
|
||||
__msg_queues: WeakDioxusChannel[];
|
||||
finalizationRegistry: FinalizationRegistry<{ id: number }>;
|
||||
|
||||
getQuery(request_id: number): WeakDioxusChannel;
|
||||
|
||||
createQuery(request_id: number): NativeDioxusChannel;
|
||||
}
|
||||
}
|
||||
|
||||
// A message that can be sent to the desktop renderer about a query
|
||||
class QueryParams {
|
||||
id: number;
|
||||
data: { method: "drop" | "send"; data?: any };
|
||||
|
||||
constructor(id: number, method: "drop" | "send", data?: any) {
|
||||
this.id = id;
|
||||
this.data = { method, data };
|
||||
}
|
||||
}
|
||||
|
||||
window.__msg_queues = window.__msg_queues || [];
|
||||
// In dioxus desktop, eval is copy so we cannot run a drop handler. Instead, the drop handler is run after the channel is garbage collected in the javascript side
|
||||
window.finalizationRegistry =
|
||||
window.finalizationRegistry ||
|
||||
new FinalizationRegistry(({ id }) => {
|
||||
// @ts-ignore - wry gives us this
|
||||
window.ipc.postMessage(
|
||||
JSON.stringify({
|
||||
method: "query",
|
||||
params: new QueryParams(id, "drop"),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Get a query from the global state
|
||||
window.getQuery = function (request_id: number): WeakDioxusChannel {
|
||||
return window.__msg_queues[request_id];
|
||||
};
|
||||
|
||||
// Create a new query (and insert it into the global state)
|
||||
window.createQuery = function (request_id: number): NativeDioxusChannel {
|
||||
return new NativeDioxusChannel(request_id);
|
||||
};
|
||||
|
||||
export class NativeDioxusChannel extends DioxusChannel {
|
||||
rust_to_js: Channel;
|
||||
request_id: number;
|
||||
|
||||
constructor(request_id: number) {
|
||||
super();
|
||||
this.rust_to_js = new Channel();
|
||||
this.request_id = request_id;
|
||||
|
||||
window.__msg_queues[request_id] = this.weak();
|
||||
window.finalizationRegistry.register(this, { id: request_id });
|
||||
}
|
||||
|
||||
// Receive message from Rust
|
||||
async recv() {
|
||||
return await this.rust_to_js.recv();
|
||||
}
|
||||
|
||||
// Send message to rust.
|
||||
send(data: any) {
|
||||
// @ts-ignore - wry gives us this
|
||||
window.ipc.postMessage(
|
||||
JSON.stringify({
|
||||
method: "query",
|
||||
params: new QueryParams(this.request_id, "send", data),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Send data from rust to javascript
|
||||
rustSend(data: any) {
|
||||
this.rust_to_js.send(data);
|
||||
}
|
||||
|
||||
// Receive data sent from javascript in rust. This is a no-op in the native interpreter because the rust code runs remotely
|
||||
async rustRecv(): Promise<any> {}
|
||||
}
|
||||
Reference in new issue
Block a user