2024-10-16 | PyCharm, a JetBrains IDE | Django and htmx Tutorial: Easier Web Development

htmx与Django:回归Web本质,轻松实现懒加载、搜索与无限滚动

媒体详情

上传日期
2025-06-27 10:01
来源
https://www.youtube.com/watch?v=i1eMxu_PR5Y
处理状态
已完成
转录状态
已完成
Latest LLM Model
gemini-2.5-pro

转录

下载为TXT
speaker 1: We've normalized the wrong way, and this is a little closer to the right way.
speaker 2: It's what html would have been if they would have kept developing it, right?
speaker 1: So whatever you're using on the back end, this makes your front end life a lot easier. It makes a big difference in things like testing. I don't need selenium to do have these tests anymore. It does change the balance of things. You know, being able to get rid of react or angular or those kinds of things have meant that they've actually allowed their customers .
speaker 2: to do larger, more complex things. Hx has a lot to offer Jango developers. It's the developer experience going further with the skills you already know, which is ding in my book. You know.
speaker 1: I first sort of stumbled across it. I was just like, Oh, why couldn't it always be this easy?
speaker 2: Hi everybody. I' M Paul Everett, developer advocate at jet brains. We have a very special guest who's been with us before Christopher Trudeau was with us to do Jango ninja. Is that right, Christopher? Yeah.
speaker 1: I believe that's what we did.
speaker 2: Yeah real quick for everybody. This is a little bit of a different flavor. It's not a live shing. Christopher has prre ecded the material. We'll jump into it in just a second, and then we'll do little conversations in between each section in there where we talk about this. We're talking about Jango and htmx today. And let's jump right into the segment where chopher gives us a little bit of an introduction.
speaker 1: So nothing's ever free, right? Here's an embedded ad, thinly veiled as me, telling you who I am. My day job is a fractional cto, which means I help organizations with both architectural stuff and technical process stuff. Generally, I try to use fancier words than stuff when I'm in front of a client, but that's the way it goes. In the middle there is where you might recognize my voice from. I am the co host of the real Python podcast. Christopher Bailey tolerates me every couple of weeks, and we talk about the latest stuff going on in the Python world. There's that stuff again. And of course, if you've been listening to the podcast, you'll know I've written a book. It's called Jango in action. And in fact, the htmx stuff that you've come here for as an entire chapter on it, I'll start with a quick overview and then dive into some code so you can see how htmax works. If you've happened along this talk accidentally and don't know what htmx is, it's a JavaScript library that helps you create dynamic web pages. Before I discovered htmx, I did a lot of my dynamic web coding in view js. You can't do everything in htmx that you can do in view or react or the others, but you can get a fair way along without writing any JavaScript at all, which, as a Python programmer, I like. It does this by taking the ideas that are at the base of html and expanding them in html. When you click on a link on your page, the browser goes and fetches a new page and replaces the one that you're on. With htmx, you can apply this same kind of logic to parts of a page using pretty much any html tag, although nine times out of ten it's going to be a div. The browser is still fetching new content, but now it's fetching snippets of pages and replacing just parts of the page with the fetched snippet. Typically with other dynamic libraries, you've got all the html parts downloaded already, and then when you click, the event causes some data to be fetched from the server, then pops up a dialog box or makes the change or whatever you want here. Instead, those new parts are dynamically fetched. This means a couple things. One, your page is less complex. And two, you can use the mechanisms built into tools like Jango to manage the snippets. Jango's been around for a while, over 20 years in fact, and although it's been growing and changing, it's still structured in a very web 1.0 kind of fashion. Its core concept is a url maps to a view, which then outputs a render template. The htmx approach allows you to use this core concept to replace parts of the page instead. This gives you pretty dynamic pages without having to do very much lifting. Htmx isn't the right tool if you're building a web based, say, spreadsheet, but the vast majority of dynamic interactions in a business application can be done with a lot less code. In fact, the htmx website even has some case studies where react based sites converted to htmx. One case saw two thirds drop in the amount of code. It removed 95% of their JavaScript dependencies and reduced page load times by 50%. An interesting side effect of all this was that their site could then handle larger data sets because the page was now lighter weight. So this is all goodness and gravy. On the jangle side, you pretty much treat snippets for htmx like any other page, which means you're using the same tech for Full Page rendering as you are for the parts. The htmx library is also careful about informing you when an htmx event is what triggered your view. This means you can have a single view for a page that outputs either the full thing or just the snippet. This is kind of similar to how you would write a view for a form that has both get and post activities inside of it. Okay, that's enough background. You came here to see some code. I've got a basic Jango project that I'm going to adapt and add some H2 mx features to. The features I'll be covering today are lazy loden, search as you type, and infinite scroll. I've been playing with library thing lately, so when I needed an idea for a demo, I basically just ripped it off. If you're not familiar, library thing is a site where you can catalog your book collection. My version is much simpler though, it has a book model class to store a book info in the database, and then I use the Jango edmin so I can muck around with content. If I want to start off, the only view is a home page that lists all the books and shows their author info. And in a second, I'll open up the editor and give you a quick tour of the code so you're up to speed before I start htmexfying it. By the way, all the code I'll be showing you here is available up on GitHub. In fact, I've split it off into steps so you can see each of the stages as I go along before the next feature is added. Alright, I've got my project on the left side here. Nothing unusual in the structure, just your typical Jango project. This code is from the file called step one in the folder on GitHub. The project has one Jango app, which I've called books, and inside it you get the typical model admin and view files. Let me start by opening the model file. I'm keeping it simple. There's only one model in this project for a real project, youwant, to have foreign keys to an author, etc. But the purpose here is to just show off htmx. My model has four fields, a title, the author's first and last name, and then tracking the library thing ID. I've grabbed images of the book covers, and I'm storing those locally using the library thing ID, the file name, again, not the way you do it in the real world. I'm serving them up as static content that I didn't want. Na complicate the example with upload handling code down here, I've got a couple helper methods, the typical thunderstring piece, as well as a short version of the title. I have this so that inside of the Jango admin, the column stays short. By the way, if you haven't seen the truncator class here, it's a hidden gem in Jango. This is the class that the various truncate filters use. The class itself isn't documented, but I don't think it's going anywhere because the four different filters are based on top of it and down at the bottom. Here I have a property that maps to the file name for the book cover all. That's the model. Let's go take a look at my only view. I'm wiring this view up as the homepage. It doesn't get much simpler than this. I create all the books, and then I render them as the home page. So let's go look at the home page. Open up templates. I've got a base template that I'm not going bother showing you. It contains the structures of all the pages for the site. It uses a bootstrap five layout, nothing fancy, just easier to look at than the vanilla html. And then inside that base is a block called content, which is the placeholder for the actual content on the page, which is what I am extending here. The home page renders all the books in the database, showing the book image in one column and the title and author information in the other. Okay, that's the key bits. I've registered the url for the homepage in the projects url file. And to get static library to work, I needed a bit of configuration. But for the most part, the rest is just what gets created automatically for you when you create a new project. In case you haven't coded Jango in Pyron before, there's this great feature here where you can define a environment configuration, essentially determining what gets run when you hit the play button. I've tied this to the Django development server. If you're using the community edition, you have to do this by hand. But if you've got the proo seup, there's a wizard that creates this for you. Since I've defined this environment, all I have to do is hit the play button and it starts the jangle server for me. Give me a second and I'll switch to my browser, lohost. And there it is in all its simple glory, lots of book images, titles and authors. Okay, that's a good .
speaker 2: framing of htmx getting us kind of oriented with the project that we're about to do in Jango. Chris, or let's talk about htmx. I like to phrase it as putting the web back in the web. What do you think? Yeah, it's you know, I first sort of stumbled across it.
speaker 1: I was just like, why couldn't it always be this easy? And there is actually this sort of, there's this little subculture that's saying this should be part of the html spec, that it that's the way, that's the future. I don't know whether itever go there, but the htmx community is, if I say rabid, I mean it in a nice way. Like there's some very, very hardcore people who think this is the answer. And I find, particularly with business applications, I very, very seldom have to switch to something heavier. It almost always solves my problem and it just makes my life so much easier.
speaker 2: And one of the things that you'll show on this is you don't have to switch to something else because you already have something else .
speaker 1: called Jango which knows how to generate html, right? Exactly. It if fits very, very nicely with the sort of web 1.0 architecture that Jango still has underneath, although it's got other features for that. And and you know it's also like it's not you know we're here to talk about it with Jango, but it doesn't care. It's a JavaScript library. Whatever front end you're using, it essentially just brings you new features on top of your html and makes it easier to call web based view like things and make it go. So whatever you're using on the back end, this makes your front end life a lot easier.
speaker 2: And you framed it correctly. That's why Carson, the creator of htmx, puts it, it's what html would have been if they would have kept developing it, right? Yeah. All right. We're going to take a look at your first segment on lazy loading.
speaker 1: Alright, so you've seen the project. It's time to start adding some htmx features. The first one I'll show you is fairly simple, just a half dozen lines of code and allows you to do some lazy loading. You typically lazy load if an asset is large or if you're dynamically generating it. Say you need to dynamically generate an image for a dashboard. You don't want the whole page to wait while it is getting ready. So you lazy load the image without htmx, this would be a few lines of JavaScript, and then youhave to insert a new tag in the dom. With htmx, you don't have to write any JavaScript at all. Htmx does its job by making new attributes available to the browser, which you apply to your html tags. One of the attributes I use most is hx get. This specifies a url. To get content from all the htmx attributes, begin with the hx prefix. And not only that, but htmx implements the whole rest interface. So there's an hx get, hx post, hx put, etc. For all the http methods. So how does the page know when to call hxget? Well, you need some sort of event. Hx trigger says when to fire, and hx get says where to get the content when the event fires. You only need three lines of html to implement lazy loading. With htmx, you create a div, which is the target for the replacement content that gets fetched from hxget. You can put something inside of the div if you like, and that will get shown until the trigger happens. Here, I've used some text, but typically what you're gonna to do here is put in a spinner. The hx trigger specifies the event, which in this case is the page loading. You can also use other events. For example, infinite scroll uses the revealed event, which says to fire the event when the tag becomes visible. I'll talk more about that one later. What gets done when the load event fires is specified by the hx get attribute, which in this case goes and loads the url. And that's it. Obviously you need some code on the server for rendering this page and rendering the snippet with the content, but from an htmx point of view, that's all you need. Loveliness ball, let's go implement this in book thing. I'm gonna to start out by creating a template. So let me just copy the home page to use that one again. I don't need the rest of this content block. And what goes here is pretty much what .
speaker 3: I showed you on the slide.
speaker 1: Okay, now for the views. I need two views for this code, one to display the page, that's what I've done here, and then the other is to return the lazy loaded code. I've put a little time delay inside of this view so that you can actually see the effect of the lazy load. So it takes a little while to have it happen. This views for the lazy loaded content. Remember, what's going to happen is the contents of the dib on the html will get replaced. So you don't actually want the image itself, but you want html that shows an image. I like to keep all my htmx snippets together, so I'm gonna to create a directory for them. Some people call this partials. I'm using snippets. Doesn't really matter what you use here. Must remember to import time. And now I'll go register .
speaker 3: these views.
speaker 1: Okay, there's the views. And then the last thing I need to do is create the snippet. Again, this file is the html that htmx will stick inside the div. And as I want to show an image, that means an image tag. In the real world, this would likely be something in an uploads directory that you've created on the fly. But I'm just hard coding it to something I've got on hand already. And the last thing I have to do is actually install htmx. So let me open up the base file. I've got it in my clipboard here, so I'll just paste it in. And essentially what I'm doing is installing the script here. It's available on cdns, so you don't have to download it separately and track it yourself. And that's pretty much it. Let's save this file and give it a go. Going to the url, there's my delay and there's the result. Speaking of lazy, I probably should have built an half bar anyway. The lazily loaded dashboard like thing is here. I love this image. I stole it from library thing. It shows how tall the stack of books in your collection would be if you piled them up. I'm disappointed I haven't surpassed the statue of liberty yet. It's nice to have goals.
speaker 2: All right, I like how you led with lazy loading and the way that you intred it, you showed it is I've got something that's already working. It's already sending the html back and I'm just gonna take that as my starting point. It's kind of move it over there, right? Yeah.
speaker 1: in fact, it's sort of a theme in htmx. Like so much of this is, Oh, take something that you could do that was just a little slower or it might cause a new page to refresh or something along those lines and a few lines of code, and all of a sudden it looks a lot smoother and a lot more modern. It just sort of integrates very, very nicely with those pieces that are out there. And I like starting with lazy loading from a teaching perspective, because you don't have to get your, you don't have to wrap your head around too many things right? It's just Oh, okay, I put in the url and then there's there's a little delay and the browser hits it and it comes up and gets it in the back end background. So Yeah, it's a it's a good place to start if you're learning it.
speaker 2: And for the record, what you did and say was take that thing that was already working and make it faster by installing an entirely new programming language and tool chain and framework and go learn all of that and hope six months from now .
speaker 1: that it still works. Yeah, it's it's one of those things I've always found a little frustrating on the net. Like you'll see somebody go, Oh, how do I do x and theybe? Like, Oh, well, you shouldn't you should use that framework instead. And it's like, great. I didn't ask for that. It's it's not the answer, right? And and even if that frameworks better, it's not that it isn't. It's just that Yeah I'm trying to solve a particular problem right now. And yet hdmx very much is sort of that drop in replacement kind of thing. It works very, very smoothly. In fact, that that described .
speaker 2: the drop in real quick. I apologize for your argument. The way you describe the drop in reminds me of something that we used to call progressive enhancement, right?
speaker 1: Yeah and in fact, it's why it's why I'm a fan of view. Before I was doing htmx, I tended to use view, and it was because I could have degrees of how much of it I was using. Whereas I find react and angular, it's an all or nothing thing. You're there or you're not there. And so if you've already got a framework like Jango happening in the back end, it's nicer to be able to put something in where you can just sort of turn the dial a little bit. And hdmx is very, very much that way.
speaker 2: And one little technical question. At one point, I think it's as you're showing the image in the bar chart, you have a path and you have it, I say path, url path, you have it relative to where it started from in home. And then you were factoring to snippet, and then it's gonna to go over to hcmx, which is gonna to issue a request. The relative paths all still work.
speaker 1: So sorry, are you asking about the relative path of the html? Are you asking about the weird relative path of the image that's being lazy loaded?
speaker 2: The lazy loaded image? Yeah. Okay. So let me .
speaker 1: answer both just in case anybody else has got those questions. So for the templates, it's just because you're just running a view. You're just calling the templates the same way you would call any other templates. That image is a bit of a hack. So I didn't actually dynamically create anything. I'm just loading it out of static. So it's it's acting as if that were a static file. If you were actually building a dashboard where you were using lasleloading, then the result of that would probably be you're calling a view that outputs some sort of image, usually to your uploads directory or something along those lines. You're putting a file that's dynamically generated somewhere else and then it would get loaded. Alternatively, you could also have that snippet call a third view. And that third view could is output, could be the image itself, if you wanted to go down that path depending on what you were trying to do. But Yeah, I was for simpliity's sake, I was skipping past the whole dynamic image thing and faking it out with with the with the snippet.
speaker 2: And as it turned out, everything you just described in kind of the next level of Zen sounds totally normal to a Jango person.
speaker 1: Yeah except for the fact that htx is going off and doing this on an event, everything else would just be, however you were building a dashboard. So if you were doing this, you know if you were doing something like Jango channels and doing this asynchronously, then youuse the same mechanisms. If you were just going to let the page load slowly and go to sort of stutter as each image gets filled in, it would be the exact same mechanism. So the only thing that's different here is that the event is causing that load instead of the .
speaker 2: initial page. Yeah. You didn't say go read the react router .
speaker 1: document exactly. Yes.
speaker 2: Okay. Let's take a look at the next segment where you're going to cover something that's a really common pattern in Jango sites and all websites, which is search.
speaker 1: The rest of this session incrementally builds a search as you type interface with infinite scroll on the results. But to do that, first you've got to have a back end that can do a search query. Once you've got a Jango view that can do search wiring in the searches you type isn't too bad. In fact, this is kind of a thing with hgmx. You can take your web 1.0 code, tweak it a little bit, and you get web 2.0. But first I need the 1.0 code. My search view is going to use a query parameter for the search text, ittake one or more words, and then do a combined query for the first name, last name, entitle the book to be the beginning of any of the words in the query. To top it all off, the results will be paginated, one, because it's good practice, and two, because I'll need that for infinite scroll when I get there eventually. All right, let's head back to the editor and get this going. All I'll open views high and I need to add a new view. This one's going to be a little longer than our almost one liners before I'm going to start out by getting the query parameter that contains the search text. Normally, I'd use a parameter here that was smaller and valike q, but once you tie it into the htmx, this value ends up in a whole bunch of places. So I'm sticking with something that's a little more understandable, even if it means I have to type some more stuff because I'm using a query parameter, the browser is going to encode it, which means I need to decode it. And in order for that, I have to have the url lib imported, so I'll go up to the top. And back down at the bottom .
speaker 3: and my squigggly .
speaker 1: red line went away because I've imported it. Now in case there are no search terms, start out with an empty result. And now I want to actually process the terms. I'm splitting the search string up into parts based on spaces, and I'm going to match each part against a starts with dth clause in the database, normally a Django filter call and ds its query terms together. In this case, I want or and if you want that, you have to do something a little different. The Django q object allows you to dynamically build queries, and each q object works like the parameters to the filter call in the more typical way of doing stuff. So here I'm starting with a query with the first word in parts against the first name or against the last name or against the title. Let me just pop up to the top and create the import. And back down to the bottom. One of the advantages of A Q object is that you can use operators to chain them together, giving me the or condition. If I just use filter on its own, I would have ended these conditions together, which isn't what I want. Now, my query at this point matches the part against either the first name or the last name, but it's only the first word. So now I need to loop to get the remaining parts in the query. If there are some. Same kind of clause, but I want to or equal it so it gets chained onto anything that was built already. Now that I've built the actual query.
speaker 3: it's time to call it.
speaker 1: And you just PaaS in the queue instead. And now the filter happens on the ord clauses. Now that I've got my books, I want to paginate the results. Thankfully, Jango has a class that does that for you. It's called paginator. Let me go up to the top and import it. I'm sure my lack of alphabetizing here is driving some of you crazy black would fix it for me. Okay, let's paginate this. To use the paginator, you instantiate it with two arguments. The first is the query set you want to have paginated, and the second is the number of items on a page. You probably want something bigger than two, but this helps me show it off. Good site. Would also have query parameters so users can override the number. Do you remember when I earlier mentioned that I was lazy? Yeah, don't expect that to change in this demo. Once you've got the paginator object, you call its page method to get a page from the paginated values. So I grab a query parameter called page, defaulting to one if it isn't given, and then I PaaS that into the paginator to get the current page. All that's left is to stick this in a context so I can render it. The object list is the paginated values, so if we had ten books in our query, the paginator would grab the first two or the second two or the third two, depending on what the page number is, and object liwould contain just those values. As next returns a boolean if there .
speaker 3: are more pages.
speaker 1: For real sight, you should probably wrap some of this code a little more with some error checking. You want to make sure that the user doesn't do something stupid like a page minus five. But Yeah, remember, lazy. Alright, with that in place, let's build search dot html. The search page should have an input box where users can type in their query. For right now, I'm not going to wire it up to the view. I just need to put it in the page so you can see it, and then I'll add htmx stuff to it later. Okay, now I need a results section which isn't that dissimilar from the home page. Just loop through the books and print the results. Handily, I've got a copy of that off screen, so let me just paste it in here. And last, seeing as this is paginated, I want to check if there's another page and if so give a link to it. All right, that's the html.
speaker 3: Now let me wire the view up.
speaker 1: And let's try this out. There's the page. Now remember, I didn't wire the input box to the view, so I have to type the query in by hand. All right, there's with one. Oh.
speaker 3: something's not quite .
speaker 1: right there, is it? I'm a double checked code and I kind of mucked that up. This is what happens when you copy and paste code. I don't actually want the first word over and over again. I want the remaining parts. Let's try that. Let me reload that. And now go on to the second page and there's some bebooks to go with it. There we go, fixed and working.
speaker 2: Okay, that search in Jango again from a basis of what you already know, right? Christopher? Jango and search and hdl templates and Jango templating and stuff. What was interesting to me in looking at this is as you're implementing this stuff, you're using all of the server side machinery. You already know about picking apart url's, and that's not a fun place to be on the JavaScript side. And when you're constructing all of that stuff, you have access to the state that's already there on the server. Yeah and this keeps coming back to .
speaker 1: that theme of getting to 2.0 without really having to rethink your 1.0, right? So the mechanisms that a Jango programmer would use in a standard sort of website where you've got you know paginated results coming back, we're just going to use those same mechanisms and take advantage of the event system out of htmx to make it happen a little more smoothly. So Yeah, the you when I put this together, I was I was always like, Oh, this is a little chunky, but the reason it's a little chunky is because of what's coming. I could have done this in like a few steps and added pagination afterwards, or a couple of you know or done something slightly less complicated with the q query. But you know this is a little bit of the vegetables, right? So now you've consumed your broccoli, the htmx on top of it really is just okay. Now we can wire all these things and there's almost no html code left. I think I write like four more lines. The rest of the rest of the segment, everything else becomes htmx. Another thing that jumped out to me.
speaker 2: because I do a lot of Python developer avgate and web developer ravagates. I do a lot of JavaScript frameworks and stuff like that. And over there you get a lot of coding assistance if you're in react or view or whatever, and TypeScript and tsx and jsx. But all of that stuff started on the server youhave to mirror all of that over into the client side and have a json schema or tons of machinery would start to appear to try and have all that structure that was already there on the server. Yeah a big part .
speaker 1: of this because we're sort of trying to do a lightweight event mechanism on the front end. You're pushing a lot of the code into the back end. And I think I mentioned one of those essays that's on htmx dot org. They had massive drops in the amount of JavaScript that they had to write. Oh Yeah. And I'm seeing this over and over and over again that the essay I was talking about, I think it's four or five years old now, I just saw posting a few days ago, it was basically someone had written the exact same essay, but at their place, right? It was like, Oh, we got rid of this, and we got rid of that and all this cleaned up. And like, if you're already doing the dual environment of you know Python and Jango on the back end and JavaScript on the front end, you've probably mostly hired Python people and they all kind of hold their nose and do a little bit of JavaScript. That's my bias showing. But you know you sort of you sort of do what you have to on the front end, but because this sort of moves a bunch of things into the back end, it can make a, it makes a big difference in things like testing. It makes a difference in how you think about these pieces because now I no longer have, I don't need selenium to do half these tests anymore because I'm pushing more of the code into the back end, right? So it does change the balance of things. It also tends to be a lot easier to check on things like performance as well, because you can beat the heck out of your code on the server side, fine tune it, and now you don't have to worry about what's going on in a person's browser or on their phone or all the rest of those things. It essentially moves the front end back to being what it is, which is a display mechanism.
speaker 2: You for the record, you said testing before I did, which might be a first time ever in a recording that I've been a part of. And it's an important point, both testing and performance and the other things you mentioned, it's what you already know. So you move this stuff into a partial, so then it's a different implementation, so you got to go test it. But it's the same testing stuff you already have in Jango, right? Yeah, yes.
speaker 1: very much so. And because Jango, you know it fakes out the views when it tests like it's not actually you know you can write tests for selenium and things like that, but it's actually calling the view functions directly, pretending it's a browser. So the more you can move into those views, the more you can use Jango's specific test framework without other pieces to get it .
speaker 2: all to to work together. Okay, let's go on in the next segment. It's a step a little bit beyond this. It still searbut it search as you type.
speaker 1: Now that the longer Cody part is out of the way, it's time to wire it up with htmx. For such as your type, you're still going to use the hx get attribute, but this time you're going to use it on an input field. What changes is what causes the hx get to be invvoked. Now instead of it being based on the page loading, you want it based on key presses. Once the new content is fetched, you have htmx replaced the results of a placeholder div using a css query selector to specify where, and so that you're not pounding the heck out of your server. You don't want every keypress to invoke a brand new search. Instead, you want to devounce. This means inserting a bit of a delay so that if the user 's typing quickly, you don't search while they're typing. Htmx provides this capability by allowing you to say how long after a keypress to wait before invoking. And if that's not sexy enough, htmx also offers the ability to dynamically update a query parameter based on the contents of the input tag. This gives your users the ability to bookmark or deep link their search results. I love this feature. It's the simplest thing, and without htmx, it would have been a pain in JavaScript to write. But here it is. Just add an attribute and it works. Putting all that together and you get something like this. Note that the name of the input must match the name of the query parameter. That's how htmx knows what's to populate. So the event is a key up, which gets called when the user hits a key, or technically when they let go of it. By augmenting that with changed, that modifies the event so that it only triggers if the contents of the input change. This way, pressing an arrow key doesn't trigger the search. The other part here is the delay. This is the debouncing. So the full event is a key up that changes the input and then is followed by 500Millis of no other events. You set the target to tell the input where to stick the results, and this little attribute is all you need for the deep linking feature. Add hx, push url, and the contents of the input tag get expressed as a query parameter with the same name as the tag deep linking out of the box. Alright, that's the idea. Let's use this to update bothing. Alright, let me open up the search. It's scrolling up .
speaker 3: to the input here.
speaker 1: and now I'm going to add the htmx attributes. The ax get points to our search view. This is the trigger with the .
speaker 3: dobouncing feature.
speaker 1: I want the results to go into a div that I'm going to name results. Then here's the deep linking thing. That's the htmx. Now let's deal with the results on the page. The next thing I want to do is move all of this result output so that it becomes part of the dynamic content. That means I wanna put it in a snippets file. When the dynamic part gets loaded, this snippet will display the actual results. I'm using static in here, so I've got to add up a import. Oh, and I missed the more part.
speaker 3: Just let me go back.
speaker 1: And this will now be inside the snippet. Now if I'm deep linking the page, I don't want a dynamic call in order to populate the page. I want to show the results directly. Luckily, I can take advantage of Jango to do that. This is where my .
speaker 3: results are going to go.
speaker 1: And by including the snippet directly, I can create a page without doing the dynamic piece populating the first set of results, enabling us to do deep linking easily. The last thing I need to do is update the view so that the hx get fires properly. Remember, back here with lazy loading, I used two different views, one for the page and one for the actual snippet. You don't have to do that though. Htmx turns on a header for any calls it invokes. So like with a form where you check the request method, you can also check for the htmx header and render a snippet instead of the Full Page. You could write the code that checks that header by hand, but there's an easier way. There's a third party library called Jango htmx, which provides a bunch of tools to make your Django htmx experience simpler, one of which is to put a boolean on the request object. I'll show you what that code looks like, and then I'll show you how to set the library up. So here's that boolean that I was talking about. If htmx calls this view, this will be and this chunk will render instead of this trunk. This means that when htmx is calling, the snippet gets returned, and when the browser's calling on its own, then the Full Page gets returned. I've stuck in a little delay here as well to mimic a query that's more expensive and makes it a little leisure to demo this stuff happening dynamically. Itbe. Even more important when I start to an infinite scroll later. All right, I mentioned Jango H chx. You p install that puppy, but you do have to make a couple configuration changes. So let me just show you how to do that. I need the app. And because it mucks with a request, it uses middleware to do that. And instead of typing it out and getting it wrong, I've got it in my clipboard. Okay, that's pretty much everything it goes. Let's fire this puppy up and try it out. Well, that was fun. This is what happens when you do things live. Statics, a library, not a string. Let me go back to the editor. Try again. And there we go. I got a page, let me search. And as I start typing things, it updates and goes along getting more. And of course, we can deep bookmark now and there are the results.
speaker 2: Okay, Chris, this one was really interesting to me because of key Press, because it starts to show htmx. You know, it's like Carson has thought about these things a little bit and he's thought about the event system in the browser. He had a background and something called intercooler before this, and then how it all fits together. Kepress is a pattern on the browse ser side with a lot of sharp edges. And so you got to do extra things like debouncing, but you wind up covering it in this segment because htmx actually solves that, but solves it. Declaratively right.
speaker 1: Yeah. It's the Carson's experience at doing this before. And and actually using this means this isn't somebody just sort of dreaming up what if it could, right? Like that. There's there's a lot of practicality behind this and you can see it all all through the library, know the string that they use for the event. I find a little weird, like sort of, okay, we're parameterizing it sort of, but once you kind of get over the ugliness of it, there's a lot of power there, right? So the fact that they've thought about the fact that there are different kinds of key presses, right? I don't want moving the cursor to invoke the search. I don't want na be going back to the server because somebody's hit a key that doesn't actually change anything. I don't want to be going to the server if somebody is typing particularly quickly. I don't want to be going to the server 15 times so that they can type their you know, their 15 letters, let them type it and then figure it out afterwards, right? So you know the my hesitation about the syntax of event and then random attribute and random attribute, as long as you're kind of comfortable with that sort of magic wand that goes inside that inside of that quote mechanism, it solves the problem and it works quite .
speaker 2: effectively. And similarly, and I think you cover this in the segment hx push url.
speaker 1: Yeah this is one of .
speaker 2: argument that's worth its weight and gold because I do not want to deal with the the pile of crap that is the JavaScript api for dealing with history.
speaker 1: Yeah, it's it's it's a thing of beauty and not only is it just, Hey, looks add an attribute and it's done, but it's the kind of thing that I have frequently intentionally not done because of exactly what you're talking about, right? Like it it's a nice to have feature. No one's ever gonna na scream and yell if it's not there. It doesn't stop you from making money. You can just keep doing your process right? Like it just isn't a necessary thing. But the you know by by adding an attribute equals and all of a sudden you've got deep linking, you've got bookmarking and it's like, Yeah, this is a, you know, it comes back to our theme. This is the way html should have been in the first place.
speaker 2: Can we get this added to the standard? And it's also your theme about the development process. You started with something that was already there, cut and pasted it into another template, kind of like progressive .
speaker 1: enhancement again. Yes. Yeah.
speaker 2: You also, I think gave a mention to the Jango htmx package by Adam Johnson, right? Yes.
speaker 1: It's for the example that I'm using in the slides here. It's not absolutely necessary. You can essentially, it allows you to sort of check the header. So htmx sets a header every time an event calls the view. And this allows you to differentiate between the htmx call versus a standard browser call. And as I mentioned, this allows you to do things kind of like the get and post model that we're used to inside a Jango when dealing with forms. So you could write it's about the same amount of code to actually check the header, but it's just cleaner to have it in their requests. There are deeper things in that library that are worthwhile though. There's things that you can do inside of the middleware that make exception handling on the request different. So you can cause you can flip basically like a debugging bit and get them to 404 properly so that if the htmx call is causing problems, it's easier to debug that. So there's some deeper stuff in this library that's definitely worth looking into. But Yeah, you know there's there's beauty in just being able to say request dot htmx and and have your code rather than you know the it saves whatever it is.
speaker 2: seven or eight key presses. So I think Carson has seen this movie before.
speaker 1: You get the feeling that very much so Yeah.
speaker 2: Yeah. Okay, let's get on to our next segment. A foot gun of epic proportions if you've ever tried to use it. How to bring your site down without even trying. Infinite scroll.
speaker 1: Having a more button is seyesterday. Let's add infinite scroll. So if you haven't come across the term before, I know you've come across the experience. Infinite scroll is when contents get added on the fly, when you get to the bottom of a page, pretty much all the social media feeds use this pattern. I'm not sure that's an argument for doing it. Nevertheless, you pretty much already got all the tools for this already. It's similar to lazy loading. The only difference is the event that triggers it, the revealed event gets fired when the tag it is in gets rendered on the page. To modify search as you type so that it uses infinite scroll, all you have to do is replace the more button with a div triggered by the revealed event. And you're good to go recall the more button got moved into the results snippet. So let me open that up. And I want a div. Copy and paste the url from my button there. The event triggering this is this tag being revealed on the page. And here's something a little new. By default, if you don't specify otherwise, the content of the tag being triggered gets replaced. The insides in this, I don't want the insides replaced because then the revealed tag would still be there. And youget another revealed event. I've done that. It's ugly. It's spirals infinitely. What I want to do is wipe out the holding div. The hx swap attribute specifies the behavior of replacement using outer itch to Mel, like I have here, says to replace the whole div rather than only its children don't need the .
speaker 3: button anymore.
speaker 1: a little message and then close the div. That's it. No Python changes. Everything's already there. Just add the dynamic stuff to the html and you're ready to go. Let me try this out. Go back to my search page.
speaker 3: Feching, a couple things.
speaker 1: There's only two items per page, so it's loading two at a time. As I scroll down, I'm done. So I'll go back up, add another search term. There's the new results, loading pause, loading pause, scroll down, loading pause. And it's done. Well, that's a quick intro to H2x, but I've barely scratched the surface here. There are lots of other parts to the library I mentioned at the top that hx get has siblings, pretty much all the http methods. There are other events as well. You can detect mouse movements like on enter or exit. You can check clicks. You can do periodic polling and other stuff. You can use css style transitions when a swap happens, making things look a little slicker. And there's a built in confirmation mechanism. So if you want to do an, are you sure, dialogue when somebody clicks something, the event firing can be contingent on them pressing. Yes, the docs are pretty good. So htmx dot org is a decent place to start if you want to dig deeper. And they have loads of examples there, including everything I covered, as well as click edit. So you have a text widget, become an input and you know, back again, multi selecting rows, doing bulk edits, progress bars, uploads. It integrates with bootstrap and other tools. If you're using their dialog boxes, animation, drag and drop keyboard shortcuts, other stuff, see, I brought back that stuff from the beginning there lots of stuff. Okay, I can't let you get away with a one more ad. Everything I covered today is part of chapter twelve of Jango in action. The book is divided into three parts. Part one covers the base chunks of Jango views templates in the orm. Part two is about the deeper components of the framework, like how to deal with multi user sites, forms and file uploads. And then part three is a tour of different third party Jango libraries. There's a chapter on apis with ninja, there's the htmx one, and then there's a power tools chapter that covers a load of smaller libraries that are useful when building sites, the books based on a single project. So with each chapter, you're building on the last, and there's no toy problems at the end. You'll have the pieces that you need for a robust multi user website. The link in the bottom left hand corner here is to the manning site. The book's also available on Amazon. Both digital and dead tree versions are there on manning. If you buy the paper copy, you get the digital copy for free. And if you are using manning, the code Trudeau jet 24 will get you a 45% discount.
speaker 2: Okay, that's infinite scroll. It was say it feels in the segment like this is a little step up from things like pagination or whatever, but behind the scenes, the declarative nature of htmx is .
speaker 1: really helping you out, right? It definitely saves you a whole bunch of code. It is still a little bit of a foot gun. It's just a smaller caliber bullet. If you if you don't remember the the outer swap html, you can run into the same kind of recursive descent problems that you can with with less helpful code. So so Yeah, there is still a little bit of danger here, but there far less code to do it. And in in putting together this this sample, I did forget that outer wrapper myself, but it becomes immediately evident because you just sort of see, Oh, there's the title and there's the title nested inside of it and then there's the title nested inside of it. Where's control c? So you know and it just becomes a quickly, Oh great, okay, I got to remember what I'm swapping and what I'm not swapping, but Yeah, that there's, you know, it's starting to become repetitive. Paul, this saves you a whole lot of work, right? So Yeah, the concept of it is very, very straightforward. It maps very nicely. There's a few spots here that you have to remember like which div you're loading, you know my loading inside the div, or am I replacing the whole thing? But once you've got the knack of those kinds of things, it smooths out the coating a lot, and you've got a lot less to write.
speaker 2: And one of the things that jumped out to me in this segment was Carson's got a basically a book right on the web platform, web architecture and hypermedia, and how html and linking fits into all of this. And in this segment, you're showing these attributes you put in there kind of mirroring the htcp interface, right? Yes, in fact.
speaker 1: so the like the hx get can be any of the, I always call them rest calls because that's where I use them. But technically they're just http methods. So you can do the get the puts post get put post page te, there we go, delete all the rest of those things. So there's there's an hx attribute for each one of those things. So this is you know that's a subtle thing, but that gives you an awful lot of power. So one of the examples that's on hmx org is a click as you edit or click to edit. Excuse me. And so you're you've got something that looks like a form, but isn't a form like some text blog, like an address, like say, right? So and then there's a button that says edit, and then it replaces the html that's there with the form on the fly. Well, so you're in get mode and then you wanna be in post mode. Well, again, you're just taking the same div, replacing it, putting it out, and you get to post the form. You're using hx post instead of hx get. So Yeah, all these things all wire together and you're using all the same mechanisms you would normally use in more static pages, which makes it you know if you're already a web developer and you understand these mechanisms on the back end, it takes very, very little to pick this library up.
speaker 2: right? And what you were just describing, a little bit of a change right there. That seems to be the place where people immediately just want to jump off the Cliff and switch from I have a site to I have an app, and then the app has all this other little stuff. And for me, one of the big takeaways is htmx is extending the runway for what you can do with your site and a lot of those things, those really little things or what people are throwing this big pile of JavaScript at.
speaker 1: And it's not just the JavaScript, it's even the html itself. So if I were to do some of these things in view, the classic page I have, if, let's say, I've got three or four different dialog boxes that need to come up, well, that html page has to have all those dialog boxes in it. And then I've got view calling bootstrap, and I've got bootstrap modal, and I've got to cross my finger that I've wired that correctly for them all to pop up. And if the user doesn't do any of that on the page, they have still downloaded all that crap into the browser. Now by moving this into snippets, you don't have that problem, because now those dialog boxes become snippets that on the server. And so if the user interacts with it and hits it, itgo and fetch it. So this shrinks the size of the page mentally, is the developer. It also it feels more module to me, html isn't really set up for modular programming, right? So one of the things I do sometimes in Jango is I will stick stuff in separate html files and then include them, knowing that that's more expensive from a compile perspective, from compiling the template perspective, just so that I can organize my code, right? Like I don't want the 500 lines of html. Well, the nature of this is you're using that sort of modular thinking as you go, and there's an elegance to the architecture of it, and it feels separate. Oh, I've got the dialog box. For the most part, the dialog box is self contained. That goes in its own file. It's got its own view. There's there's a cleanliness that goes with this and all of it's just as a side effect of the way the library .
speaker 2: thinks about things. And if I remember correctly that those patterns are the kinds of things that Adam was trying to automate a little bit out of the box for his Django htmx package, right?
speaker 1: Yeah, it helps with some of these pieces.
speaker 2: Yeah the composition of things like that. So we've talked a lot about developer experience and how htmx really lets you continue being a Jano developer and go further with your site without having to switch over to this other architecture. But one of the last things people will say is, well, performance. I need a single page application to go through my site so that it feels like an app or something like that. I believe both of us have seen a counter revolution happening this year a little bit, that people in the world of Jango are questioning, that people in the world of the web are questioning that. You just touched on it a little bit.
speaker 1: Maybe the fast way is actually slower, right? Yeah, it's I'm conscious of my bias with this and Yeah, you know, I've got enough great the temples there. I'm not quite at your level, but I'm almost there. You know, I try my best when I'm building these things to do what is best for the customer. And I think we sometimes get we get bogged down in the you know is the ui slick, is it pretty? And that can help you with things like sales. But fundamentally, a lot of the times, does it really really matter whether or not the you know the dashboard widget inside of it refreshes without the page refreshing or whether it refreshes if the page does refresh? And as long as the page comes down quickly, I know doesn't look it doesn't look quite as slick, but does the user actually gain anything from that interaction? Right? So where some of this slickness is important is you know being able to drag and drop something that's an interface that you can use that you wouldn't be able to do in the 1.0 world. So htmx allows you to sort of decide where your slider is with that, right? So how much of that do you want? What does it mean? And there is a quantum gap between it and libraries like react and angular because the entry level for those are, Oh no, no, we're controlling all of that on the front end. You don't have a choice. Like I mentioned before, this was why I tended to favor view because I could do things like, Oh, for the pages that are just gonna to be 1.0 load, I could do that and then I would stick some view in in order to you know do the on the fly, add something to the schedule or whatever so so that you're not having to load a new page each time that happens. Well, htmx allows you to do a lot of those things. So you know I mentioned earlier, am I going to write a spreadsheet in this? No, but 95% of business applications don't need that kind of functionality and htmx shrinks that gap. The lot of the things that you would want in the business style applications will get you there. And it's got things like polling in it, right? So I've seen people build games in it, and they essentially are just using the polling mechanism in the background and a web socket. And all it does is my browser pulls your browser through the server and then, okay, Oh, yepaul's done his turn, moved his rock. It's my turn to move my rock, right? So you can get a long, long way towards fully dynamic applications and yet still use basic html and frameworks like Jango on the back end and oftentimes with a lot less code. And to come back to know, I'm yammering now, I can tell, but to come back to sort of your question, the users of this, the people who have been using it in the field, have found that because it means so much less JavaScript and because it means the bundles are smaller, you tend to get a lot more performance. So there's several essays on the htmx dot org site where the shrinking of the code by you being able to get rid of react, tor, angular or those kinds of things have meant that they've actually allowed their customers to do larger, more complex things because you're not eating up as much of the browser's memory. So you get this performant side effect, not because you actually optimized, but just because there's less things going on. And so Hey, you get a boost automatically, right? So it feels a little bit like a free lunch, but I think it's really because we've gotten so used to doing it. I'll be as bold and save the wrong way, right? We've we've normalized the wrong way and this is a little closer to the right way.
speaker 2: So just for those two points, for why people should care, you get to keep the developer skills you already have and know and you probably get better performance. Hmx has a lot to offer Jango developers. It's the developer experience going further with these skills you already know, which is ding in my book. But also you actually might get better performance than switching to a single page application. Christopher, you've got a book. This is just one of the chapters in the book. In the video that we are seeing today. You cover a little bit about the book, but just give us a quick blur. What are a couple of .
speaker 1: the other things in the book? So for folks whocome from Jango before already, there are some good chapters in here. We've mentioned up at the top that you know we've chatted before about Jango ninja. There's a chapter on that. There's a chapter on power tools. So the small things that you might want na plug in to to make your development experience easier and for, that's kind of the last third of the book. And the first two thirds are for folks who are learning the framework.
speaker 2: Last thing, what was it like writing a book, and will you ever do it again? I get the .
speaker 1: question a lot. I really should have like a prere canned answer at this point and not have fear in my eyes. What I've kind of been telling people is some of the stuff that I thought was going to be easier was harder than I thought it was. And some have thought that stuff I thought would be harder was easier than I thought it was. It's it's one of those things, for some reason, people hold in awe. And quite frankly, it's about perseverance more than anything else. Like youjust kind of have to keep going at it. I've tried to be as grateful as I can be about all the folks whohelped me behind the scenes. You know, manning's got a great mulpart review process. They've got the early release of the books. And that made a big difference for me because, know, it's the same with the video courses when I do them right? It's like, okay, you do it and then you crash your fingers and then you stick it out into the world. And then it's like two months later, somebody comments on and goes, this doesn't make any sense. And and you don't want that, particularly if you've just killed a whole bunch of trees. So this prre release process made made a big difference, and I hope it made it a better. Well, I know it made it better. I don't know if it made it great, but it definitely made it better. Could have been worse. There's there's the humble way of saying it could have been worse.
speaker 2: Let that be the mantra for the wrap up of this joy, perseverance and htmx. Could have been worse. Chris Rida, thanks for joining us today. Thanks for educating us on the way we should do our Jango.
speaker 1: Thanks for having me. It's been fun.

最新摘要 (详细摘要)

生成于 2025-06-27 10:08

概览/核心摘要 (Executive Summary)

本次讨论深入探讨了htmx库如何与Django框架结合,以极少量JavaScript代码实现懒加载、即时搜索和无限滚动等动态Web功能。主讲人Christopher Trudeau与主持人Paul Everett一致认为,htmx通过扩展HTML的核心概念,让Web开发回归其最初的简单性。它允许开发者充分利用已有的Django技能,将更多逻辑保留在服务器端,从而显著降低前端复杂性、简化测试流程,并常常带来优于传统单页应用(SPA)的性能。

htmx的核心优势在于其声明式特性和对服务器端渲染的友好支持。它通过引入hx-前缀的HTML属性(如hx-get, hx-trigger, hx-swap)来驱动页面的局部动态更新。演示中,Christopher Trudeau通过一个图书目录项目,展示了如何逐步为既有Django应用添加htmx功能,并借助django-htmx等第三方库进一步简化集成。最终结论是,htmx为Django开发者提供了一种高效、低成本的替代方案,使他们能专注于后端逻辑,同时构建出具有现代动态体验、高性能且易于维护的Web应用。

htmx与Django:回归Web本质的开发模式

核心理念与优势

  • 回归Web本质: htmx被誉为“HTML如果继续发展下去本应有的样子”,它将Web开发“带回Web本身”,使动态功能实现变得异常简单。Christopher Trudeau(发言人1)表示:“我们已经将错误的方式常态化了,而这(htmx)更接近正确的方式。”
  • 服务器端逻辑优先: 与将大量逻辑置于客户端的SPA不同,htmx鼓励将逻辑保留在服务器端。这与Django的“URL -> 视图 -> 模板”架构完美契合,开发者可复用现有技能。
  • 最小化JavaScript: htmx的设计目标是让开发者“几乎不需要编写任何JavaScript”即可创建动态页面,显著降低了前端开发和维护的复杂度。
  • 渐进式增强: htmx支持渐进式增强,可逐步为现有项目添加动态功能,而非React或Angular等框架的“全有或全无”模式。
  • 开发体验: 允许Django开发者“用已有的技能走得更远”,无需学习全新的前端框架和工具链。

性能与测试优势

  • 性能提升:
    • 案例研究: 根据htmx官网案例,有React站点迁移至htmx后,代码量减少三分之二,JavaScript依赖减少95%,页面加载时间缩短50%
    • 轻量化: 通过按需加载HTML片段而非预先下载庞大的JavaScript包,htmx应用通常更轻量,浏览器内存占用更低,能够处理更大数据集。
  • 测试便利: 将更多逻辑保留在后端,意味着可以使用Django强大的测试框架覆盖更多功能,减少了对Selenium等端到端UI测试工具的依赖。

工作原理

htmx通过向HTML标签添加以hx-为前缀的特殊属性来工作,核心属性包括:
* hx-get, hx-post, etc.: 指定向服务器发送请求的URL和HTTP方法。
* hx-trigger: 定义触发请求的事件,如load(页面加载)、click(点击)、keyup(按键)或revealed(元素可见)。
* hx-target: 指定一个CSS选择器,用于确定服务器返回的HTML片段应插入到页面的哪个位置。
* hx-swap: 控制内容如何替换目标元素,例如是替换内部内容(默认)还是整个元素(outerHTML)。

实战演示:图书目录项目

Christopher Trudeau使用一个简单的Django图书目录项目,演示了如何集成htmx实现三大核心功能。

项目设置与htmx集成

  1. 基础项目: 一个典型的Django项目,包含Book模型、视图和模板,用于展示图书列表。
  2. PyCharm集成: 使用PyCharm专业版可方便地配置和运行Django开发服务器。
  3. 安装htmx: 在基础模板中,通过CDN链接引入htmx的JavaScript文件即可。
    html <script src="https://unpkg.com/htmx.org@1.9.10"></script>

功能一:懒加载 (Lazy Loading)

  • 目的: 延迟加载大型或生成耗时的内容(如动态图表),提升初始页面加载速度。
  • 实现:
    1. 在模板中创建一个div作为占位符。
    2. 使用hx-get属性指向返回内容片段的URL。
    3. 使用hx-trigger="load",使页面加载完成后自动触发请求。
  • 实现注意: 演示中,Christopher特意在视图中加入time.sleep()来模拟耗时操作,以清晰展示懒加载效果。

功能二:即时搜索 (Search-as-You-Type)

  • 后端准备:
    • 创建一个Django视图,该视图接收查询参数,使用Q对象构建动态OR查询,并使用Paginator对结果进行分页。
  • htmx集成:
    • 触发与防抖: 在搜索输入框上,使用hx-trigger="keyup changed delay:500ms"。这实现了仅在内容改变时触发,并设置500毫秒的防抖,避免因快速输入而频繁请求服务器。
    • 结果替换: 使用hx-target="#results"将获取到的结果片段更新到指定div中。
    • 深度链接: 添加hx-push-url="true"属性,可自动将搜索词更新到浏览器URL中,轻松实现可分享、可收藏的搜索结果链接。
  • django-htmx库:
    • 推荐使用django-htmx第三方库,它简化了请求判断。
    • 通过在视图中检查request.htmx布尔值,可让同一个URL根据请求来源(htmx或普通浏览器访问)返回HTML片段或完整页面。
    • 实现注意: django-htmx库需通过pip install安装,并在Django项目的settings.py中将其添加至INSTALLED_APPSMIDDLEWARE

功能三:无限滚动 (Infinite Scroll)

  • 目的: 当用户滚动到页面底部时,无缝加载更多内容。
  • 实现:
    1. 在搜索结果列表的末尾,将传统的“下一页”链接替换为一个div
    2. 触发事件: 使用hx-trigger="revealed",当该div进入用户视口时触发加载下一页内容的请求。
    3. 内容替换: 使用hx-swap="outerHTML",这是此功能的关键。它会用返回的内容(下一页的结果及新的触发div)完整替换当前的触发div,从而避免无限递归加载。
  • 实现注意: Christopher强调,忘记设置hx-swap="outerHTML"是常见错误,会导致页面陷入“无限螺旋”的加载循环。

总结与展望

htmx的功能远不止于此,它还支持所有HTTP方法、CSS过渡动画、内置确认对话框、文件上传、WebSocket集成等高级功能。它代表了一种务实、高效的Web开发哲学,让开发者能够构建功能丰富、性能卓越的应用,而无需陷入现代前端生态系统的复杂性之中。