Skip to content

Latest commit

 

History

History
437 lines (425 loc) · 27.6 KB

TODO.md

File metadata and controls

437 lines (425 loc) · 27.6 KB

General

  • Add a prelude to import all html tags, svg tags, html attributes, svg attributes that doesn't conflicts
  • Make a module that isolate the with-dom features.
  • Rework the dumb patch taking the advantage of feature gating the dom capability.
  • Add documentation to functions
    • Add examples to usage of methods in Program.
  • Loosen the lifetime requirement of the Fn inside Callback from 'static to a generic one eg: 'c
    • Done in mt-dom branch: non-static-lifetime
  • Deprecate the tag macro since it complicates the conflict in reexporting the functions
    • ie: style! as a tag, style! macro for attributes, style as attribute call.
  • Change the README example to use the node macro syntax
    • rename the old minimal to minimal-alt and use the node-macro-syntax in minimal example
  • Move sauron-syntax into html2sauron project
  • [ ] Expose Cmd,Component outside of with-dom feature gate
    • This would allow a total isomorphic app reusing the components
    • [ ] Make an equivalent for Program(client-side updater) for use in server-side ~~ - ie: ServerRender, where Msg could be passed as a data to hydrate the view (template) before sending to the client~~
    • We don't need to use the update function in server-side rendering. We set the state of the app by instatiating the app with appropriate data.
  • Fix the render function where attributes of the same name not merged
  • Change type of tag, attribute_name, style keys from &'static str to &'a str
    • This will remove the need for hardcode HTML_STYLES lookup, which could be a performance penalty
  • Add the RealWorld example
    • Use the elm base code https://github.com/rtfeldman/elm-spa-example ~~- [ ] breaking put back style as a normal attribute, to avoid possible confusion to new users.
    • Cancelled, since style is treated differently in attributes. ~~
  • breaking merge Browser to Window.
  • Add and_then, sequence to Cmd to perform a task after the preceding Cmd succeeds.
  • Create a document on why it is needed for events such as on_click to have a copy of the variables outside of its environment.
  • Rethink on the naming of Component, SimpleComponent, SubComponent.
    • Component is actually Application since it the app that is manipulated by the program(executor).
    • Other names: Root Application, Component, Control, Widget
  • Merge init and init_with_program
    • It make sense to make init mutable so the developer can manipulate the component at initialization stage.
    • Make call to init right after the Application is mounted into the DOM, rather than before.
    • Simplify Application::init to not have access to Program since it can return a Cmd. It can however, modify itself.
  • Rename the type alias Callback into EventCallback or Listener. This way, we can use the more generic Callback in Components and in Cmd.
    • Recreate Callback from a clean state, with no TypeId and used it in Cmd.
    • Listener will have it's own dedidate struct with the TypeId.
    • Use Callback in Cmd
  • Component system declared in view.
    • The current system needs to store all state of the Application and its member sub components, regardless if they are specific to the Aplication or not.

    • Some component will have properties that the App don't need to store.

    • To do this, we need to create higher level macro and function which includes Component to be a node variant.

      enum Msg{
          FuiButtonMsg(fui_button::Msg),
      }
      
      fn view(&self) -> Node<Widget>{
          <div class="wrapper">
              <FuiButton on_click=|_|Msg::BtnClicked style="full"/>
          </div>
      }

      leaf.rs

      pub enum Leaf<MSG>{
          Text(String),
          <...>
          Component(Box<dyn Component>),
          <...>
      }

      attribute_value.rs

      pub enum AttributeValue<MSG>{
          Simple(Value),
          CompProperties(<ComponentProperties>),
      }
    • The Application don't have to store the state of FuiButton component, it will be stored into the Program object.

      • Issue how will be map the Msg of the sub component to convert it into the Msg of the main Application?
    • Merge the Container and Component which the view is now requires to have children components

    • Add a CustomElement trait which facilitates the component to be a custom element

    • Rethink of the sauron-component-macro

      • Redo it, maybe we don't need it and then manually implement all the Components
      • [ ] Make Application trait for internal usage only
  • Make Http api pass a decoder function
  • Additional to the dispatching of mount event.
    • on_mount
      • on_will_mount
    • on_dismount
      • on_will_dismount
  • Make the mount event be wrap as a real event, this way we can dispatch it in the real dom instead of from the virtual node
        let mount_event = new Event("mount");
        elm.dispatchEvent(mount_event);
  • Call set_attribute in addition to setting the special attributes such as value, checked, this should trigger the attribute_changed callback in web components
  • The attribute_changed method in CustomElement should return an MSG which will be dispatched in the WebComponent struct.
  • There is conflict with the use of style
    • style! macro
    • style attribute function
    • style method in Application, Component, Container
    • style html tag Maybe use css or stylesheet as the method name.
    fn css(&self) -> Vec<String>{
    }
    fn stylesheet(&self) -> Vec<String>{
    }
  • Make the compilation error in jss!, style!, more informative
  • Optimize handling of style by diffing each style properties
    • Update only specific stype instead of setting the whole style attributes

Internal

  • [ ] Find a way to map Cmd<APP,MSG> to Cmd<APP2, MSG2> ie: Cmd<ChildApp, ChildMsg> to Cmd<App, Msg> This is needed since Cmd from update function of sub components are not dispatched in the program. Only the top level component Cmd can be dispatched

    • [ ] Find a way to map Program<APP,MSG> to Program<APP2,MSG2>
      • [X] map DomUpdater<MSG> to DomUpdater<MSG2>
      • Issue mapping fields of Program that are in Rc<RefCell> seems not that simple ~~ as the Rc value of dom_updater is to be borrowed and will have a borrow checker issue~~
  • Merge Program and DomUpdater

    • Issue: DomUpdater has multiple fields, which would then be wrap with Rc<RefCell> individually
  • [ ] Change the 'static of trait implementation by specifying the lifetime - ref: https://stackoverflow.com/questions/52187644/lifetime-must-be-valid-for-the-static-lifetime-so-that-the-types-are-compatible

  • Get rid of test_fixtures and move it to test directory

  • Make each component have a reference to the root dom where it is mounted.

    • This will make local state changes to the component easier to do, as opposed to diffing the whole DOM tree.
  • Unify the code of Program replace_mount, append_mount

  • [ ] replace the request_animation_frame with the code from execute_request_animation frame

  • Create a function to derive Component name from the struct name of the Component and preprocess the jss with it before injecting it to the main program

  • Clean up CreateNode

    • no need to wrap Node and Element instead just return them as created with their closures
  • Cmd should include a should_update: bool field which indicates if the update should be made or not - Cmd{ commands:Vec<..>,should_update } - Cmd::noop() // no update operation - Fixed in 0.36.0

  • Remove the Dispatch trait and pass Program as it is in dom_updater and apply_patches module

    • There is only one implementation of Dispatch trait anyway, that is Program
    • Dispatch serve its purpose to make the code less clutter, by passing arguments around with less generics.
  • ISSUE: sauron node! macro doesn't work on svg tags since it is using only html_element function which namespace is not supplied.

    • Fixed in 0.35.0 by checking whether a tag has a namespace.
  • Change program: Option<&DSP> to just program: &DSP since there program is needed everywhere.

  • Improve sauron-node-macro to call on the equivalent html function instead of elment_ns.

    • This would improve performance since the function already has the information whether or not it has namespace or not.
    • Mitigated with the use of Lazy HashSet look up in sauron-parse for faster lookup.
    • Further improved using sauron-parse by resolving the value of self-closing and namespace at compile time in the node macro.
  • Create a hashed collections in sauron-parse to optimize lookup of tags for namespace or self-closing

    • Created a fast lookup for is_self_closing(tag) amd tag_namespace.
  • old elements that has an event attached has no way of knowing the equivalent new element has the same event attached as their callbacks are clone of closures inside of Rc and no 2 closures are the same even if the have the same code.

  • ~~[ ] add a conditional function for event attribute that if any of the other attribute is changed the event will have to be remove and re-attach.

    • This is to mitigate the aggressive recycling of nodes which we skipp diffing for event listeners for performance reasons, as it is impractical to reattach event listener at every render cycle.~~
    • This has been solved by using the TypeId of the closure of the callback.
  • Remove NodeIdx traversal and also remove NodeIdx in mt-dom TreePath, as traversal path prove to be correct.

  • Maybe Remove the style functionality as Components and Applications can manipulate the style in the document directly

    • Change style that it returns only a String instead of Vec<String>.
    • The injected style shall have a class name equal to the the type_id of the APP.
  • Add maybe_attr(name: &str, value: Option<Value>) to set the attribute if there is a value. empty otherwise.

  • Centralize the handling of attributes tha has a state such as value, checked,.

  • Issue with not finding the nodes to be patched

    • This issue manifested in performance-test-sauron repo
    • Suspecting it has to do with mount_node and root_node as replace and append could have a different behavior in the 2.
    • Solved by: using mutable reference to the root_node rather than a mutable reference to a clond one.
  • Rethink about the replace_mount in Program

    • It is useful for replacing the preload spinner when the application is finished loading
    • Have an enum for mount action
          enum MountAction{
              /// append as child to the target mount
              Append,
              /// clear any child to the target mount then append
              ClearAppend,
              /// replace the target mount with the root node
              Replace
          }
    • Mount event should have a reference to the host_node and the root_node - host_node is the node where the view is mounted, usually the parent - in case of replace host_node is the same as the root_node.
  • Maybe we don't need the async in update.

  • [ ] Refactor Program that will have to use less of Rc<RefCell<>>, by having an inner structure which is wrapped into Rc<RefCell<>>

    • this is not possible because we need to update each field separately, and borrowing the inner program state will disallow borrowing other fields.
  • BUG: if the dispatch_inner is not called in a callback which is request_animation_frame or request_idle_callback

    • This will cause the dispatch_mount event to dispatch before the root_node is set in the program when the program is to be mounted
    • Note the dispatch_mount is triggered when the view has on_mount event.
    • mitigation: make the dispatch_inner spawn in a thead either via callback, or spawn_local from wasm_bindgen_futures.
  • Tighten visibility of objects that are not meant to be pub

    • some fields in Program
    • struct types that are not meant to be public
  • Use the deadline object in request_idle_callback_with_deadline, instead of just f64, which calculates the remaining time manually

  • Migrate to rstml, since syn-rsx is unmaintained.

  • Remove Dispatch and just pass Program around

  • Make an alternative to Effects and Cmd that can be used in Component.

    • call it Task a wrapper to a future, will resolve into MSG which will then be dispatched into the program
    • does not have access to program for dispatching
  • Remove the use of Closure::forget()

  • Refactor ActiveClosure to use - add a field dom_closures in Program which stores all closure for a certain Element - all other closures is stored in active_closure rust closure_id_counter: usize, type ActiveClosure: BTreeMap<usize, Closure>;

  • unify the Program::add_event_listener which attach the event to window and the dom_node::add_event_listener_callback usage used in set_element_attributes

        Program::add_event_listener(&self, target_element: EventTarget, event_listeners).
  • Make the svg attributes follow snake_case convention

    • viewBox -> view_box
    • preserveAspectRatio -> preserve_aspect_ratio
  • As an alternative to Task where Component can not use Cmd, due to it referencing Program, we can instead return listeners.

    • window listeners
    • document listener
    struct GlobalListener{
        window_listeners: Vec<Attribute<MSG>>;
        document_listeners: Vec<Attribute<MSG>>;
    }

    add these events:

    • on_interval(|i32|{}) for attaching interval in the Window Http can be done with task
  • Make Http functions return a Task

  • Make Sub as counterpart to Cmd

    • We can use Sub in the Component
     fn on_resize(&self) -> Sub<Msg>{
     }
  • Bring back CreatedNode maybe with a different name: DomNode which wraps the Node or Element along with it's closures from event listener

    • These are then saved into the Program where when removed or dropped, it will also drop the associated closures with them, thereby simplifying the code.
    • Right now, we are attaching a vdom-data attribute for nodes that have listeners
  • Make use of Arc<RwLock> to check if can solve copying the APP via transmute_copy. it didn't solve it

    • See if there are performance penalty
  • Make template node for when app view is first created

    • The template is then patched when the app is mounted.
    • Templates will have to be saved in the global context, so it can be used accross multiple programs
    • Using the template was slow, Find out which part is the program spending much time. check the time in the diffing
      • Most of the time is spent on applying the patches
    • Make use of prediff to shorten diffing on nodes that are full static
    • unify the code where template is patched and converted
  • Create a view! macro which generated the view function + template + prediff functions

  • Make a variant of Node leaf to be Component, component can contain attributes and optionally children components + elements

    • This requires moving the mt-dom types into sauron-core so as to make a specific diff function for component
  • put back map_msg module where it contains map_msg functions for Node, Element, Attributes

    • this way, it will be easier to see the flow of map_msg method
  • remove is_static methods as they are not useful to template patches anymore

  • process the patches in the component when attributes and children are removed or added.

  • Make an additional diffing algorithm for the template blocks

    • This is anchored as next to the last sibling
    • This can not just be backtrack to the parent, as it defeats the purpose of doing as little diff as possible.
  • Make a TemplateView struct and a variant of Leaf

    struct TemplatedView<MSG>{
        template: Box<dyn Fn() -> Node<MSG>>,
        skip_diff: Box<dyn Fn() -> SkipDiff>,
        view: Node<MSG> ,
    }
  • remove SafeHtml variant in leaf, instead provide a safe_html which is parsed and converted into node

    • using safe html alters the dom tree
  • add symbol_html for html entities such as &nbsp; &gt, &lt etc.

    • this should be safe to be inserted
  • remove innerHTML func in AttributeValue as it could alter the DOM node tree

  • Keep track of which attributes to be skipped in SkipDiff{shall:false}

    enum SkipStrat{
        SkipAll,
        SkippIndex(Vec<usize>),
    }
  • Maybe disable the template usage for now

  • Make Cmd to be used internally as it needs reference to the Program<APP>

    • Use Task for returning from Application init and update.
    • The Recurring Task is actually just a Sub in elm
      • Issue with recurring task, how to store the closures which has different multiple types for the in arguments
      • Store the closures with Closure<dyn Fn(IN)->MSG>
      • Then task becomes Task<IN,MSG>
    • The SingleTask is a Cmd in sauron
    • Rename Cmd to Command alternative: Action, Operation, Instruction, Effects, Dispatch
      • This is effects in elm
    • Rename Cmd to Dispatch
    • Rename SingleTask to Action
    • Rename RecurringTask to Sub
    • enum Command{Cmd,Sub} into one unified type.
    • Cmd is a vec of Command
    • Sauron just consolidate them into one enum struct for simplicity
  • Remove Modifier and measurements

  • Unify vdom::Node and dom::DomNode

    • pro: This way stateful component can also be rendered server-side
    • pro: The node can be patch in the server-side
    • con: usage of Rc and RefCell on the components
      • Need to have a reference to Parent to apply replace_node
      • The Patch patch can get a reference to the parent via backtrack()
  • Issue with attribute in stateful component

    • attribute_changed method is not changing the view as it has no access to the program to dispatch some commands.
    • Maybe stateful_component need to have access to its program
    • Maybe Application will also need to have access to its program create its own program and mount itself with App::new().mount_to_body()
  • Remove use-teamplate as the DomNode is now a wrapper, this defeats the purpose of making a faster dom via template as deep clone has to also wrap the children nodes

Features

  • Storage service (May not be needed since the user can directly use web-sys)
    • using wasm-bindgen directly will remove the need for Storage service wrapper
  • Fetch service
  • Url change service
    • using wasm-bindgen directly eliminates the need for Url change service wrapper
  • re-think about the sauron-core features:
    • with-dom when used in client-side, default
    • []with-ssr when used in server-side rendering, mutually exlusive to with-dom
      • Server-side rendering is implicit when target is not wasm.
    • no_request_animation_frame this should be additive
      • crate is now using with-request-animation feature
  • with-markdown
    • Add sanitation to markdown parser, use ammonia crate
    • expose the sauron-md as sauron::markdown module, behind a feature flag
  • Add example using markdown
  • Make use of serde_json to parse style into components
  • Add an example where a program is a custom html element, that way sauron could be used as a way to migrate parts of an existing html/js code base.
    • Custom element which is defiend as a web component where it can be used by some other Application.
    • The App should be serializable and each of the fields will become an html attribute which
    • There is an issue with the patch not being able to find the element to be patch when using custom element due to the reason that the root_node stored in the dom updater is not the first element of the view, but rather the root_node in the dom updater is the first element of the view. The old technique was the replace the root node with the created first element but this is not ideal when used for custom_element since we need to get the attributes from the custom element Possible solution: - Add a mount_node to the dom_updater alongside with the root_node
    • custom element would be appended to the shadowRoot
      • Usage of custom element inside another sauron component should skip the custom-element internal DOM elements
    • custom element should also need access to the textContent of the tag for further processing
  • Properly trigger the MountEvent at the appending of the component to the DOM
    • Right now, it is triggered when the virtual Node is created into a real Node.
  • Maybe rename #[web_component] macro to #[custom_element]
    • Also WebComponent to CustomElementWrapper
  • Implement the StatefulComponent
    • attributes and attribute value (key)
    • removing children and setting attributes
  • Make the fancy-ui example work
    • [X] Change Patch to use PatchTarget, where TargetNode will be specified for StatefulComponents
      • This is necessary since StatefulComponents child container is not necessarily the root node.
      • Alternative way is to convert child_container to TreePath by searching for it from the root node
  • Rename local variables to their descriptive name
    • batch_pending_cmds -> batch_pending_dispatch
    • init_cmd -> init_dispatch;
  • dispatch attribute_change for all attributes in stateful component

Performance

  • Fix the reported issues with benchmarks
    • fixed by setting the target to web when building the wasm
  • Create a new benchmark for the js-comprehensive-benchmark suite
  • Use Weak pointer for program instead of Rc where strong reference is not needed. - Program stays as long as the user is using the app.
  • Add Program::batch_dispatch(&self, msgs: Vec<MSG>) to call update on each of the messages before calling on the view, this would improve performance when there are multiple messages to be dispatched to the application
    • implemented with dispatch_multiple
  • Make a benchmark for building views with more than 2000 nodes, like a text-editor.
    • There is a huge performance regression in between 0.40 and 0.42
    • It was cause by jss style! macro where the lookup for style name is recreated everytime, due to the use of const instead of static in a once_cell::Lazy declaration. This is fixed in jss 0.3.3
  • Create the dom nodes in depth-first-traversal
  • Make a pending patches in the DomUpdater which stops applying remaining patches when the time remaining for the deadline is up.
    • There is a problem with storing the patch in a struct which will need to have explicit lifetime
    • This can not also be stored in a global variable since there is a generic MSG
    • Solution:
      • Make an owned Patch which has no reference to the node
          struct OwnedPatch{
              tag: Option<TAG>,
              node: Node<'a,...>,
          }
      • Store the patches as Closures, so as to get away with the generics
        • but then there will be error can be shared between threads because closure is not Send
      • Make a DomPatch which a DOM version of the patch with the target node and created node
  • Find a way to break up building the view into multiple frames, since view can take a long time to build
  • Find a way to break up diffing the current vdom and the new vdom as they can also take a bit of long time as well.
  • Add benchmark function for using CACHE_ELEMENT and not
  • Make dispatch pending patches break when the animation frame timeouts, same way as dispatching pending msgs
  • Check the last time the dom is updated, if it is less than 17ms, delay the dom update until 17ms has elapsed since the last update.
  • Make use of talc allocator for faster and leaner memory

Maintenance

  • Move sauron-markdown into it's own repo, for keeping sauron slim.
  • Move jss into a new crate sauron-jss for keeping sauron slim.
    • Use json crate for jss.
      • The quote on keys are optional, so this is good for use in writing css.
  • Enumerate the exported modules and structs in prelude instead of just using glob(ie: *).
  • Fix the data-viewer example to use Components on the views rather than Application
  • Revisit and use style_name identifier in usage of jss in examples
  • Move html::units to jss crate
  • Rename DomUpdater to DomPatcher.
    • move apply_patches into DomPatcher.
  • Rename CreatedNode to DomNode.
    • Maybe completely remove CreatedNode
  • Move fields from DomUpdater into Program such as
    • current_vdom
    • root_node,
    • active_closures,
    • pending_patches
  • Remove the use of wee_alloc crate
  • sauron-core and jss should have a different version of Value, where jss value can be converted into.
    • Value struct needs to reside here, since it is a corner-stone data structure and used eveywhere.
    • Maybe Value should be in a very common crate. Say sauron-common.
  • Use xtask for scripting: building, checking, publishing, running the examples
  • Use { workspace = true } to common dependencies for easieir maintenance work.

Bug

  • When 2 nodes with multiple similar keys, multiple replace node patch is generated. But it couldn't seem to find the correct target element. or the target element has no parent, therefore can not replace/insert the node.
    • This is solved by getting the type_id of the closure.
  • Add more test for recycled nodes with keys
  • When 2 text are next to each other, the second text will become a comment
  • Runtime errors when using fragments
  • usage of classes_flag seems to be broken with complext trait requirement.
    • This should work very simply classes_flag([("todo", true), ("editor", is_editing)])
  • Organize the generated docs, such that the most commonly used structs and enums are displayed on the first page of docs.rs/sauron instead of under prelude module

Limitations

  • In rust, no two closures, even if identical, have the same type. Therefore closure can not be check for equality. Solved by using the original type_id of the function callback
    • In sauron node are matched and reused aggressively, except when the keys are different then the node is discarded.
    • If we don't reuse nodes with event listeners aggressively, then we would have a performance penalty, since every recreation of the node with event listener will have to discard and create a new one for it, even if it is matching itself.
    • Adding key attribute provides a good trade off.