Hologram - full stack isomorphic Elixir web framework

That would be awesome @bryanjos! I’d love to use your help.

2 Likes

It’s probably worth looking into compiling to WASM at one point – it’s a huge task, I know, but it would be more in line with where things seem to be going.

4 Likes

Yeah, definitely, I agree.

1 Like

That’s true and I think there’s room for a hybrid approach. Something that starts out mostly as JS and transitions more and more to WASM as it matures.

4 Likes

Thanks @bartblast … Suggest using this forum for discussions/suggestions and reserve GitHub issues for “demonstrable Issues” and/or enhancement requests as per https://github.com/phoenixframework?

Understand you’re pressed for time … happy to help with add/edit/enhance docs and examples once a standalone project bare bones structure is in place.

Thanks again for 0.1.0!

3 Likes

I want to clarify this section a bit. This sentence only applies if you try to use LiveView for client-only interactions, but the docs are very clear that you should not use LiveView for that.

I am aware that you know this, I am commenting to avoid confusion in other threads. :slight_smile:

Assuming you have to hit the server, for example to talk to a database, LiveView response time is actually better than regular HTTP requests because it works over an existing authenticated web socket connection. So the work of parsing headers, authenticate users, etc is not done on every server interaction. Hologram and LiveView should be similar here, as both are WebSockets based.

Similarly, Fly.io gets you closer to your users, which means any server-based application could benefit from Fly.io.

In other words, LiveView is for server based interactions and it excels at that, with or without Fly.io. Indeed, for more complex cases on the client, then you need custom JS (or something that aims to fully cover both server and client, like Hologram).

18 Likes

Thanks for the clarification @josevalim. I can see how some folks could get confused by this section.

1 Like

You just posted about this in the Client-side rendering, server-side state thread.

There are some nice developments there how you can ease the pain of request/response and syncing state. (which is already perfect with LV, but there are use cases that can’t be done with LV). It’s not clear how Hologram handes this. Sure, the state is on the client, but what if I need (parts of) it on the server. I see there is command, but in the example its not used (or by some magic). Will we have completely separate states on server and client with Hologram?

And also some good news for you:

Until someone manages to create quantum internet (e.g. by taking advantage of entanglement), there are no workarounds for this problem. Not sure if this is even technically possible, though

No, you’ll be safe from that.

Yes, you’re correct - commands are for running backend or async code, e.g. database access, filesystem, etc., commands are not transpiled automatically (only actions are). Commands receive params and may return action, but there is no state kept on the server.

I’m curious on how do you plan auth working with it. Server should be able to handle both guest and logged in users and it should be aware of which one has submitted data in login form. :thinking:

How does offline SPA would work with for example routing? Would it be possible to render other template (here named page like MyPage) when app would be disconnected, but offline application cached data locally in some storage? :file_cabinet:

Also it would be worth to have:

  1. A fallback action which is called when SPA application is disconnected as it would allow the application to fail, call said fallback action and therefore notify user that x feature requires connection to internet. :arrow_backward:

  2. server states for current page (like count) and all pages (like current_user) :brain:

Also I saw 0.2.0 in Projects tab, but there is only information that ~half tasks are done. Do you have some “lazy” estimates (like this month/summer/year)? :clock:

Your roadmap is interesting. Do you plan to use Github releases? I saw you don’t have any even for version 0.1.0 and I would like to subscribe it in my RSS reader. :smiling_imp:

Anyway, I’m waiting for more news, documentation and examples. :+1:

Auth will use session data. I haven’t gotten to work on session yet, but I’ve got a few ideas:

  • session will be accessible through commands at all times and on page init (so only through the server for security reasons)
  • stored in HTTP-only cookie or database

It would be possible to implement offline SPA if all pages are downloaded in a bundle and client storage is used. But I’m not targeting this behaviour in the initial phases. In the beginning, redirecting to a new page will require a connection with the server. But at least once the page is loaded it can work offline provided the client doesn’t load new data from the server through commands. Eventually, client storage (such as IndexedDB or local storage) could be used to improve this. It could work similarly to mobile apps - once there is a connection the app syncs the data with the server backend.

As for the Projects tab it’s not up to date, at one point I decided that it creates too much overhead at the moment since I work on this myself, and right now I’m using plain old OmniFocus for project management (this will change when there are contributors).

Note also that the roadmap is not up to date. For the past few months, I’ve been working on a rewrite full time, which fixed all pressing issues and actually, most of the syntax is already done. Sorry for the confusion! I’ll get back to the roadmap once I get to rewriting the browser tests of the features listed on the roadmap.

Yeah, I’ll probably use GitHub releases eventually.

Version 0.2.0 with full Elixir syntax support (except the stuff related to parallelism), with a demo app and with a basic “getting started guide” will be done this summer.

1 Like

Excellent news! :rocket:

I really understand, so for me it’s definitely not a problem, but not everyone is enough patient and understanding or they just don’t have a knowledge about possible vacations and other things. Called update because I’m interested, but also to avoid such topics:

https://elixirforum.com/t/anyone-know-about-the-status-of-the-funwithflags-project/56670?u=eiji

Having in mind this net time I believe it would be worth to add just a few words on top of readme like:

I’m rewriting project to version 0.2.0 this summer. Therefore below roadmap is outdated.

btw. If you would have a complete guide of how to make hologram app feel free to ping me here. I can’t say how it would be in future, but one thing I’m sure about is that at least I would like to give it a try, search for potential edge cases and give my suggestions.

It would be amazing if client would have 2 states. One accessible only on single page (let’s call it page_state) and other accessible on all pages (let’s call it state). For example changing language or theme could be easily done on client and should be shared on every page unlike counter in example.

defmodule MyPage do
  use Hologram.Page

  route "/my-page-path"

  def init(_params, _conn, state) do
    initial_page_state = %{count: 0}
    {initial_page_state, state}
  end

  def template do
    # …
  end

  def action(:change_language, params, page_state, state) do
    {page_state, put(state, lang, params.lang))
  end

  def action(:increment, params, page_state, state) do
    {put(page_state, :count, page_state.count + params.by), state)
  end

  def command(:log_in, params, session) do
    # log in
    {:logged_in, Map.put(session, :current_user, current_user)}
  end
end

I’m not really sure if bundling is needed at all. I would say that you could do one much simpler thing i.e. add a hologram command to cache specific page i.e. the user clicks on some button to make sure data would be available also offline and the app literally do a simple loop calling just one command:

# call hologram command
required_offline_pages = [IndexPage, ShowPage]
Enum.map(required_offline_pages, &send_cache_hologram_command/1)

# call app command
required_offline_data = [Blog,Post]
Enum.map(required_offline_data, &send_fetch_all_command/1)

# this way in let's say IndexPage we could replace such a custom code
data = send_fetch_all_command(Blog.Post)
# to for example
data = send_fetch_all_command(Blog.Post, fallback: &read_from_cache/1)

Since as you said once the page is loaded it works offline, so you already have code to load specific page and call init. If so you can just use said code, but without calling page init. Do you think it’s lots of work this way?

Taking into account that pages and layouts will be components (which may have their own state - similar to LiveComponent) and that Hologram will have something similar to LiveView sticky components, the situation which you describe (language, theme, etc) can be handled by sticky layout components (I’ll probably call them something different). I will evaluate all the available options related to keeping state, but we need to take into account such things as maintainability and debugging (I plan to implement something similar to Elm Time Travel among other things). It’s very important to be able to reason about the state changes easily.

That’s interesting, a little similar to Ember model store preloading (if I understand correctly) but I think people may find it a little “too magical”. But I agree - something like that would be very helpful for offline apps. I’ll evaluate some options in the later stages.

1 Like

If you follow LiveView meaning then it’s not good. Sticky components are not re-rendered losing their state, but kept when changing Page. Look that changing language and other options that are used in many or even all components / pages may be completely outside of layout components.

The simplest example is this forum. User preferences would be a standard Page like any other. There is absolutely no need to make it sticky. Since we are talking about client code how about a simple API for accessing let’s say … window.__HologramAppState__?

This way developers would be able to write for example:

# …
def action(:change_language, params, state) do
  # save user preferences in database
  call_update_lang_command(params.lang)
  # changes page state
  put_window_state(:lang, params.lang)
  # window.__HologramAppState__.lang = params.lang
  state
end
# …

This way it should be simple to add get, put and update functions. Those functions should be easy to implement, cross browser, give lots of flexibility for an application’s global config and should not conflict with any JavaScript library.

Actually, that’s how it’s going to work in Hologram as well, if a component is flagged as “sticky” (or “persistent” - I haven’t decided on the name yet), its state won’t be lost on new page redirect, and it will be kept in the DOM (no patch).

So if we have a page:

defmodule MyPage do
  route "/hello"

  def template do
    ~H"Hello world!"
  end
end

Hologram implicitly wraps this page template in the default layout component, so in practice it’s:

<Layout>
  <Page />
</Layout>

If we make the layout component sticky, it won’t lose its state.
Page state is also implicitly passed to the layout and available in the layout template as vars, but if we’ve got @lang or @theme state on the layout only, it won’t be overwritten by page state when we redirect to a new page.

It may be better to keep such state as theme and lang in session anyway, I just showed some examples of how it could theoretically be used.