PhoenixFramework HTTP/2

Ok @aseigo here it is as promised, and only 4 weeks delayed :slight_smile: !

I pushed this out quickly because it had been sitting in my drafts for a few weeks, so let me know if anything seems off.

6 Likes

Awesome! Thanks; will read it today and let you know if I have any feedback. Thanks for writing this up, most useful…

1 Like

Let me know specifically if there are any areas that I’m glossing over, especially with the JS stuff.

@ChaseGilliam is there a reason why webpack is run as a server instead of emitting static js? Is that related to the bit at the end:

I’ll leave it as an exercise for readers to explore pushing multiple js files to the client and combining Webpack’s lazy loading to push files on demand.

If so could you elaborate on that?

Minus webpack, as far as I can tell all thats needed to enable http2 is the updated deps, the cert, and the changed https endpoint config. Neat!

1 Like

why webpack is run as a server instead of emitting static js

This is just in development mode so that you can make use of code reloading. For production you would use webpack -p or similar to build production assets.

Minus webpack, as far as I can tell all thats needed to enable http2 is the updated deps, the cert, and the changed https endpoint config.

Right, that’s basically the minimal set of requirements. However, webpack gives you nice tools for splitting your JavaScriot into N small files. This helps you leverage HTTP/s’s multiplexing. I didn’t want to go into too much detail on that point, because Webpack 4 will have some major changes, and I want to revisit the topic once their ecosystem has transitioned to their new APIs.

Also, thanks for checking out the guide!

2 Likes

i’ve been reading up on http2 push and it needs to be initiated from server. Can you tell me what you were thinking about regarding webpack lazy load and push?

Webpack’s lazy load is well suited to pulling js on an as needed basis, but I suppose you could you it for push, I’m just not sure how. In general I’m still thinking through how best use push with Phoenix, and when to use that vs. channels/web sockets.

1 Like
  defmodule Http2TodayWeb.LayoutView do
    use Http2TodayWeb, :view
    def js_script_tag do
      if Mix.env == :prod do
        # In production we'll just reference the file
        """
        <script src="<%= static_path(@conn, "/js/vendor.js") %>"></script>
        <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
        """
      else
        # In development mode we'll load it from our webpack dev server
        """
        <script src="https://localhost:8080/vendor.js"></script>
        <script src="https://localhost:8080/app.js"></script>
        """
      end
    end

    # Ditto for the css
    def css_link_tag do
      if Mix.env == :prod do
        "<link rel=\"stylesheet\" href=\"<%= static_path(@conn, \"/css/app.css\") %>" 
      else
        "<link rel=\"stylesheet\" type=\"text/css\" href=\"https://localhost:8080/css/app.css\" />"
      end
    end
  end

can be somewhat optimized if you move if outside of the function

  defmodule Http2TodayWeb.LayoutView do
    use Http2TodayWeb, :view

    js_script_tag = 
      if Mix.env == :prod do
        # In production we'll just reference the file
        """
        <script src="<%= static_path(Http2TodayWeb.Endpoint, "/js/vendor.js") %>"></script>
        <script src="<%= static_path(Http2TodayWeb.Endpoint, "/js/app.js") %>"></script>
        """
      else
        # In development mode we'll load it from our webpack dev server
        """
        <script src="https://localhost:8080/vendor.js"></script>
        <script src="https://localhost:8080/app.js"></script>
        """
      end
    
    def js_script_tag, do: unquote(js_script_tag)

    # Ditto for the css
    css_link_tag = 
      if Mix.env == :prod do
        "<link rel=\"stylesheet\" href=\"<%= static_path(Http2TodayWeb.Endpoint, \"/css/app.css\") %>" 
      else
        "<link rel=\"stylesheet\" type=\"text/css\" href=\"https://localhost:8080/css/app.css\" />"
      end
    
    def css_link_tag, do: unquote(css_link_tag)
  end

if you care about small performance gains …

1 Like

Not just a performance cain, but you want to keep Mix.* calls outside of function calls so they stay at compile-time only. If you deploy a release then Mix will not exist (it’s a build system after all) so that would crash if inside a function (you can unquote it to move it outside the function if necessary). ^.^

3 Likes

Yeah, that’s much more important, didn’t think of that …

@idiot and @OvermindDL1, that’s a great point. I’ll make a note and update the post soon.

what would that look like in this case? I’ve never used unquote for that sort of thing.

env = Mix.env()
def func(), do: unquote(env)

probably.

2 Likes

and that works in prod?

When this code gets compiled, Mix.env still presumably works. And then in prod it’s the same as

def func(), do: :prod

So, yes.

1 Like

Oh, of course. That makes sense. Thanks!

Yeah if you do something like:

def blah() do
  IO.inspect(unquote(Mix.env()))
end

It’ll get compiled like:

def blah() do
  IO.inspect(:dev)
end

For whatever the environment is.

1 Like

oh cool. I’ll have to start using that.

Do note, if you ever want to encode something that is not a basic value then you’ll need to escape it into the AST, so change the unquote(...) into being unquote(Macro.escape(...)), this also means you cannot encode transient values like PID’s and REF’s. :slight_smile:

2 Likes

I finally got around to updating this to use unquote.

defp env do
  unquote(Mix.env())
end

Thanks for the help!

1 Like