4 minute read

项目需要界面选择了leptos,对web ui整体认知不足,一直踩坑,理下思路。

reactive

leptos把自己定义为一个reactive框架,与responsive区别

How do these differ? Responsiveness implies thoughtful action that considers long and short term outcome in the context of the situation at hand. Reactive behavior is immediate and without conscious thought, like a knee jerk response. Reactive behavior is often driven by the emotions.

react是本能、条件反射,response是经过思考的回应。

在前端开发领域,react涉及两方面:信号(signal)和效果(effect),主要是对用户的事件进行响应(如点击、滑动)。responsive是指不同ui尺寸下显示合适的界面布局(这个当然需要事先思考规划),一般通过css控制。

html streaming

html文件本身也是可以流式传输的,浏览器接收一部分显示一部分。这个过程对于后台而言就是一个请求,返回html流。leptos ssr模式中,所有non-sync mode都是返回的html流,调用server_fn是html返回流的一部分。所以后端middleware(比如鉴权)应该放在返回html的端点上,而不是单独应用到server_fn所对应的/api endpoint。

浏览器的进程/线程模型

leptos本质是webassembly框架,它把所有信号、效果保存在Runtime结构体上。csr模式下Runtime是std::thread::LocalKey

Chromium的相关文档:

  • https://www.chromium.org/developers/how-tos/getting-around-the-chrome-source-code/
  • https://docs.google.com/presentation/d/1ujV8LjIUyPBmULzdT2aT9Izte8PDwbJi
  • https://www.chromium.org/developers/design-documents/multi-process-architecture/
  • https://www.chromium.org/developers/design-documents/multi-process-resource-loading/
  • https://chromium.googlesource.com/chromium/src/+/HEAD/docs/threading_and_tasks.md
  • https://www.chromium.org/developers/design-documents/displaying-a-web-page-in-chrome/
  • https://chromium.googlesource.com/chromium/src/+/HEAD/content/README.md

MDN 相关文档:

  • https://developer.mozilla.org/en-US/docs/Glossary/Browsing_context (每个origin都是一个browsing context)
  • https://developer.mozilla.org/en-US/docs/Web/API/Document
  • https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe (embeded browsing context)

总结下,chrome浏览器包含进程:

  • 一个browser process, 包含2个线程:
    • main browser (UI) thread,包含对象:
      • 1个Browser
      • RenderProcessHost(每个render process对应一个)
      • RenderFrameHost(在RenderProcessHost内部唯一)
      • RenderWidgetHost
    • I/O thread,包含对象
      • 多个IPC channel(用于与render process通信)
  • 多个render process, 每个redner process包含2个线程:
    • main thread, 包含对象:
      • 1个RenderProcess(与browser process通信)
        • 1个RenderThread(用于cross-thread communication with RenderView)
    • render thread, 包含对象
      • 1个RenderView(继承widget,代表一个tab view,可以包含一个或多个RenderFrame);RenderWidget(下拉框是唯一widget独立于view的情况)
      • RenderFrame(默认1个, 存在<iframe>时有多个)
        • https://www.chromium.org/developers/design-documents/oop-iframes/
        • https://issues.chromium.org/issues/40319720

web app的lifecycle

  • https://developer.chrome.com/docs/web-platform/page-lifecycle-api page lifecycle

关于leptos Runtime

保存的数据类型

Runtime使用slotmap存储,key类型有:

  • NodeId (ReactiveNode)
  • TypeId (用于context api)
  • ResourceId
  • StoredValueId

ScopeProperty有:

  • Trigger(NodeId)
  • Signal(NodeId)
  • Effect(Nodeid)
  • Resource(ResourceId)
  • StoredValue(StoredValueId)

关于Runtime对象自身的存储

gbj的youtube视频里实现的简易版本,他直接leak了一个Runtime。

在leptos实际的代码实现中, 定义了几个全局static

csr端:

  • RUNTIME (thread_local!, Runtime) ssr端:
  • TASK_RUNTIME (task_local!, RuntimeId)
  • CURRENT_RUNTIME (thread_local!, RuntimeId)
  • RUNTIMES (thread_local!, RuntimeId->Runtime)

注意csr端RuntimeId是unit struct;ssr端RuntimeId是slotmap::new_key_type!

关于Runtime对象自身的生存周期:

@me: Hi I have a question about the lifetime of Runtime. In the browser (csr mode), Runtime is created as a static thread_local! variable. Take chromium for example, I read some docs about its thread/process model https://www.chromium.org/developers/design-documents/multi-process-architecture/, and came to the conclusion that one browser tab means one render process, which has one main thread and one render thread, and leptos runs in the render thread. So Runtime is a thread_local! variable in the render thread. If a user navigates away from my leptos website to another website, under the same browser tab, does it mean that my website’s Runtime still exist in that render thread?

@cperry6060: pretty sure if you navigate away from the document anything that ran in that document’s environment is discarded. In the same way that a force-refresh would recreate the entire app. Otherwise you would just always be leaking memory for every previous page you visited thats a hard-core “im guessing” though. so ymmv and im stupid

@me: I would assume the same. But can you find references in chromium docs that clearly confirm that assumption? for example it would clear all thread-locals of the render thread when a renderer loads another website? Though I think that if a cleanup thing exists, it should be triggered when origin is changed. Loading different documents from the same origin shouldn’t trigger it.

@gbj: You are overthinking this one, I think. The “thread” here is a “thread” in the sandboxed virtual machine running the WASM module, it is absolutely not the system thread chromium is using to render the page.

@me: oh, does every WASM module gets its own virtual machine?

@Bloo: for now at least iirc they will allow wasm modules, something like that

@gbj: Yes they are executed independently of one another If the browser allowed websites you to leak arbitrary code across pages like this it would be an insane security hole — like if you visited a website that started running a bitcoin miner, and then navigated away to another page and the browser kept running the miner because you’re allowed to arbitrarily access memory in the actual process chromium is using to render?

@me: yeah that shouldn’t happen

@cperry6060: i think wasm is just always single-threaded as well right? Since it is basically just all backed by a simple stack of memory

@remrevo2048: I mean it doesn’t have to be. IIRC there are extensions for atomics and thread creation. So depending on the runtime, you might even do normal multi-threading.

@me: So if I refresh the page and request the wasm file again, it would mean a brand new Runtime is created for me?

@remrevo2048: Yes.

@me: Got it!

@cperry6060: i just meant like the current version that most browsers use. That is cool about the extensions though

@remrevo2048: I think the furthest thing in that direction is wasix, which is basically full posix in webassemby. The future is exciting!

总结:就跟js是运行在sandbox里一样,wasm也不会直接在render thread中运行,也会有它自己的sandbox

  • https://www.quora.com/What-is-meant-by-JavaScript-running-in-a-secure-sandbox
  • https://www.oreilly.com/library/view/learning-javascript/0596527462/ch01s05.html (Learning JavaScript by Shelley Powers)

在MDN的文档中也有提及: https://developer.mozilla.org/en-US/docs/WebAssembly/Concepts

  • Keep secure — WebAssembly is specified to be run in a safe, sandboxed execution environment. Like other web code, it will enforce the browser’s same-origin and permissions policies.

关于Runtime中存储的数据的生存周期:

  • https://discord.com/channels/1031524867910148188/1163432463817785375 (resource的lifetime)

    @gbj: Like a signal, StoredValue, or any other type in the reactive system it (Resource) will be cleaned up either 1) When the scope it’s created in is cleaned up (i.e., when the portion of the tree it’s in “rerenders”), or 2) when you call .dispose() to dispose it early

    If you are creating a large number of resource in a high/global scope, then yes they are leaking unless you dispose them

    Take a look at leptos_query if you haven’t already. Otherwise, you can either create the resources at the right scope for them to be disposed automatically, or dispose of them manually

    @me: Hi @gbj , are there online docs about how scopeworks in leptos? like how scopes get created, and how Signals\StoredValue get bounded to them. If not, where should I look into the source code?

    @gbj: Sure. It’s not super complicated. Every effect or memo is an Owner. It owns everything created while it is running. When it reruns, it disposes of everything from the last time (and runs again, creating them all again)

    Most of it lives around here: https://github.com/leptos-rs/leptos/blob/3e93a686f45657c6d2e86790c2b7e8fcc574df4b/leptos_reactive/src/runtime.rs#L193

    Generally speaking this will only come up if you’re doing things like creating signals and trying to store them somewhere higher up in the component tree, or in a global, but then the effect under which they were created reruns

  • https://discord.com/channels/1031524867910148188/1121455457853251725 (#[component]的lifecycle)

    @gbj: See also https://docs.rs/leptos/0.3.1/leptos/struct.HtmlElement.html#method.on_mount

    There is no component lifecycle per se, because components run once. However, as you’ve noticed they run before the DOM elements they return are actually mounted (which makes sense, as they return them and the parent mounts them where it wants them). Create effect + request animation frame (to delay a tick until it’s actually been mounted) or on\mount both work. In the other direction, on_cleanup