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

Initial commit of Components #766

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open

Initial commit of Components #766

wants to merge 10 commits into from

Conversation

dmjio
Copy link
Owner

@dmjio dmjio commented Feb 19, 2025

🍜 Miso Components

Adds React-style components to Miso. This allows the embedding of App within App using an existential wrapper Component implemented as a new branch of the View tree.

⚠️ This is a work in progress ⚠️

API

data View action where                                                                                                                                                                                                                    
  Node :: NS -> MisoString -> Maybe Key -> [Attribute action] -> [View action] -> View action                                                                                                                                             
  Text :: MisoString -> View action                                                                                                                                                                                                       
  TextRaw :: MisoString -> View action                                                                                                                                                                                                    
  ComponentNode :: Component -> View action                                                                                                                                                                                               
                                                                                                                                                                                                                                          
data Component = forall model action . Eq model => Component (App model action)                                                                                                                                                           
                                                                                                                                                                                                                                          
component :: Eq model => App model a -> View action                                                                                                                                                                                       
component app = ComponentNode (Component app)                                                                                                                                                                                                                                             
-- ^ note how `action` and `a` are different. This is how we embed `App` inside each other (recursively)

notify :: m -> App model action -> action -> Effect a m
-- ^ `actions`'s unify here, ensuring that we're sending the right `action` to the right `App`
-- `m` is still required because `Effect` is not a `State` monad, yet.

Example usage

calendarApp :: App CalendarModel CalendarAction
calendarApp = App { view = viewModelCalendar, ... }

viewModelCalendar :: CalendarModel -> View CalendarAction
viewModelCalendar x = div_ [ id_ "calendar" ]
  [ "Calendar app - with an embedded todo-list component app"
  , component todoListApp -- <-- here is where we embed
  ]
 
todoListApp :: App TodoModel TodoAction
todoListApp = App { update = updateModelTodo, ... }
  
updateModelTodo :: TodoAction -> TodoModel -> Effect TodoAction TodoModel
updateModelTodo (AddTodo todo) m = do
  notify m calendarApp (MakeCalendarEntryToday todo) 
  -- ^ Here is where we can write to the Calendar App's Sink. 
  pure m { entries = entry : entries m }
  • Adds Component existential wrapper used for nesting App
  • Adds notify for bidirectional communication between components (parent, child or siblings)
  • Modifies diff.js to mount / unmount components
  • Creates top-level ComponentMap used to store sinks and threads for each Component
  • Adds components example (modified from simple.jsexe)
  • Renames Notify to Waiter (reuse 'notify' elsewhere)
  • Creates Miso.Internal

Outstanding:

  • Implement Key on Component
  • Add tests for Component mounting and unmounting, and syncChildren
  • Add documentation
  • Consider modifying Effect to be a State monad so m won't be required for use with notify
  • Implement stubs for diffing components w/ other elements in diff.js
  • Make a better example in examples/components
  • Add component lifecycle hooks (onMounted, onUnmounted, onBefore*, onAfter*, onException, etc.)
  • Experiment with ways to statically enforce uniqueness of App names and mountPoints (e.g. App "todo-app" model action)

Pitfalls:

  • It is possible to create cycles in the component graph by mutually recursive notifications. This can lead to runaway recursion. No real good way to statically keep this from occurring, yet.

  • mountPoint is now used as both the component name and the id of the mountPoint element. With the exception of top-level App, which is not a Component, and should specify "body", or an element with an id that already exists.

  • Component names must be unique, and will be used to create a div. Consider somehow making this type safe.

- Adds Component existential wrapper used for nesting App
- Adds `notify` for bidirectional communication between components (parent, child or siblings)
- Modifies diff.js to mount / unmount components
- Creates top-level ComponentMap used to store sinks and threads for each Component
- Adds components example (modified from simple.jsexe)
- Renames Notify to Waiter (reuse 'notify' elsewhere)
- Creates Miso.Internal
@brakubraku
Copy link

Let's go!

@brakubraku
Copy link

Perhaps effectful can be a useful guide/inspiration on how to implement static safety?

@dmjio
Copy link
Owner Author

dmjio commented Feb 21, 2025

@brakubraku Will check it out. We have options. Want to keep it simple for the LLMs too. You can join our matrix server if you want to discuss more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants