Skip to content

Commit 44fa879

Browse files
authored
make macro components private for now (#3850)
* make macro components private for now We're exploring a more powerful approach to macro components (see #3846), therefore, to prevent breaking changes, we do not want to make the current interface public for the LiveView 1.1 release. Instead, we just expose colocated hooks and colocated JS for now. * add note about macro component API
1 parent fe9bf1c commit 44fa879

File tree

4 files changed

+131
-128
lines changed

4 files changed

+131
-128
lines changed

CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ Here is a quick summary of the changes necessary to upgrade to LiveView v1.1:
3737
env: %{"NODE_PATH" => [Path.expand("../deps", __DIR__), Mix.Project.build_path()]},
3838
```
3939
40-
## Macro components and colocated hooks
40+
## Colocated hooks
4141
42-
A `Phoenix.Component.MacroComponent` defines a compile-time transformation of a HEEx tag. This can be used to transform a tag and its content into something else, for example to perform compile time syntax highlighting, or even remove tags from the template entirely and write them elsewhere. `Phoenix.LiveView.ColocatedHook` is a macro component that allows you to co-locate LiveView [JavaScript hooks](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook) next to the component code that uses them, while ensuring they are included in your regular JavaScript bundle. A colocated hook is defined by placing the special `:type` attribute on a `<script>` tag:
42+
When writing hooks for a specific component, the need to place the JavaScript code in a whole separate file often feels inconvenient. LiveView 1.1 introduces colocated hooks to allow writing the hook's JavaScript code in the same file as your regular component code.
43+
44+
A colocated hook is defined by placing the special `:type` attribute on a `<script>` tag:
4345

4446
```elixir
4547
alias Phoenix.LiveView.ColocatedHook
@@ -80,7 +82,9 @@ Colocated hooks are extracted to a `phoenix-colocated` folder inside your `_buil
8082
})
8183
```
8284

83-
The `phoenix-colocated` folder has subfolders for each application that uses colocated hooks, therefore you'll need to adjust the `my_app` part of the import depending on the name of your project (defined in your `mix.exs`). You can read more about colocated hooks in the module documentation of `Phoenix.LiveView.ColocatedHook` and `Phoenix.LiveView.ColocatedJS`.
85+
The `phoenix-colocated` folder has subfolders for each application that uses colocated hooks, therefore you'll need to adjust the `my_app` part of the import depending on the name of your project (defined in your `mix.exs`). You can read more about colocated hooks in the module documentation of `Phoenix.LiveView.ColocatedHook`. There's also a more generalized version for colocated JavaScript, see the documentation for `Phoenix.LiveView.ColocatedJS`.
86+
87+
We're planning to make the private `Phoenix.Component.MacroComponent` API that we use for those features public in a future release.
8488

8589
## Types for public interfaces
8690

@@ -209,7 +213,6 @@ To enable this, a new callback called `annotate_slot/4` was added. Custom implem
209213
* Add type annotations to all public JavaScript APIs ([#3789](https://github.com/phoenixframework/phoenix_live_view/pull/3789))
210214
* Add `Phoenix.LiveView.JS.ignore_attributes/1` to allow marking specific attributes to be ignored when LiveView patches an element ([#3765](https://github.com/phoenixframework/phoenix_live_view/pull/3765))
211215
* Add `Phoenix.LiveView.Debug` module with functions for inspecting LiveViews at runtime ([#3776](https://github.com/phoenixframework/phoenix_live_view/pull/3776))
212-
* Add `Phoenix.Component.MacroComponent` ([#3810](https://github.com/phoenixframework/phoenix_live_view/pull/3810))
213216
* Add `Phoenix.LiveView.ColocatedHook` and `Phoenix.LiveView.ColocatedJS` ([#3810](https://github.com/phoenixframework/phoenix_live_view/pull/3810))
214217
* Add `:update_only` option to `Phoenix.LiveView.stream_insert/4` ([#3573](https://github.com/phoenixframework/phoenix_live_view/pull/3573))
215218
* Use [`LazyHTML`](https://hexdocs.pm/lazy_html/) instead of [Floki](https://hexdocs.pm/floki) internally for LiveViewTest

lib/phoenix_component/macro_component.ex

Lines changed: 121 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,125 @@
11
defmodule Phoenix.Component.MacroComponent do
2-
@moduledoc """
3-
A macro component is a special type of component that can modify its content
4-
at compile time.
5-
6-
Instead of introducing a special tag syntax like `<#macro-component>`, LiveView
7-
implements them using a special `:type` attribute as the most useful macro
8-
components take their content and extract it to somewhere else, for example
9-
to a file in the local file system. A good example for this is `Phoenix.LiveView.ColocatedHook`
10-
and `Phoenix.LiveView.ColocatedJS`.
11-
12-
## AST
13-
14-
Macro components work by defining a callback module that implements the
15-
`Phoenix.LiveView.MacroComponent` behaviour. The module's `c:transform/2` callback
16-
is called for each macro component used while LiveView compiles a HEEx component:
17-
18-
```heex
19-
<div id="hey" phx-hook=".foo">
20-
<!-- content -->
21-
</div>
22-
23-
<script :type={ColocatedHook} name=".foo">
24-
export default {
25-
mounted() {
26-
this.el.firstElementChild.textContent = "Hello from JS!"
27-
}
28-
}
29-
</script>
30-
```
31-
32-
In this example, the `ColocatedHook`'s `c:transform/2` callback will be invoked
33-
with the AST of the `<script>` tag:
34-
35-
```elixir
36-
{"script",
37-
[{"name", ".foo"}],
38-
[
39-
"\\n export default {\\n mounted() {\\n this.el.firstElementChild.textContent = \\"Hello from JS!\\"\\n }\\n }\\n"
40-
]}
41-
```
42-
43-
This module provides some utilities to work with the AST, which uses
44-
standard Elixir data structures:
45-
46-
1. A HTML tag is represented as `{tag, attributes, children, meta}`
47-
2. Text is represented as a plain binary
48-
3. Attributes are represented as a list of `{key, value}` tuples where
49-
the value is an Elixir AST (which can be a plain binary for simple attributes)
50-
51-
> #### Limitations {: .warning}
52-
> The AST is not whitespace preserving. When using macro components,
53-
> the original whitespace between attributes is lost.
54-
>
55-
> Also, macro components can currently only contain simple HTML. Any interpolation
56-
> like `<%= @foo %>` or components inside are not supported.
57-
58-
## Example: a compile-time markdown renderer
59-
60-
Let's say we want to create a macro component that renders markdown as HTML at
61-
compile time. First, we need some library that actually converts the markdown to
62-
HTML. For this example, we use [`mdex`](https://hex.pm/packages/mdex).
63-
64-
We start by defining the module for the macro component:
65-
66-
```elixir
67-
defmodule MyAppWeb.MarkdownComponent do
68-
@behaviour Phoenix.Component.MacroComponent
69-
70-
@impl true
71-
def transform({"pre", attrs, children}, _meta) do
72-
markdown = Phoenix.Component.MacroComponent.to_string(children)
73-
html_doc = MDEx.to_html!(markdown)
74-
75-
{"div", attrs, [html_doc]}
76-
end
77-
end
78-
```
79-
80-
That's it. Since the div could contain nested elements, for example when using
81-
an HTML code block, we need to convert the children to a string first, using the
82-
`Phoenix.Component.MacroComponent.ast_to_string/1` function.
83-
84-
Then, we can simply replace the element's contents with the returned HTML string from
85-
MDEx.
86-
87-
We can now use the macro component inside our HEEx templates:
88-
89-
defmodule MyAppWeb.ExampleLiveView do
90-
use MyAppWeb, :live_view
91-
92-
def render(assigns) do
93-
~H\"\"\"
94-
<pre :type={MyAppWeb.MarkdownComponent} class="prose mt-8">
95-
## Hello World
96-
97-
This is some markdown!
98-
99-
```elixir
100-
defmodule Hello do
101-
def world do
102-
IO.puts "Hello, world!"
103-
end
104-
end
105-
```
106-
</pre>
107-
\"\"\"
108-
end
109-
end
110-
111-
Note: this example uses the `prose` class from TailwindCSS for styling.
112-
113-
One trick to prevent issues with extra whitespace is that we use a `<pre>` tag in the LiveView
114-
template, which prevents the `Phoenix.LiveView.HTMLFormatter` from indenting the contents, which
115-
would mess with the markdown parsing. When rendering, we replace it with a `<div>` tag in the
116-
macro component.
117-
118-
Another example for a macro component that transforms its content is available in
119-
LiveView's end to end tests: a macro component that performs
120-
[syntax highlighting at compile time](https://github.com/phoenixframework/phoenix_live_view/blob/38851d943f3280c5982d75679291dccb8c442534/test/e2e/support/colocated_live.ex#L4-L35)
121-
using the [Makeup](https://hexdocs.pm/makeup/Makeup.html) library.
122-
"""
2+
@moduledoc false
3+
4+
# A macro component is a special type of component that can modify its content
5+
# at compile time.
6+
#
7+
# Instead of introducing a special tag syntax like `<#macro-component>`, LiveView
8+
# implements them using a special `:type` attribute as the most useful macro
9+
# components take their content and extract it to somewhere else, for example
10+
# to a file in the local file system. A good example for this is `Phoenix.LiveView.ColocatedHook`
11+
# and `Phoenix.LiveView.ColocatedJS`.
12+
#
13+
# ## AST
14+
#
15+
# Macro components work by defining a callback module that implements the
16+
# `Phoenix.LiveView.MacroComponent` behaviour. The module's `c:transform/2` callback
17+
# is called for each macro component used while LiveView compiles a HEEx component:
18+
#
19+
# ```heex
20+
# <div id="hey" phx-hook=".foo">
21+
# <!-- content -->
22+
# </div>
23+
#
24+
# <script :type={ColocatedHook} name=".foo">
25+
# export default {
26+
# mounted() {
27+
# this.el.firstElementChild.textContent = "Hello from JS!"
28+
# }
29+
# }
30+
# </script>
31+
# ```
32+
#
33+
# In this example, the `ColocatedHook`'s `c:transform/2` callback will be invoked
34+
# with the AST of the `<script>` tag:
35+
#
36+
# ```elixir
37+
# {"script",
38+
# [{"name", ".foo"}],
39+
# [
40+
# "\\n export default {\\n mounted() {\\n this.el.firstElementChild.textContent = \\"Hello from JS!\\"\\n }\\n }\\n"
41+
# ]}
42+
# ```
43+
#
44+
# This module provides some utilities to work with the AST, which uses
45+
# standard Elixir data structures:
46+
#
47+
# 1. A HTML tag is represented as `{tag, attributes, children, meta}`
48+
# 2. Text is represented as a plain binary
49+
# 3. Attributes are represented as a list of `{key, value}` tuples where
50+
# the value is an Elixir AST (which can be a plain binary for simple attributes)
51+
#
52+
# > #### Limitations {: .warning}
53+
# > The AST is not whitespace preserving. When using macro components,
54+
# > the original whitespace between attributes is lost.
55+
# >
56+
# > Also, macro components can currently only contain simple HTML. Any interpolation
57+
# > like `<%= @foo %>` or components inside are not supported.
58+
#
59+
# ## Example: a compile-time markdown renderer
60+
#
61+
# Let's say we want to create a macro component that renders markdown as HTML at
62+
# compile time. First, we need some library that actually converts the markdown to
63+
# HTML. For this example, we use [`mdex`](https://hex.pm/packages/mdex).
64+
#
65+
# We start by defining the module for the macro component:
66+
#
67+
# ```elixir
68+
# defmodule MyAppWeb.MarkdownComponent do
69+
# @behaviour Phoenix.Component.MacroComponent
70+
#
71+
# @impl true
72+
# def transform({"pre", attrs, children}, _meta) do
73+
# markdown = Phoenix.Component.MacroComponent.to_string(children)
74+
# html_doc = MDEx.to_html!(markdown)
75+
#
76+
# {"div", attrs, [html_doc]}
77+
# end
78+
# end
79+
# ```
80+
#
81+
# That's it. Since the div could contain nested elements, for example when using
82+
# an HTML code block, we need to convert the children to a string first, using the
83+
# `Phoenix.Component.MacroComponent.ast_to_string/1` function.
84+
#
85+
# Then, we can simply replace the element's contents with the returned HTML string from
86+
# MDEx.
87+
#
88+
# We can now use the macro component inside our HEEx templates:
89+
#
90+
# defmodule MyAppWeb.ExampleLiveView do
91+
# use MyAppWeb, :live_view
92+
#
93+
# def render(assigns) do
94+
# ~H\"\"\"
95+
# <pre :type={MyAppWeb.MarkdownComponent} class="prose mt-8">
96+
# ## Hello World
97+
#
98+
# This is some markdown!
99+
#
100+
# ```elixir
101+
# defmodule Hello do
102+
# def world do
103+
# IO.puts "Hello, world!"
104+
# end
105+
# end
106+
# ```
107+
# </pre>
108+
# \"\"\"
109+
# end
110+
# end
111+
#
112+
# Note: this example uses the `prose` class from TailwindCSS for styling.
113+
#
114+
# One trick to prevent issues with extra whitespace is that we use a `<pre>` tag in the LiveView
115+
# template, which prevents the `Phoenix.LiveView.HTMLFormatter` from indenting the contents, which
116+
# would mess with the markdown parsing. When rendering, we replace it with a `<div>` tag in the
117+
# macro component.
118+
#
119+
# Another example for a macro component that transforms its content is available in
120+
# LiveView's end to end tests: a macro component that performs
121+
# [syntax highlighting at compile time](https://github.com/phoenixframework/phoenix_live_view/blob/38851d943f3280c5982d75679291dccb8c442534/test/e2e/support/colocated_live.ex#L4-L35)
122+
# using the [Makeup](https://hexdocs.pm/makeup/Makeup.html) library.
123123

124124
@type tag :: binary()
125125
@type attribute :: {binary(), Macro.t()}

lib/phoenix_live_view/colocated_hook.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
defmodule Phoenix.LiveView.ColocatedHook do
22
@moduledoc ~S'''
3-
A `Phoenix.Component.MacroComponent` that extracts [hooks](js-interop.md#client-hooks-via-phx-hook)
3+
A special HEEx `:type` that extracts [hooks](js-interop.md#client-hooks-via-phx-hook)
44
from a co-located `<script>` tag at compile time.
55
66
## Introduction
77
8-
Colocated hooks are defined as macro components with `:type={Phoenix.LiveView.ColocatedHook}`:
8+
Colocated hooks are defined as with `:type={Phoenix.LiveView.ColocatedHook}`:
99
1010
defmodule MyAppWeb.DemoLive do
1111
use MyAppWeb, :live_view

lib/phoenix_live_view/colocated_js.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
defmodule Phoenix.LiveView.ColocatedJS do
22
@moduledoc """
3-
A `Phoenix.Component.MacroComponent` that extracts any JavaScript code from a co-located
3+
A special HEEx `:type` that extracts any JavaScript code from a co-located
44
`<script>` tag at compile time.
55
66
Colocated JavaScript is a more generalized version of `Phoenix.LiveView.ColocatedHook`.

0 commit comments

Comments
 (0)