Say No To Complexity With AlpineJS

Bookmark

Modern JavaScript is powerful, but big frameworks like Vue or React add an overwhelming amount of complexity to a developers' workflow. That’s why I wrote AlpineJS. It’s less than 10kb, requires no build process, takes almost no time to learn, yet gives you the declarative templating and reactivity you can’t live without. I can’t wait to show it to you.



Transcription


Hey there. I am Caleb Porzio. I'm a full-time open source maintainer, developer. I maintain a few packages. One of them is LiveWire. It's a full-stack framework for the Laravel framework for PHP. I also host a podcast called No Plans to Merge with my buddy Daniel. Every Friday we hang out and we talk about code and just hang out every Friday. So give that a listen if you care. And then also the other project I maintain is AlpineJS, which I'm here to talk to you about today. AlpineJS is my ode to simplicity. It's basically for a lot of projects that I work on, like full-stack framework type, like Rails, Laravel projects, and even static sites and small things, I don't always want something as big as Vue.js or React, but VanillaJS just isn't enough. It doesn't give me the reactivity and the things that I've come to love with these bigger frameworks. So I basically wrote Alpine, and that's what I'm going to show you. We're just going to live code it, and I'll show you exactly how it works. So those, well, the first thing, those slides that I just showed you, that's just an Alpine component. This is the entirety, a simple HTML page. So I'm actually going to get rid of that. And we're going to start from a totally blank slate. This is a simple HTML page loaded in a browser with a few base styles. So there's no magic happening here at all. So the first step is going to be installing AlpineJS. And to go with that simplicity mantra, there's no NPM anything. You can NPM installed if you want, but personally, I love the good old days of a simple CDN, dropping it into a page, and then getting up and running. So you can go to the Alpine repository on GitHub and just yank this CDN out, and then we'll paste it in. And now we are ready to go. Alpine is totally installed. It's 7.5 kilobytes of JavaScript minified, so it's extremely small. Okay. Let's get to it. So our first component, I'm just going to write a counter component. This is like the hello world of JavaScript frameworks. A counter meaning we just hit a button, plus and minus, and see a number update, and then we'll kind of backfill some of the knowledge. But I just want you to see what Alpine looks like, a basic Alpine component. So we're going to create a div, and we'll create a button that's a plus, a button that's a minus, and a span tag for the actual number. So here's our button, plus and minus. Okay. And to turn this into an Alpine component, everything is in the markup. So we'll add this xdata property. Xdata tells the markup, like, here's going to be the Alpine component. Then inside of it, you put a JavaScript object literal of your state for the component. So for us, it's going to be count is zero. Think of this like the data object inside of a Vue component. Okay. And the first directive we're going to see outside of xdata is xtext. So xtext, if you're familiar with Vue.js, a lot of these should be very familiar. This is V text in Vue. But xtext is basically saying, hey, whatever you put in here, we're going to put count in here. Whatever you put inside of this expression, bind to the text value, the inner text value of this element. So if we refresh the page, we should now see a zero. Okay. And there we go. We refresh, we have a zero. So now we need to wire up the plus and the minus buttons so that when we click them, we mutate that state. So here's the next directive. X on. It's similar to Vue. V on. X on. You can specify any browser event. In our case, we want to listen for a click. And then it'll run this JavaScript when you click. And just to be super clear, what's going inside of these quotations, this is just a JavaScript expression. So you can put any valid JavaScript, even stuff that needs the window or document object. So we can say alert, hey, when we unclick that button. And let me refresh the page. We hit plus, and then we get hey. Okay. And we also have access to the state inside this button. So we can say count plus plus to increment the state. And we'll also add an X on click, count minus minus to decrement that state. And if you're familiar with Vue.js and V on, you can also use the shorthand syntax that Vue makes available to you. Just at click, count plus plus and count minus minus. Okay. We refresh. And now we hit plus. And we hit minus. And that's it. That's the hello world of Alpine components. It's extremely simple. You express some data that the component is going to use as its state. And then you can listen for events and run JavaScript inside of it. And you can also bind pieces of the template to that data. And it will all react automatically. I should note that Alpine is a really lightweight framework. I meant it to be extremely robust. So it doesn't actually use a virtual DOM or anything. This uses mutation observer and simple DOM manipulations to make this stuff happen. Okay. So we just introduced you to three different directives. The X data directive. I also introduced you to X on for listening for events. And I also introduced you to X text. So Alpine is just this. Alpine is a collection of attributes that you can add to your HTML to basically compose behavior directly in your markup. So there's actually 13 directives. That's the entirety of Alpine. If you go to the docs, the entirety of Alpine is 13 directives and six magic properties that we'll get to in a little bit. We're not going to cover the entirety of Alpine today, but we'll cover the basics. And the idea with Alpine is that it's behavior mixed into your markup similar to how like a library, a CSS library like Tailwind or I'm trying to think of the other utility libraries where you basically compose your styling directly in your markup. Where before you were extracting classes to separate files. So Alpine is a similar concept. It's like Tailwind for JavaScript where you're putting your behavior directly inside your markup. Okay. So let's keep going. Let's look at our next directive is going to be X model. So X model, if you're familiar with Vue is very similar to V model. And we're going to create a different example. We'll make a text area. And this is going to be kind of a like world's simplest Twitter clone ever. The idea is that we're going to have like a little tweet box. And then as we type in, sorry, here we go. What is happening to my computer? Okay. So here's our tweet box. And then as we type into it, we want a character counter just like on Twitter. Okay. So here's our div. Let's make this a Alpine component with X data. And we're going to have our property, our data property is going to be tweet. And we'll start off with the value of say something. Now, if we say X model tweet, what we're telling Alpine is to bind the value of tweet to the value of this input element and vice versa. When the text area changes, the data will change. So this is two-way data binding that you're familiar with from old Angular and also Vue.js. So if we refresh, there we go. The text area now says say something. And we can use our X text from before to bind the value of tweet to this div. So let's say X text tweet. Okay. Refresh. And now we have say something and say something. And if we change it, it's going to update in realtime because it's completely reactive. And also to reiterate that inside of these expressions, this is plain JavaScript. So we can say tweet.toUpperCase. Refresh and now everything is going to be toUpperCased. I think that's something that takes a little bit of time to sink in that even in Vue.js or another framework like that, you're used to extracting things to separate methods or computed properties and just referencing those inside these expressions. With Alpine, you're totally free to put in any JavaScript you want inside of these expressions and you're encouraged to. All right. So instead of tweet.toUpperCase, we're going to say tweet.length to get the length of this tweet. So we start off with say something. As we change this, the length updates. So there it is. Super simple. Now, let's take it a little bit further and say that we want the tweet to be limited to 20 characters. But if you type over 20, the tweet count turns red. Okay? So let's make a class to make that tweet count red. Just going to add a style tag up at the top. And inside the style tag, a class called red that turns the color to red. Okay? And now if we apply this class to this div. So class equals red and refresh the page, we then have our red character count. But what we want to do is conditionally add and remove this red class depending on the tweet length. So to change attributes, here's our next property or our next attribute in Alpine. X bind. You can X bind any attribute in HTML. Could be class, could be style, could be disabled. Whatever you want. So you can X bind class. And then here is just another JavaScript expression. So we could, like, concatenate RE plus D and refresh. And it's going to be red. Because it's a JavaScript expression that gets evaluated. And of course, you can use the data from the component inside of these expressions. So let's say tweet.length. If tweet.length is greater than 20, then we want to make this the red class. If not, we want to make it an empty class. Okay? So we have 14 characters, and as we type, we get to 20. And bam. Now it's red. And it's totally reactive. And just like in Vue.js, in Vue, there is V bind. In Alpine, it's X bind. And in Vue, you can leave off the V bind and just use the colon. Same thing here. If you want to use the shorthand syntax, you can just use colon class. So there we go. Let's move on to another example. The next directive that I want to show you is called X init. This is basically the only lifecycle hook in Alpine. So any Alpine component, let's say we add X data and then an empty object. And then we'll say X init. Now anything inside X init is just going to get run on initialization of Alpine. So we can say alert. Hey. Okay? And now we refresh the page. And you guessed it. Hey is just going to show up. So it's very simple. It's just a nice little hook to run JavaScript when Alpine is initialized. So one of the common areas that I do things like this, like X init, notice I don't even have a stateful data object. Alpine is like a Swiss Army knife for JavaScript for me. I use it for all sorts of things. And one of the really common use cases for me is using it to control and initialize other third party libraries. So let's say that I wanted to take an input element and turn it into a date picker. So right now let's get rid of that. Hey. So we don't have to see that every time we refresh. So we have this input element. And let's say that we want this to be a date picker, but we want to pull in a library to make that happen so we don't have to build it ourselves. So here's Picaday. It's a fantastic, a refreshing JavaScript date picker. Super lightweight, no dependencies. I love it. So to install it, I already have a snippet so that I don't have to copy and paste it. So Picaday just has some JavaScript and CSS. And then to initialize it, normally this is what you would do. You would add a script tag or maybe this would be in your JavaScript bundle. And you would say new Picaday, pass in an object with a value called field and then document.querySelector, you know, that element that you want to turn into a date picker out of the DOM like this. Okay? And now if we refresh our page, we click in the input and now we have a date picker. So that's really nice. But because we want this to run on initialization, we can just stick this inside of our Alpine X and knit property. One of the benefits of this is that you don't have to, you know, normally you would have to do something like document.addEventListener DOM content loaded or on ready or something like that. And because it's running when Alpine is initialized, it's just going to run in the right order anyway. Okay. So we new Picaday and that totally works. But we have this document.querySelector right here. So let me introduce you to the next attribute, xref. So we're using document.querySelector, which is fine, but input, you know, obviously in any other app, there's multiple inputs. This isn't going to be deterministic. So maybe you would end up doing something like date picker, like use an ID. But then maybe you want to have multiple on the same page and you run into ID collisions, whatever. I loved the concept of refs from Vue, so I brought it over to Alpine. In Vue, you could do something like this. Ref input and then from any JavaScript expression, you could say refs.input and access that element without doing the whole query selector thing. So in Alpine, it's xref input and then refs.input. And now let's refresh and make sure that worked. Okay. There we go. So I use this all the time. Like, sometimes I'll make an Alpine component just to use refs so that I can pull things out of the DOM more predictably, programmatically, whatever you want to call it. All right. So let's clean this component up a little bit. If you're not actually using the data property, you can get rid of the value and just use xdata to shorten it up a bit. And I'll say that we don't even need this div. We can take this entire expression and we can pop it in line right here. Okay. And that cleans it up. Now it's just a one-liner. And finally, we can also get rid of this xref completely because there's another magic property in Alpine called $l. And $l is going to give you the current element of the Alpine component. So here we go. Let's make sure this works. Refresh. Hit. And there we have our nice little date picker in one line of code. It's directly in line. The behavior is entirely co-located with the markup that it's concerned with. And we added literally this much JavaScript to initialize a date picker. So I use Alpine for all sorts of little JavaScript tasks like third-party libraries like this date picker. Awesome. So the final directive that I want to show you is called xshow. So naturally a good example for xshow is a dropdown. And honestly, I wrote Alpine because dropdowns and modals are actually surprisingly hard with vanilla JavaScript. I got so used to using Vue.js, I had forgotten what it's like. And then you go to create something simple and you realize that it's just harder than you want it to be and then you go back to something like Vue. So Alpine initially started that I want a framework that I can easily make dropdowns and modals and toggles and things like that. And then it turned into this, of course. So let's create our little dropdown. This is going to be our show dropdown button. And then this is going to be our dropdown contents. Okay. Let's take a look at those on the page. Show dropdown and dropdown contents. So what we want to happen is we want dropdown contents hidden by default. So let's first initialize this component. So xdata, and this is going to be open, is false. Okay. So now dropdown contents, we can bind to that Boolean with xshow. We'll say xshow open. And remember, this is just another JavaScript expression. You could put, you could literally hard code true in here. You could do whatever you want. Call a method, doesn't matter. So xshow open. So now if we refresh, this should actually go away. And it does. So now we want to listen for a click on show dropdown, set open to true, and it should just react. So let's do that. So we'll listen for a click, and then we'll say open equals true. Okay. Refresh. And now if I hit show dropdown, it opens. Perfect. Next question is, how do we hide the dropdown? So we could make this a toggle so that when we click this again, it hides it. But in a lot of cases like this with dropdowns and modals, you want to be able to click away from the thing that's shown as a way of hiding it. And so to do that is actually kind of annoying. So I added a little helper inside of Alpine. If we listen for a click on the dropdown contents itself, we can add a modifier called away. So we can say add click.away. And now we're saying when we click away from this element, run this JavaScript, and we'll set open equal to false. So check it out. Refresh. We show dropdown. Now we click away, and dropdown is now hidden. Perfect. So you get this complex event delegation behavior. If you've ever tried to implement this yourself, it's a little bit complex, and you just get it out of the box. So there we go. Let's do another one. Let's say we can listen for a key down event. I love listening for key down events in JavaScript, or sorry, in Alpine and Vue because they make it so easy. You can say at key down dot escape. And now we're listening for the escape key to hide that modal. Or sorry, the dropdown. It could be modal, whatever. We show it. Now I want to hit escape and hide it. So if I hit escape, notice that, well, nothing happens, and then show dropdown gets kind of this focus outline. So that's because we're listening for the escape key on this element. And what we really want to do is listen for the escape key at the whole document level so that when we hit escape anywhere on the page, it's going to hide it. So we can do that with another really handy modifier called window. You can add dot window to any event listener, and it will then listen at the window level. So let's refresh. We show dropdown. We hit escape, and now we hit it. So let's keep going with these convenience modifiers. For X show, I also added another one called transition. So we can say X show dot transition. And now when we show dropdown, we get a nice transition in and transition out. And we're on Zoom, so it's possible you didn't even see that. You can configure it with more modifiers. So let's say we'll make this 2,000 milliseconds long. So it's going to be a full two-second transition. So we'll hit X show dropdown, and now it transitions in with opacity and scale for two seconds. And then it'll transition out. And I really sweated the details over this transition system. It's pretty robust, and I used all the defaults of Material Design's motion specifications. So it's really well thought out. You can also customize it. You can say only transition in, only transition the opacity, all sorts of stuff like that. And if you're from Vue and you're used to V transition and using tailwind utility classes, there's also X transition, which works the exact same way and gives you the full power. OK, so that's X show dropdown contents. The final step here is, let's say you're repeating things around your expressions, and maybe you want to extract some of this logic into a separate JavaScript file. Maybe the markup mixed with behavior is a little bit too much for you, although I'd love a good expression right in line. Let's say that that's the case. You can always extract this JavaScript into a separate file or a script tag by exposing a function. So let's expose a function called dropdown that returns a piece of data. And that piece of data is open false. OK, and so here we can actually run this function dropdown. And that is going to give that component the data. And additionally, you can create methods. So we can say show and then this.open equals true. And now open equals true could then just reference the show method. And if we refresh, we hit show dropdown. And there we go. So that is Alpine. We've only gone through eight of the directives, but there's also X if, there's X for, there's lots of other power for you. It is my ode to simplicity and minimalism. It's my JavaScript Swiss Army knife. It's seven and a half kilobytes of JavaScript and as simple as a CDN link. So if you're interested, go over to the Alpine JS repository and dig around. All the documentation's right in there. There's a bunch of helpful examples. And that's it. Hopefully you really like it. And I should say that I work on this stuff full time. I work on Liveware and Alpine full time. And I basically rely entirely on GitHub sponsors for my income. So if you use Alpine and you're really into it, head over to my sponsor page and give me a sponsorship. It means the world to me. It's what allows me to do these things. And also as another form of generating revenue so I can build this stuff all day is I'm going to be launching a course on VS code. So I do a lot of screencasting and a lot of people say like, you know, I love your setup, your key bindings, all that stuff. How do you set it up? What extensions do you use? And turns out I'm pretty opinionated about my VS code setup. So I started an email list. And if you care, go to make VS code awesome.com. And you can sign up for this email list. I basically start off like, here's VS code out of the box. It's kind of an eyesore, very distracting. And here's how we get to something more minimal and clean. And all the and like I go deep on a lot of VS code workflow tips. So if you give me your email, I'll send you stuff right away. You'll get a bunch of tips right away. And then eventually I'll put it into a course. So if you're interested in that, check that out. Again, this has been AlpineJS. Thank you for watching. I hope you use it and happy coding. Thank you. Excellent stuff, Caleb. Amazing. You can't see me but the listeners can, the viewers can. I've got a microfiber thing on my head. Oh, I can see you. Yeah, because my brain literally exploded. All the goodness there. Thank you. I had so much brain that even though it exploded, I can still continue. Good. Good. Yeah. Caleb, please join me on the stage of interrogation where I shall mercilessly interrogate your questions until you weep or beg for mercy. Perfect. So viewers, folks, that was Caleb Portio because I have rechristened him. So every time you call him by his new name, Portio, I'm on 10%. I'm going to be a millionaire. Caleb, lots of questions from the community for you. I'm trying to make sure I don't ask all the performance-related ones so you don't end up repeating yourself too much. So you mentioned smaller file sizes as Alpine's main selling point. How does it compare with React and Vue performance-wise? I don't think it's its main selling point. I will say that. I think in general, the whole file size thing is a bit of a distraction and I fall prey to it totally because I idolize that too. But in reality, the most important thing to me is developer experience and productivity. What is it like to use the tool? Does it make you more productive? Does it feel good? Does it feel simple? Is it aesthetically pleasing? Those are the things I care about. Top priority. That's its selling point. The file size is a fun little side effect that I get to say, hey, it's one single character file size. I will say that. That's the disclaimer. So how does it compare performance-wise? So I started digging into this. I'm working on the next major version of Alpine and performance is going to be the main goal in the next major version. The goal of this version was to get the API where I wanted it. And I will say that it is pretty darn fast because what's under the hood of Alpine, there's no virtual DOM compiling and patching or anything like that. It's all native browser APIs. I don't know how to create a virtual DOM framework. So I started the most minimal that I could. It's like, well, so an X text directive, that would take the element and set its dot inner text to that value. So I started there and then built up. I will say that the prototype version for V3 is faster than Vue 2, which I imagine it's who knows what Vue 3. And I don't mean to compare, you know, like at this level of performance comparison, I made like a X4, which is the same as V4 of like, oh, I don't know, however many thousands of rows, like 30 or 40,000. Then you start to really notice the difference. And yeah, because I have the advantage of not dealing with a template compiler. So that's nice. It allows me to kind of only update the pieces of the DOM that need to update when a piece of data changes. So the next version of Alpine, I'm super excited to get really nerdy and see how far I can push the performance gains. And I think it's pretty far. Yeah. So that would be my answer. Pretty vague, but yeah. No, no, that's good. So file size aside, you're not going to I'm not going to give my users a performance hit if I choose to use Alpine for its better developer experience. Yeah, no. And the majority of things people are doing is toggling stuff. Like you're making a dropdown and a modal and a little slider. And this is great for that. And that's sort of the Alpine ethos. Alpine isn't a full front end. It's not an SPA tool. You're not going to build your entire front end system with Alpine. You're going to do it with, you know, whatever, a static site generator or a Rails app or Laravel app or Django or Express or something that produces HTML from the back end. And in that case, you're sprinkling in behavior. It's JavaScript sprinkles. So performance is one of those things that I love to geek out on. But in reality, I could get away with murder and people wouldn't even really know unless they were doing things that maybe Alpine's not even good for, like subbing out your entire front end template for Alpine. So I don't know if you heard in my intro, I was extolling the virtues of jQuery. But would it be fair to say this is kind of like that? You know, I can use PHP or whatever to produce my HTML and then sprinkle in as much or as little jQuery as I want to do the grunt work of toggling things and mucking about with individual DOM elements. Exactly. Yes. And that's where it was born out of, is me being a Laravel developer, kind of going back to those ways. So if you're in a back end context, MVC type framework like Rails or Laravel or something like that, Alpine is just that. And it's a lot of so it kind of sits in between jQuery and something like Vue, where it gives you some of the fancy reactivity of Vue, but it gives you the simplicity of jQuery, CDN. There's no need to compile. There's no webpack. There's nothing like that. You don't have to do that stuff. And it's not even recommended that you do it because you're sprinkling in behavior here and there. So, yeah. Interesting you talked about Laravel because here in my home city of Birmingham in the UK, there's a really nice web shop called Jump24. I'm not trying to push them. It's just I drink a beer with their chief execs at times. And when I tweeted about this SmashingMag article, he said, oh, we love that framework. It works so well with Laravel. I think not realizing that you were connected to the Laravel project. Yeah. But enough about my personal beating on it. Let's move on to another question. You mentioned there's no VDOM, virtual DOM. So Vlad Stavishki asks, how does it work for rendering if you mutate a few DOM elements at the same time? Does Alpine re-render them all at the same time or successively? Yeah. So there's a couple different answers to this. I guess what do you mean by mutate two different elements at the same time? Because I think in general, the Alpine paradigm is you're mutating the data and then the DOM updates with the data. Does that make sense? So like in our counter example, you would change count and then the DOM would update. So right now, I crawl the DOM. For that initial render, I'm crawling the DOM and I'm setting things as I crawl through it. The next version of Alpine, I'm pretty pumped about because that first initial render, as I crawl, I perform actions. So when I get to, let's say, a div with X text count, whatever, when I get to that, I wrap that expression where I actually execute dot inner text where I set it in a observable function. So now, any time data that was used to drive out that dot inner text changes, only that will execute. So only those pieces of JavaScript will execute. There's no virtual DOM rendering or anything like that. And then I use mutation observer to kind of reinitialize things that are added after the fact or remove things that are removed. It's robust. I built it to be really robust. It uses mutation observer. A lot of things are computed at runtime. So you can use it with something else. You can manipulate things, and it's not all going to blow up in your face. That's kind of a goal for me. I want it to be something that's a little more resilient than an entire virtual DOM-driven thing that owns the DOM. I want it to be something that manipulates the DOM as it needs. Gotcha. Interesting you say when you crawl, you do actions because my lady friends tell me that when I'm drunk, I'm much the same actually. Is that right? Yeah, a little bit of a PR tip. So you crawl over the DOM, so you sprint over the DOM. Give the idea that you're not slow, like straight. Yeah, right. Good call. Good marketing tip. Yeah. No worries. Again, 10% for that. Of course. Another interesting question, not about deep tech or performance. Naomi Meyer asks, Naomi Meyer says, great talk, Caleb, with an emoji that looks like a carrot. But I suspect it might be some kind of party thing. I've got terrible eyesight. And then she says, does Alpine support internationalization and localization functionality or components? Yeah, I guess I'll say no. I'll say that, so like I'm using Alpine with Laravel most of the time. And Laravel has really good localization support. So that's kind of taken care of with Laravel. Does that answer the question? Yeah. Yes. I think it does. So what you're saying is, no, it doesn't, because that's not its goal. It's the thing that produces the HTML is the actual goal. Is that a reasonable paraphrase? Yeah, so like the back end is the thing producing each. Alpine is the thing composing behavior within that HTML. So let's say if I'm in Laravel and I want to localize a string, I would do that on the back end. And then I could toggle that string with Alpine on the front end, but the actual localization would have happened further upstream. And we have many more questions, but the voice of God in my head is telling me we're out of time, Caleb. Thank you so much for answering the questions. And please, if you could go to the Zoom room, paid ticket holders, you get a chance to interrogate Caleb. I hope I didn't make you cry with my ruthless questions, Caleb. I'm holding it back. Thank you so much. Good man. Good man. Thank you so much.
33 min
18 Jun, 2021

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Workshops on related topic