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.

7 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 ā€¦

@idi527 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