msaraiva
Surface - A component-based library for Phoenix LiveView
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative way to express and use components in Phoenix. Some of the main features include:
- Components as modules - they can be stateless, stateful, data-only or compile-time
- Declarative properties - explicitly declare the inputs (properties) of each component (like React)
- An HTML-centric templating language with built-in directives (
:for,:if, …) and syntactic sugar for attributes (inspired by Vue.js) - Contexts - allows parent components to share data with its children without passing them as properties
- Compile-time checking of components and their properties
- Integration with editor/tools for warnings, syntax highlighting, jump-to-definition, auto-completion and more
A work-in-progress live demo with more details is available at surface-demo.msaraiva.io.
Installation instructions and other useful information can be found at github.com/msaraiva/surface.
And finally, a VS Code extension that adds support for syntax highlighting is also available at
marketplace.visualstudio.com.
There’s still a lot of work to do and I hope some of you out there might be willing to help me in this journey. Bear in mind that the LiveView Component API has not reached a stable version yet and it’s currently under development, so although we try to keep track of the latest changes as much as possible, there might be temporary incompatibilities between Surface and Phoenix LiveView until a final version is released.
I’d like to thank @chrismccord and the Phoenix Core Team for buying the idea of reusable components and bringing the necessary basic concepts to the core API.
I also want to thank @josevalim, not only for the many valuable insights during development but also for his inestimable help with the main parser.
Happy coding!
-marlus
Most Liked
msaraiva
Call me stupid but can someone explain the value in having such an abstraction in practice?
I think there’s no need to call you stupid. It’s a valid question. However, I believe that either you didn’t have the opportunity to read the “Getting started” guide at surface-demo.msaraiva.io or, most likely, I did a terrible job trying to explain some of the benefits there. So, please, let me try again.
In the Grid example it looks like you could have put the
<table>inside of a template in the render function
Of course I could, but then I’d have missed the whole point of demonstrating how to use children as data. The Grid example was extracted from the section called “Children as data” of the “Getting started” guide mentioned above. That guide is not meant to be a tutorial on how you should design your application, it’s just a guide with simple examples that demonstrates the main features. The way you use those features is up to you.
and it would work the same
Well, it’s an abstraction on top of EEx, so it expected to work the same. Actually, by the end of the day, anything you can do with Surface can be done with EEx, just like anything you can do with React/JSX can also be done with pure Javascript. It’s hard to know when to stop adding abstractions on top of existing ones. There is always a tradeoff.
and be more similar to how templates work with EEx.
I have no intention whatsoever to keep Surface similar to EEx. On the contrary, I don’t want any dependency between Surface’s and EEx’s syntax. Each one of them tries to solve different problems. The main issue with EEx is that it makes no distinction between plain text and HTML (or any other structured format). Everything is treated as text. When using EEx, all you end up with is a big unstructured list of lists of chunks of text, consequently, a lot of useful information that we could use in our favour to boost productivity is lost. Here are some, IMO, clear benefits of keeping that information:
Normalized syntax for HTML elements and components
Let’s take a look at how HTML elements and Phoenix components are defined in EEx:
<input style="padding: 1px">
<%= live_component(@socket, Input, style: "padding: 1px") %>
The syntax is completely different. One is declarative and clean, the other is a function call inside a weird <%= ... %>.
Now let’s take a look at how HTML elements and Surface components are defined:
<input style="padding: 1px">
<Input style="padding: 1px">
Now both definitions are declarative, clean and use the same syntax. That makes the reading experience much more pleasant. I can also read it much faster, not only because there’s less noise but also because I don’t have to keep switching contexts (HTML ↔ EEx) all the time. This is only possible because we know that <Input> is a component. We didn’t’ lose that information so we can generate the necessary code to initialize it.
Syntactic sugar for attributes/properties
There’s an example of this feature in the “Getting Started” guide. I’ll just write a shorter version here to save us some time.
Imagine you want to create a button component that sets CSS classes based on the following
rules:
button- always setis-loading- set if@loadingis truthy
so assuminng @loading is true, the following code should be generated:
<button class="button is-loading">
if it’s false:
<button class="button">
Using Surface we can achieve what we want by just:
<button class={{ "button", isLoading: @loading }}>
Do you see how clean that code looks without any conditional or ugly string concatenation?
Now you can argue that we could achieve the same result with EEx by creating a function. Again, of course you can. After all, that’s exactly how it’s implemented under the hood.
We could also extend this very same concept to boolean attributes like disabled or readonly and create another function so we can handle those too. Something like:
<button class=<%= css_class(["button", isLoading: @loading]) %> <%= boolean_attr(:disabled, @disabled) %>>
Well, that works for sure. But I must confess that it makes my eyes bleed. Could Surface do any better? Let’s see:
<button class={{ "button", isLoading: @loading }} disabled={{ @disabled }}>
Doesn’t it look better? I truly believe it does.
Static checking
-
Syntax checking - Since EEx doesn’t care about the structure of your code, any invalid HTML will only raise errors at runtime. When using Surface, most checks are done at compile-time.
-
Validation of properties and children - you can restrict what kind of properties and children a component accepts.
-
Other examples of static checking are available at GitHub - surface-ui/surface: A server-side rendering component library for Phoenix · GitHub
Grouping and traversing children
A parent component can classify its children in different logical groups and later traverse them and make decisions based on the information retrieved. They are not just dumb unstructured chunks of text. The concept of parent and child is not lost.
Tooling
-
Syntax highlighting - Since EEx allows you to create incomplete/invalid HTML code, it might get tricky to make syntax highlighting work properly when mixing HTML with EEx/Elixir code. Code written in Surface, on the other hand, is structured, predictable and validated at compile-time. It took me just a couple of hours to create a VS Code extension for it.
-
Auto-complete - Since information about components, properties and data (state assigns) are always available for introspection, it was trivial to add this feature to ElxirSense.
-
Documentation, Go-to-Definition and … a bunch of other related stuff around tooling.
Example of auto-complete/suggestions of assigns:
Example of auto-complete/suggestions of properties and directives:
I could keep going on and on, showing more benefits of the proposed abstraction and its positive impact on productivity and maintainability. I could also try to introduce some of the planned features like scoped styles or slots, but I’m afraid that, if I haven’t convinced you yet, keep trying is not going to make any difference.
BTW, it’s totally fine if you don’t see any value in the solution. As I mentioned before, choosing the write abstraction is hard and will depend heavily on the requirements of the project in hand. I don’t’ have any expectation that Surface or even LiveView will always be a good choice for all kinds of projects. There’s still a long way to go, lots of ideas to be validated and certainly many mistakes to be made. The one thing I believe is that keeping an open mind, trying to explore new ideas to improve existing solutions will always be beneficial to any ecosystem.
Cheers.
Malian
Hi folks!
Last week, Surface v0.3.0 has been released and it’s now available on hex.
This version improves the Surface Catalogue API which was previously released in v0.2.0 and also introduces a new surface compiler that allows autoloading colocated JS hooks.
Source code and installation instructions for the Surface Catalogue can be found at GitHub - surface-ui/surface_catalogue: An initial prototype of the Surface Catalogue · GitHub. If you want to see it in action, there’s a short video available at Marlus Saraiva on X: "Introducing the new Surface Catalogue. A first attempt to build something similar to Storybook for Surface and, hopefully, for any Phoenix/Liveview project in the near future. Prototype and installation instructions at https://t.co/rzden5u98g @phoenixframework #myelixirstatus https://t.co/xO2QHbmdqX" / X. The full changelog can also be found at surface/CHANGELOG.md at main · surface-ui/surface · GitHub.
We are happy to see that Surface is becoming more and more popular. There’s big a chance Surface is already part of many developers stacks, so it becomes essential to support these developers to have a stable version 1.0 of Surface.
Let’s not be afraid of words, our ambition is to make Surface the best possible platform for creating LV projects! To move forward on this path, we’re pleased to announce that @Malian and @miguel-s have joined our core team and will be helping us to improve Surface.
If you would like to shape the future with us, report issues or simply need help, feel free to join us on the slack channel or on github!
Happy coding!
msaraiva
Hi folks!
Surface v0.1.0-alpha.2 has just been released with lots of bug fixes and new components. Here are the highlights:
- New Markdown component.
- New context-aware form components (wrappers around phoenix helpers), including Form, Field, Label, TextInput, RadioButton and many others. (thanks to @zamith and @alexandrubagu)
- New Link (wrapper around
link/2) component. (Thanks to mathewdgardner). - Compile-time check live components without
idthat implementhandle_event/3. - Update LiveView to v0.13.3
Along with the new built-in components, there are also some new UI components available. For documentation and live examples see:
- Button (thanks to @tiagoefmoraes)
- Table
- Tabs
Source code can found at GitHub - surface-ui/surface_bulma: A set of simple Surface components based on Bulma · GitHub.
The Markdown component
The new <#Markdown> component allows users to write markdown content directly in a Surface template. The content will be validated and translated to HTML at compile-time.
Surface’s website was mostly written using this component. It’s very handy, especially if you want to mix static markdown and HTML content with runtime interactive components.
Example:
Reporting syntax errors:
If you’re using VS Code, there’s a new version of the Surface extension which adds syntax highlighting to the Markdown component’s content, as you can see in the examples above.
Have fun.
Popular in Announcing
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance












