Build your own metaframework with Vinxi
Vite and Nitro are the building blocks of many metaframeworks today. Dev Agrawal will teach us how Vinxi lets us use Vite + Nitro to build our own JavaScript metaframework.
Links & Resources
Full Transcript
Click to toggle the visibility of the transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: What's up, everyone? Welcome to another episode of Learn with Jason. It's me, your friend, Jason, and I am very excited to be here because today's gonna be a fun one. We are bringing to the show someone who I have been watching online for a while. I think they're pretty sharp. And we're gonna be learning about something that I think is just even if it's not something you're gonna do, it's a good thing to know. So, let's get right into it. Let me bring to the stage here Dev. Dev, how are you doing?
DEV: I'm doing great. Thanks for having me on.
JASON: I'm so happy to have you here. So, for folks who maybe aren't familiar with you and your work, do you want to give us a bit of a background on yourself?
DEV: Sure. So, my name is actually Dev. And I am a Dev. I like to start off sometimes by saying that. I have been building JavaScript applications for about seven years now. I did a little bit of like PHP before that. So, that was a fun time. But I quickly like moved into a fullstack JavaScript, started building some toy applications, got some freelance projects here and there. And then like I've done professional work, volunteer work, all kinds of stuff here and there. And mostly these days I'm just like trying to take my fullstack JavaScript experience and trying to find newer, better ways to take advantage of the fact that we have the same language on both frontend and the backend, or client and server. And use that to the best of its capability.
JASON: Yeah. I mean, why not? If you can use JavaScript everywhere, we should try to put it in every conceivable location, right?
DEV: Yeah. Exactly, yeah. I also got into a bit of concentration a couple years ago. I have been really on and off with that. Just tough to find time.
JASON: I think -- I mean, it's a job, right? Like you got to be -- you got to really prioritize it to stay consistent and I think it's not always the most important thing. But so, okay. So, let's talk a little bit about -- I guess just a little bit more about what you just said. So, you just said JavaScript works. We can use JavaScript on every part of our stack. So, I guess that sort of started with Node way back in the day. And but it does feel like that's sort of shifted even more recently with things like ES modules and stuff like Vite and sort of the -- I guess the democratization of thing like SSR where it's not as -- not as much of a heroic task to set it up on your own anymore. But I guess why do you think it's -- why do you think people are pushing it in this way? Like what's the -- what's the big driver for trying to get JavaScript everywhere?
DEV: I mean, I just think the driver is companies trying to spend less money on their engineering teams so that they can have like just one team or one group of developers that know JavaScript that can just do everything instead of having to have like two separate backend and frontend teams. Like it sounds appealing to some people. But the people who are managing and paying for these engineers are not like they probably prefer if they had less people to deal with, and the same people can just do everything that they need. And that's like one sort of push, or one perspective on it. The second I just see is like from the perspective of the developer or the engineer itself that for us it's so much easier to just build features in this -- in this kind of fullstack, vertical slice rather than building a frontend piece and then building a backend piece or worse, building a frontend piece, then having to talk to a different backend team to do the backends piece for it and having that back and forth multiple times. I think it's just an all around win both for the engineers and the people paying the engineers.
JASON: Okay.
DEV: Obviously there are some down sides that JavaScript is not the best backend language. But there are a lot of people working really hard on picking it a good backend language.
JASON: Yeah. And I think, you know, having worked on a lot of teams that dealt with big production apps and backends and things, I've never actually seen a production problem where the language was the problem. Like when we're talking about performance, it is vanishingly rare that it's the language's fault. It's usually one of the engineers did something that, you know, it would have been wrong in any language.
DEV: Yeah.
JASON: So, when you get down to the point that the language really is the limiting factor, sure. But yeah, just it seems like that's -- that's not nearly as often as the way we talk about benchmarking would imply.
DEV: Yeah. And honestly, having a slow language, that's a great problem to have. it's just a lot of people pretend they have that problem though they don't and it ends up hurting them.
JASON: Yeah, yeah, exactly, right? It's the sort of I don't want to actually solve my problem, I'm going to pretend it's the language's problem and refactor the app. In six or seven months, we can get back to where we were with the app, no, the problem still exists. It wasn't the language. Let's talk about what -- when we talk about JavaScript at every layer of the stack, that's something that you could do in a very bespoke way by standing up a JavaScript frontend and then you could stand up a Node backend and you could write serverless function it is you needed to glue things together. But recently, it feels like that's all been growing together. And I say "Recently" meaning like the last few years. Where the new trend now is for everybody to ship a meta framework. And the metaframework is gonna give us frontend JavaScript, backend JavaScript, the data access layer, the business logic layer, all those things that we would expect and it's all kind of baked into a one package. And that's either done super-explicitly, like when you look at something like, I don't know, Astro. There's very clear like this is the backend, this is the frontend. When you look at something like some of the newer React metaframeworks, there's a mixing of it --
DEV: Yeah.
JASON: -- some of it is the server, some the frontend. And that is a conversation for another show. But what do you think is like -- how do you feel about metaframeworks in general? What's your vibe on them?
DEV: I'm personally a big fan of this. I have spent way too long having two separate backend and frontend repositories and suffering under that pain. Again, I understand that's an appealing concept to many teams, many companies out there. But I think that this divide between like the frontend and the backend being two separate things, this was -- this has always been a limitation of the tools that we are using. And I think oftentimes like we should separate out projects not based on the tools, but based on the teams. Like if you have two -- if you can logically separate two different teams that can operate kind of independently, then you should be have like, okay, you have your own repository, you have your own repository, and if these two things don't always talk to each other, then they'll be happily on their ways.
JASON: Right.
DEV: But if you have a single team building the entire thing, this backend and frontend split didn't always make sense to me. It just increased the amount of work I had to do. Instead of just -- what it demonstrated to me is that's lack of capability here. The tools that we are using are not aligned to help me build a good fullstack experience. They're aligned to help me build a good frontend and a good backend experience, which is not what I'm actually doing. I'm not building independent applications. And I think some of it was also kind of a response to the microservices hype when everyone just wanted to split everything into the finest bits. And we kind of had a hard swing back from that, obviously, about four years ago or so. And that's kind of the reason why the metaframework exists, I guess.
JASON: Yeah.
DEV: But also one kind of misunderstanding I've seen with metaframeworks is you're mixing your frontend and backend concerns together. Or make the framework is recommending that you mix frontend and backend together, which is not really what they're doing. What they're doing is get -- helping you build a better frontend using a server.
JASON: Right.
DEV: Which I think is a different kind of mentality from like mixing your frontend and backend together. Like you can still have a separate backend. Either logically separated or physically separated backend. What a metaframework is doing, a server is gonna help you build a better frontend, or deliver a better frontend experience. Whether you want to mix your backend concerns into that, that's up to you.
JASON: Yeah, and I think the thing that's interesting about this is that it sort of addresses one set of problems and revisits another set that we had sort of set aside. Because one of the big challenges, one of the reasons I think PHP got so demonized back in the day is we used to write PHP apps the way that we're currently recommending people write React apps. Where it's sort of like, make your database call and slam that database call right into the middle of the server output and put this server over here and this client thing over here, and justifiably so, that's extremely difficult to reason about and maintain over time. Which is why I've kind of found it fascinating the way that different teams approach this. How are people thinking about how do we keep this stuff maintainable, right? And, you know, in some cases like the next team seems to say, that's not our problem. You figure it out.
DEV: Yeah.
JASON: And teams like, you know, the Astro team are saying, no, here are some very strict rules on how things are split up. The Remix, or I guess the React Router team has pretty strong opinions about use this API exactly and things don't mix. We'll see what happens when they introduce React server components. But it feels like we're sort of having to think about how do we reintroduce the flexibility of everything in one place?
DEV: Yeah.
JASON: Without reintroducing all the problems of everything in one place that led to the horribly negative PR campaign that PHP's been battling back from. And it took Lamborghinis to overcome.
DEV: Yeah. The funny thing about this whole like conversation is we have been through this exact thing before. When React first came out, like no one wanted to put logic and templates together. But once we started doing that, we had a whole like crisis of, okay, how do we organize this thing? And we came up with like higher order components, render props, all these patterns to try to work smart and dumb components. All these patterns to help fit that into our head better and better figure out how the concerns -- how they should be separated in this new componentized world. We're just seeing a repeat of that. But across the network boundary.
JASON: Yeah. That -- yes. I mean, it's -- like what is code except a communication tool, right?
DEV: Yeah.
JASON: And so, figuring out how to communicate in ways that keep all of the context we need and not just the kinds we're focus on right now is an ever-like it's a permanent problem.
DEV: Yeah.
JASON: We're never gonna solve it. I think the way reason this is fascinating to me, there's also been this acceleration very recently where it felt like at first building a metaframework was a huge task, required tons of funding. You had to be -- you know, you had to be like one of the best of the best in fact world to be able to even consider doing it.
DEV: Yeah.
JASON: And even most of them would wave off of it because it was just too many things to manage. But then Vite came out.
DEV: Yeah.
JASON: And so, we got tool -- like the esbuild, the Vite, and a few other tools in that ecosystem that suddenly now we can build metaframeworks in a fairly straightforward way and then you got into my DMs and you started talking to me about a new tool that kind of brings all this together and gives us a little bit more. Can you tell us a little bit about what that tool is and kind of what it does?
DEV: Yeah, I think you did a great job on kind of showing the impact that Vite had. But there was one -- there was one other kind of milestone that happened after Vite which was Nitro, which is basically NuxtJS deciding it wants to be modular and democratize everything of all of its internals. And the web server part of Nitro completely -- or the web server part of Nuxt went into Nitro. And had the development process, and the whole ecosystem that's around it for all utility things. And that had a similar impact because we saw Analog, the framework that Brandon Roberts is working on, took Vite and Nitro and built up similar to Nuxt. You have one person on his off time building a metaframework. These two tools -- yeah.
JASON: And the really interesting thing about it, we had Brandon on the show and it is full-featured.
DEV: Yeah.
JASON: Like you can do a lot. And for folks who have been looking at Angular, ah, Angular, it's like yesterday's code. When you look at what's happening inside of Analog, it feels modern and new. The fact that Brandon was able to do that in his spare time as a, you know, more or less a team of one --
DEV: Exactly.
JASON: Is just ultra-impressive.
DEV: Yeah. And that's I guess just the Vite community's fascination with building cross -- or framework-agnostic tooling that we are seeing the power of. But I think then what happened like about a year or two years ago is the -- the team behind solid start, they were trying to build a metaframework. And what they kept running into is that there is just -- even with Vite, there is too much work here. This is like you're still spending a lot of time debugging why this isn't deploying to Netlify or not deploying to Cloudflare or not deploying to -- building adapters and all those sort of things. And that's really the part that Nitro takes care of. It's pretty obvious that Vite and Nitro are the two fundamental pieces that we need for any sort of framework to be built on top of. And anyone else is just going to be rebuilding these two pieces on their own. Whether that's -- I guess that's only NuxtJS at this point, which is not using Vite. But everyone else has moved to Vite. And whoever is not using Nitro probably has either their own server or just gives you a handler, hey, plug this into whatever server you want. But one of the people on the Solid start team, Nikhil, he really likes React and server components and really wanted to experiment with server components. But he doesn't want to bring in NuxtJS. We didn't want to bring all of NuxtJS's opinions into his project. We wanted his own kind of way to play around with server components and the new React features and build on top of those instead of using NuxtJS. And again, that's a similar mindset that I was in when I first discovered this and I started to play around with this. And he had already like tried to build server frameworks multiple times before. He did a talk at Vite Conf last year which I highly recommend if you want to -- it's pretty short as well. But if you want a quick history on why Vinxi exists and that talk is a fantastic intro into it. He had built server frameworks, he had his own experience and he was looking around at what other people are doing with Vite and Nitro. And at the same time he was trying to contribute to Solid Starting which trying hard to be a framework that doesn't need a lot of maintenance because it's a very small team working on it. So, yeah. I don't know if I -- how much I can go into what the inspiration exactly behind Vinxi was. But yeah, that's kind of the background that Vinxi was kind of born out of.
JASON: Yeah.
DEV: That we have these two fundamental tools. We need a better way to put them together. Find an abstraction that we can build frameworks on top of that we can bring the components and have the capabilities like server components.
JASON: Got it. So, basically Vinxi is taking the building blocks of Vite and Nitro, and just like Brandon did with Analog, we can say, I really like the way that Svelte does this thing. But I wish Svelte Kit was different.
DEV: Exactly.
JASON: I'm going to build my own framework of Svelte metaframework. I can build my own thing and go. Build my own Remix or NuxtJS and noting to become an expert in how RSV works.
DEV: That's what Tanner is doing with the start. He wants a React framework, he wants a re-component. He saw Vinxi and starts building on top of it. Which is amazing. There are two production frameworks being built on top of this right now. And I'm sure there will be more.
JASON: All right. Well, let's -- I could probably ask a million more questions, but it's probably at this point easier to start looking at it. So, let's dive in. And I'm gonna switch us over into this view here. And the first thing I'm gonna do is just remind everyone that this episode, like every episode, is being live captioned. The URL is scrolling across the bottom of the page here. [Big truck sound!] Got a -- somebody forgot their muffler today. And that captioning is being done by Amanda from White Coat Captioning. Thank you so much, Amanda, for being here. And it is sponsored by Netlify. So, head over to the Learn with Jason side and, you know, maybe give a little click over to Netlify and say what's up. Tell them I sent you. So, now, we're talking to Dev. So, here is Dev's Twitter profile. We are talking about Vinxi. Here is the Vinxi website. Let me actually put that one up on screen for a second. Here's the Vinxi website. And so, we are going to put the Vinxi website up on the screen for a second. It's build by Nikhil, here is the profile, you can check that out. And the rest of these we can get into. This is the episode, you can search for this on the Learn with Jason site, hit command K and type in Analog, and you'll find this episode, this is Brandon building metaframework on top of Angular. This is the talk -- I'm gonna -- my thing for capturing links in the Learn with Jason Discord isn't working as well as I want. So, this is what you should Google if you want to find -- I mean, ViteConf Nikhil Sarif is what got me there. Dev, I want to build a metaframework, see if we can out Tanner Linsley Tanner Linsley in the remaining 40 minutes we have. God damn, that's roll. That's plenty of time. Tanner, we're coming for you.
DEV: Perfect.
JASON: All right. What should I do first?
DEV: Start a new npm project.
JASON: Okay.
DEV: We're gonna start from scratch. The only baseline is Vinxi itself.
JASON: Is there an npm create command for Vinxi?
DEV: Just a blank npm project like npm init.
JASON: Let me make a project. We're gonna call this build-your-own-metaframework. Get into it. Get init, and then I'm gonna npm init. All right. And then let me actually open this one up. GitHub...
DEV: We're doing like the Vite starter you can start a Vite application by just having an empty folder with HTML or JavaScript or something like that.
JASON: Got it. Cool. This is where I'm at right now. I got nothing in here.
DEV: Just add a type module in here somewhere. Because it's gonna complain if you don't. Yeah.
JASON: Okay. Type module.
DEV: Yeah. Now let's just create a file index.html. We're gonna start at bare bones HTML, single page application setup. You can put anything in here. Sure. Okay. And then we're just gonna copy this and do an H1 so we can actually see what's on the screen.
DEV: Cool. So, the only thing we need to add now is a file called app.config.ts in your route. This is gonna be like your main configuration file. You can also use Vite.config. Vite's configuration file, but any Vinxi project, the convention is you are going to use app.config so we can see it's going to be a Vinxi project. Install Vinxi as well. Npm install Vinxi or whatever package manager you're using.
JASON: Okay. There we go.
DEV: Yeah. It should be pretty quick.
JASON: Happen here -- yep. Go.
DEV: Okay. And we're gonna have -- I think it's the default -- okay. So, in your app.config.ts, export default, create app. And do you have autoimports on?
JASON: We're gonna find out.
DEV: There you go. Create app from Vinxi. Yes.
JASON: Okay.
DEV: The core piece of abstraction, the core concept that we need to talk about here when using Vinxi is a Vinxi router. What a Vinxi router does is you can have multiple routers in your application, so you pass the router as property here as inside your create app configuration. And this is gonna be an array.
JASON: Okay.
DEV: Let's give it like the first object. So, this is -- this is your configuration for a Vinxi router. We can give it a type. And if you use autocomplete here, it's gonna tell you that there are four types of routers that you can give it. We are gonna go with the spa, or a single-page app.
JASON: Okay.
DEV: And let's give it a name. Let's call it "Client." Say every router can have a name. And let's give it a handler. So, this handler is going to be your index.html file. So, dot slash, HTML.
JASON: All right. Got it.
DEV: Yeah.
JASON: Got it.
DEV: Nice. This is like our first Vinxi router. Let's now save this and NPX Vinxi Dev in our terminal. Use Vinxi as the web server.
JASON: I'm going to do one of these so I don't forget. It's Vinxi Dev -- no flags?
DEV: No flags.
JASON: And let's npm run Dev, okay. We got localhost 3000. I'm gonna head out here.
DEV: And this should be your HTML file.
JASON: Holy buckets.
DEV: This is just a V tab, nothing fancy here.
JASON: It's cool. One of the things that I think a lot of us forget, I forget, when we run that npm command and get a Vite app or a React app or whatever it is that we're using, we're getting -- it's just code. Right? Like there's no magic.
DEV: Yeah.
JASON: What we just did here is we're configuring like, yeah, run a single-page app style off of index.html.
DEV: Yeah.
JASON: Easy, done, whatever it is. Don't need a lot of files. Config and that stuff that comes in. It's for convenience, the lint config and the prettier config, all that stuff, it's to make our lives easier, not a requirement for building.
DEV: Exactly. And you can just create a JavaScript file somewhere. Even TypeScript file, create a TSX file.
JASON: Okay.
DEV: Add something, index/-- go off of that. Add just a console log in here or something.
JASON: Okay.
DEV: Like we don't have anything else installed, we just have a JavaScript file.
JASON: That's true.
DEV: And in our index.html, we can say script --
JASON: You don't have to do this anymore. You can like do the script -- what is it, type?
DEV: Module, yeah.
JASON: Type module and then I just do the source.
DEV: Literally point it to the TSX file.
JASON: You just go right to it. What doesn't it like?
DEV: The self-closing tags don't exist, I think.
JASON: Yeah, yeah.
DEV: We had to think about that on Twitter recently. There we go. And if you --
JASON: I open this up, get into my console. There we go.
DEV: Yeah. This is the power of Vite. You can script SRC, or TypeScript file and it just works.
JASON: This is pretty slick.
DEV: Yeah.
JASON: Pretty happy to see how quickly all of that came together.
DEV: And up until now, if anyone has used Vite, they're just gonna say, okay, what's new here? This is just Vite. So, we're gonna like carry on this trajectory for a little bit.Let bring in, I don't know, React. So, let's -- we can npm install React, React DOM, types React, types React DOM as well. And let's also install the Vite plugin for React which is @Vitejs/plugin/react. Yeah. That's it.
JASON: Okay.
DEV: So now we're gonna hop over to app.config.ts.
JASON: Whoa. I had those all configured so fast.
DEV: Yeah. When we have npm install, 20 dependencies, it's gonna be slow. Yeah. Let's import our Vite plugin React here.
JASON: Okay and remind me, is that a named?
DEV: Just a default. Call it React plugin or React refresh or anything.
JASON: From Vite's plugin React.
DEV: So now that we have like the more important aspect of the router which is that each router has a plugins property. And which means each router has like an independent pipeline of plugins that you provide to it. So, yeah. React plugin as a function. And this whole thing is a function itself. So, plugins is gonna be a function that returns that array instead of just that array. Yeah,
JASON: Okay.
DEV: Each router has the independent build pipeline which is different than a normal Vite app. In a normal Vite app, you have one bundle. But the key inside Vinxi is you never have just one Vite bundle or one Vite application. You always have like different routers or like different, I guess, parts of your application that can be bundled separately. Like if you don't --
JASON: Okay.
DEV: -- necessarily need to have just one bundle for the whole thing.
JASON: Got it.
DEV: Yeah.
JASON: Just to disambiguate a little bit, because someone was talking about not knowing the difference between Nitro, Vite, Analog, et cetera. What we're getting out of Vinxi here is almost Vite. Because like Vite is gonna have this same concept -- like these are Vite plugins, obviously.
DEV: Yeah.
JASON: The difference is in Vinxi, I'm specifying routers, which is not a Vite concept.
DEV: No.
JASON: And so, I'm able to basically do multiple Vite setups --
DEV: Yes.
JASON: Ideas of one project by using this routers concept.
DEV: Correct, yeah.
JASON: Got it.
DEV: Vinxi is a layer that sits on top of both Vite and Nitro that orchestrates everything.
JASON: Okay.
DEV: In this case, orchestrates one single page application router, whatever your HTML, whatever handler you point it to, it's gonna take that, bundle it as a single page application and serve it.
JASON: got it. Okay. So, I'm gonna do the thing.
DEV: Yeah, do the thing.
JASON: Where I set up my route. Right? And then what happens?
DEV: Then you go in your TypeScript file. You can just create route from -- if you just type "Create route" it should --
JASON: Create route --
DEV: React on compliant. Get element by ID, get your root element here.
JASON: We called it root.
DEV: You can remove the hash from here.
JASON: Oh, that's right, that's right.
DEV: Yeah. And you don't pass a second parameter anymore.
JASON: Nice.
DEV: They changed out that works. Yeah. You do a dot render at the end.
JASON: Ah, got it.
DEV: Here you pass in your app or JSX, whatever you want for now.
JASON: Okay. Let's start by just doing the --
DEV: Yeah. There we go.
JASON: Okay. What are you mad about? Leave me alone.
DEV: You probably need to import React from React at the top for that.
JASON: Just TypeScript thinks that this might not exist. I'm just telling it to hush.
DEV: Yep. That's the one I was talking about.
JASON: Oh, this one is the one you were talking about. So, import React.
DEV: I'm pretty sure there's some other TypeScript configuration, but this is the easier one for now.
JASON: Yeah, because question don't have a TS config set up. If we were to set up TypeScript and extend the React rules and all that stuff, it would autodetect. But who cares? Not me. Let's go back out here and I need to start it again.
DEV: Yeah.
JASON: And it says --
DEV: There we go.
JASON: What up? We're running React now.
DEV: We now have a React app.
JASON: Excellent.
DEV: And we can make a TODO app or anything like that.
JASON: Okay.
DEV: We can make a counter app at least, I guess.
JASON: Okay. Do you want me to do it in the same component?
DEV: Let's do a now component.
JASON: Okay. So, we're gonna call this one counter. TSX. And inside in, export const counter equals and this one's gonna have -- what do we need? Like count --
DEV: So, state --
JASON: Count equals use state. And we'll start it at zero. And then we're gonna return I guess just a button.
DEV: Just a button is fine.
JASON: And we'll do an onClick. That's going to set count, and we'll do the like -- I think that's the responsible set count, right?
DEV: Yeah.
JASON: And then we're gonna show the count.
DEV: Might need to import React from React again.
JASON: Right, right. just do it out here.
DEV: Cool.
JASON: Okay. So, in our index, I'm going to import counter from counter. And then we'll change this into a fragment. Move this out here. And we'll drop our counter in. And assuming I remember how React works, this should just function.
DEV: Yeah.
JASON: Look at it go, everybody! Okay. All right.
DEV: We have a React and Vite app now. You can bring in anything that's supported in Vite, which is pretty much the entire universe.
JASON: Okay.
DEV: And anything that's important. In React, there's also a pretty big universe.
JASON: Yeah.
DEV: But yeah, that was obviously for a framework. We're not gonna stop at that point. I'll kind of hand it to you now, what do you think a framework should be doing? What else do you see in your framework? Can add some data fetching, we can add some filesystem routing, all these things that fancy frameworks have that we like to have.
JASON: Yeah, I think probably the first one I would think about is the data fetching.
DEV: Data fetching, okay, cool.
JASON: So, I would want -- if we pseudocode this up, right? Like let's say -- we'll call it a post list. And from here, I would like export default -- or export const. Post list. And that's gonna be like how would this work? We would do it... I don't know. Maybe we get one of these. And then we put one of these in here. And then we would say... like post map. And for each one of these, we would want to output a LI with a key of post ID. And then we would want the post title. Right?
DEV: Cool, yep.
JASON: And we want to be able to then load posts. And let me import React from React up here so that it stops yelling. Okay.
DEV: Cool.
JASON: And so --
DEV: You want to go from the server or the API endpoint?
JASON: I think from an API endpoint because if I remember correctly, I should have... API episodes and we can just grab some titles out of this.
DEV: Nice.
JASON: Yeah. We'll just be able to grab the titles out of this. So, I want to be able to fetch from that URL.
DEV: Okay.
JASON: And so --
DEV: Let's create our own API.
JASON: Yeah, let's create our own API. So, like I would say, okay. Yeah. Let's create our own API. Tell me how we do that.
DEV: Lets hop on to our app.config file and we're going to add a new router this time.
JASON: Okay.
DEV: The whole point is you create multiple routers and that's how you compose your application. In this you're going to do an HTTP router. We have two different types of router, a spot router that delivers a single-page app with JavaScript, and now we have an API. You can give this SRC API.tsx. Make it dot slash, the handler is gonna be a TypeScript file this time.
JASON: Got it.
DEV: I mean, the handler can be ./src --
JASON: Oh, I got it.
DEV: Create this file in a minute.
JASON: Gotcha.
DEV: Now we can create this file and this is going to be our API handler.
JASON: Okay. API.ts.
DEV: here you're using Nitro, that's what powers the server.
JASON: Okay.
DEV: And the way you create endpoints with Nitro, just export default event handler. And event handler from Vinxi.gdp. And this is going to take the end object -- this is going to take a function, sorry.
JASON: Oh, a function.
DEV: The functions parameter would be an event object. You can remove the curly braces. It's just event.
JASON: Oh, it's just event. I understand.
DEV: And you can console log anything about the event that you might be interested in. You can console log even -- or just the whole event. And you can return anything from here that you want.
JASON: Okay. So, this looks like it's doing what I want.
DEV: Yes, just hit slash API slash -- yeah, let's return something.
JASON: Okay. So, it's testing.
DEV: Now we need to add one more thing.
JASON: Okay.
DEV: So go back to your Vinxi config. Right now we have two different routers, but they're both gonna conflict with each other, right?
JASON: Right.
DEV: So, for the second router, we need to add an additional property called base. So, base we can say /API. When you go to /API, you're going to hit the API router, otherwise we're going to hit the spot router.
JASON: Whoa, whoa, whoa. What is that?
DEV: I love it when it does that. I don't know why that happened.
JASON: Excuse me, excuse me. What are you doing, bro?
DEV: Go to API/something else. This might be an issue, a bug or something, but when you have a base URL, you need to add something else after it for it to work.
JASON: Got it, okay.
DEV: But yeah. So, now we have our API. And obviously once we have it had API, we can bring in any sort of routing library here that we want. We can just say that if event -- if event.path equals post, and if event.method is get, then return this post object or something. Like that's our endpoint, right?
JASON: Let's do one of these event.method. Then we'll do path = event.request.
DEV: Just even .path.
JASON: Oh, just event.path.
DEV: Yeah.
JASON: And then we can -- I guess we'll say if it's a get. If method equals get -- so, that way we don't have to set up post. And --
DEV: Yeah.
JASON: The path equals, we'll say, post. And is it gonna include the slash?
DEV: It's not. We can also check that.
JASON: Okay.
DEV: But it doesn't include -- it doesn't include the slash API. It's gonna include the slash.
JASON: The slash post. So, it skips the base.
DEV: Yeah.
JASON: And then we'll do I guess for now we'll get const result will be await. Fetch. And then I had in here that API.
DEV: Yes. This isn't an async function yet, we can make it that.
JASON: Oh, right, we should.
DEV: Added in the the event handler. Yeah. Right there.
JASON: I was like, I know this is in here.
DEV: JavaScript is here a little.
JASON: Is this a function or a const?
DEV: So, the event handler is like -- it's just a wrapper. Like it's what defines kind of what an endpoint is.
JASON: Oh, I didn't -- I guess I've never seen that you can just export this. always thought you had to make it a function or a const or something.
DEV: If you have like export -- if you have a named export, you have to do this. But for default exports, you don't need to store it in a variable.
JASON: Got it. Cool.
DEV: So, it's the same thing we did in app.config.
JASON: Okay. Now we're gonna do the same thing here where we'll just do one of those.
DEV: Sure.
JASON: And otherwise, we will -- data will be await, res.json. You don't need to stringify it, just return it.
DEV: Yeah. Nitro takes care of all of that.
JASON: Slick. Now if I go to posts.
DEV: We did it -- it took a while.
JASON: That API is not the fastest. It doesn't cache when I hit it directly.
DEV: That's fine. Now we can go into a React app, commit a horrible sin by fetching a useEffect and then we have an application.
JASON: Okay. Let's get our useEffect. We're going to set that and get our posts and set posts and put them into useState. We'll just make that an array by default. This is gonna be grumpy at me and whatever. Or no, that would loop it, right? If I did it that way.
DEV: Yeah, that would be an npm array.
JASON: Now I can just hit it the same way I do in here, right? Hit our own API endpoint?
DEV: Right.
JASON: We're gonna get this --
DEV: This can not be async, though.
JASON: Right. I have to do my async function, get posts.
DEV: Yeah.
JASON: And put all this in here.
DEV: The good old way of doing this. Up until we get access to React.use.
JASON: And then we'll call get posts. And then down here, set posts at --
DEV: Data.
JASON: -- data. We need to update this to be API posts. But that's not gonna work because you need a full URL.
DEV: It's the same origins, this should work. It's also on localhost 3000. If you don't give it an origin, it just uses your existing origin.
JASON: Okay.
DEV: And then we need to --
JASON: Use the thingy yet. What is it mad about?
DEV: It doesn't know what types exist.
JASON: That's okay. We're just gonna ignore that.
DEV: Yeah.
JASON: Go in here, import our post list and we're just gonna drop that right in here.
DEV: Cool.
JASON: Okay.
DEV: There we go.
JASON: Holy buckets, there they are with the first hundred episodes or so.
DEV: Nice.
JASON: This is slick. This is really slick.
DEV: Yeah. And this is kind of where -- like this is at least for me when I start realizing the power that I have access to everything. I have API routes, I have my single-page app. I can bring in what I need, and not a fully formed framework and its opinions on how I should do data fetching and do routing and stick to that. From this point on, we can bring in stack query, we can bring in any sort of route their we want. And we have an API endpoint so we can put in any routing kind of library there. Like bring an express router even though that's a bad idea. But any sort of like the only thing we need to do on the API endpoint file now is that matching. Here is the request method, here is the request path. Which handler am I supposed to call? If I send a get post with ID, you cannot obviously lest just look at it like this. You need some sort of a regular expression matching to figure out what handler to call. But that's the only missing piece here.
JASON: Right.
DEV: I'm not bringing an entire framework like Express to build an API. I have everything in here to do whatever I need.
JASON: Yeah. And for a lot of, like, especially when you get into smaller projects, you don't -- I know what my endpoints are gonna be.
DEV: Right.
JASON: I've got three of them. I don't need lots of flexibility in there. Bringing in a lot of just in case code feels like a liability. I have to update the deps, and sometimes you get the minor update that's a pure dep incompatibility problem. I spent five hours updating an app that I'm not using the dependency. It's just wild card matching in our routes. That's silly.
DEV: Yeah. It gives you the entire spectrum of possibilities from here.
JASON: Yeah.
DEV: Now let's go with the possibility that we do need. Let's bring in a router now. The routing is a very fundamental part of like pretty much any applications, any web application, really. And we can use -- like React router is obviously the big one. But we can go minimal and we can go with router. So, the blue o-u-t-e-r. That's the only package. It's kind of inspired by React router, but it does very little work. And all we need here -- so, is we need -- we can import route and link from router. We can import it pretty much anywhere. Router, yeah. And now we can put our two counter and post lists. We can put them under routes. So, like maybe if route is home, you have the counter component, and if the route is posts, you have it under post list.
JASON: Right. So, I do route.
DEV: Yeah.
JASON: And then I'm gonna have another one -- so --
DEV: You can give it a path, yeah.
JASON: We've got a path. And our path is gonna be home.
DEV: Yeah. And you can give it a component.
JASON: And I set my component, and my component is
DEV: You can also pass children Instead of making route a self-closing tab.
JASON: Oh, slick. That's actually way better. Why don't duo that?
DEV: Okay. And then we can have --
JASON: This one we'll set up as route path/posts. And we'll drop -- oops. Drop this down here.
DEV: Yeah. And we can add a link somewhere just so we can navigate from one to the other.
JASON: That's doing what we want. Okay. And then we'll set up a NAV up here. Do a link? Is it a 2? Href?
DEV: It's a 2, yeah.
JASON: And then we'll say "home." Then we'll duplicate that, and we'll get one for posts.
DEV: Perfect. There we go.
JASON: Look at us go, everyone.
DEV: Any router that you want.
JASON: This is incredible.
DEV: Any router that you want, you want to bring in tan stack router to get full type-safety, you can do that. Stay minimal, you can do this. Or at one point, you can have some sort of server component router, you can bring that in. But, again, like the framework doesn't limit you a single routing library or a single way to do routing data fetching. Just bring in whatever you want, right?
JASON: So, Olivier, Oliver, has the same question I have. Which is are we able to just deploy this? I can ship this to Netlify, to Vercel, whatever, and it will just work? What's left to make this go to production?
DEV: Yeah, it uses Nitro. So, anywhere that you can deploy Nitro, you can deploy this. Which is everywhere.
JASON: Just about everywhere.
DEV: That's the point is you get a set of tools that work everywhere, Vercel, Cloudflare, self-hosted, Netlify, whatever you want. And that can go everywhere. I'm -- that also means that Vinxi doesn't necessarily have to maintain a bunch of adapters or you as a framework author don't have to maintain a bunch of adapters. This is one of the biggest pain points for anyone building a framework.
JASON: And this is I think like -- this is why I think I got so excited about this kind of like Vite/nitro revolution is that suddenly every framework -- and I think the only framework at this point that doesn't have either an adapter layer or auto deploy is Next, for obvious reasons. And so, like you just kind of -- you're like, oh, I want to use Svelte Kit. Where do I deploy it? Wherever the hell I and the features are there. Knowing I can do the same thing with the custom framework I'm building on Vinxi, it just feels nice. I'm not relearning what I did. We live in a bubble on Twitter, everybody using next or Svelte, but so many of us are using create React app. And coming from a create React app world, it was homemade, we had to figure that out, being able to just transition that over into a Vinxi metaframework where suddenly we get things like API routes, SSR, so on and so forth, that is way more palatable than having to relearn how to are write React in the next way. Or how to write React in the -- I guess Remix had a few fewer opinions. Or back in the way with the Gatsby way before it stopped -- you had to re-learn this whole world in order to adopt a framework. I haven't felt like I'm re-learning very much here. I'm just writing React the way that I would always write React you know? Up to and including I'm just doing the React mount thing and it just works. So, let's take this a little bit further --
DEV: Sure.
JASON: I want to be mindful of time. We have about 35 minutes left.
DEV: Okay.
JASON: I would love to get -- there's another question here, with an actual router where you can do data loading and rendering. Three things that I'm interested in here, one is file-based routing. One is like a data loader kind of SSR-style data loading. And the third one is the React server component stuff because I know we were kind of talking about those.
DEV: Yeah.
JASON: What else -- is this anything not in that list that you really wanted to get to today?
DEV: Yeah, let's actually do server functions because it's actually super, super quick.
JASON: Okay. Let's do it.
DEV: we literally need to install one package. Again, because of this kind of Vinxi router setup, a lot of these capabilities like server functions become like, install this, add these two plugins in two places and you have it.
JASON: Okay.
DEV: Let's install at Vinxi/server functions.@vinxi/server-functions. Let's install this in our configuration, interact.config. Add a new plugin to the client. In the plugins array, we can say serverfunctions.client. It doesn't know it's a thing yet. So, we'll need to import server functions from the package we just installed.
JASON: Get out of here. Okay.
DEV: It probably doesn't have type definitions properly. So, server functions from Vinxi, server function/plugin. So, we also need to add that.
JASON: Okay.
DEV: And server functions is inside curly braces, it's a named export.
JASON: There we go.
DEV: And one thing we need to add, we need to add a new router. Not an object. But just at the bottom of the array, just say server functions .router. So, this is like a full -- dot router. It probably does have type diffs. This is a fully-built router that the package just provides you. And this is another nice thing you can do. Now we have server function support. Wait, can you scroll down? Did you -- so, that router is a function. So, you need to call that.
JASON: Okay.
DEV: So, every time like a package like this give use a router, you can expose additional configuration.
JASON: That makes sense, we can change how it configures things?
DEV: Yeah. But now we can just -- now we have use server. So, we can instead of like reaching to an API endpoint, we can just mark a function as use server, use server code in there and we can call it a React app.
JASON: So, if I do this --
DEV: Not this way. Just a function.
JASON: Got it, okay.
DEV: So, you cannot like have components from your user. We don't have server components yet.
JASON: Got it. So, I do something like this --
DEV: Mark it as use server.
JASON: And then we could just follow the same little bit here. Will use server, and then down here, I'm gonna export function, get posts. And run this same async --
DEV: Async, yeah. Just put console log here so we know that it's running on the server. Yeah.
JASON: Okay. So, this is there.
DEV: And this is inside our useEffect instead of the API endpoint.
JASON: Okay. And so, we're going to data here --
DEV: Yeah.
JASON: And we're gonna await get posts.
DEV: You might need to rename this function here because we have two get posts now.
JASON: Oh, right, right, right. Let's...
DEV: Sure.
JASON: Okay. And then we should hopefully get autocomplete there.
DEV: Yeah.
JASON: And lest try it again.
DEV: Let's see if we get our posts.
JASON: There it is, cache and the API.
DEV: We do, and do we see our console log.
JASON: Yeah. So, here's the frontend. Let me clear, reload.
DEV: No console log here.
JASON: There is a console log here. So, we're server side.
DEV: In two lines of code, we have use server. We have this capability that maybe two other frameworks have.
JASON: Ah, that's pretty slick. That's -- that's actually really impressive that just works like that. And this is, you know, and this is kind of exactly how I would want to think about it is if I want to extract something out, because I know that, for example, this call is, you know, running on the same servers that my backend is deployed to, that's gonna be a much faster API call.
DEV: Yeah. Or you can just like talk to database in here which we can access process.env, anything that you want.
JASON: Good point. Very good point. Okay. Yeah, yeah. So, if we need secret credentials or things like that, that would be very, very cool. Can you bring a framework like Nest? Sounds like there's no rules, right?
DEV: Yeah, you could. I don't see why you would. But yeah.
JASON: Fair.
DEV: I think it's also that we are kind of using Nitro, which is almost an alternative to things like Express or Nest. So, like Nitro gives us all the capabilities that we want from Nest. Really the only thing that you would want from Nest here is kind of its dependency injection mechanism. Which sure, you can bring that in. But you won't necessarily need its own HTTP runtime, you would need all the libraries that come with Nest.
JASON: Got it.
DEV: This acts as our framework, but all the other capabilities just become libraries.
JASON: Yeah, slick. Very, very cool. Okay. So, we got -- so far we have been able to get single-page app, React rendering going.
DEV: Yep.
JASON: We were able to get an API route. We were able to set up server side rendering of --
DEV: Server functions, yeah.
JASON: Server functions. What else? Now what?
DEV: We already have a router. We already have a simple React router. We can add filesystem routing and make this feel like a proper framework.
JASON: Let's do it.
DEV: Yeah.
JASON: I want to create a pages folder and I want to make this work. We'll call this index.tsx. And I want to export default -- or is it default? Does it matter?
DEV: Yeah. We can export default, that's fine. Because we are building the framework, we have full control over how and what we export.
JASON: Our export default, we'll do one of these, one of these, and we'll return one of those.
DEV: Yeah.
JASON: And I want to make that live. And I guess I got to import my React up here. Realizing now how spoiled I am by not having to do that.
DEV: Yeah. There was a time when we all had to do that.
JASON: Back in the day when you had to import your framework.
DEV: Yeah. And we were not using TypeScript so we didn't know that until we tried to run it.
JASON: Indeed, indeed.
DEV: So, the filesystem routeing is almost a third kind of router that we have. So far we have used two kinds of router. We have a Vinxi router which takes a set of URLs and points them to a specific JavaScript bundle or a JavaScript kind of application. Either a client app or a server app. And here is the handler. Here is the build system for it, right? Actually, while we're here, let's also quickly add a third kind of router which is a starter router which will -- let's say we have a public folder or an assets folder. We probably want to serve some sort of public images, right? And we can call it static assets or whatever. And I think let me quickly check this... I don't think we give it a handler. I think we give it like a base -- a directory, right? Dir. So, we give it a directory and we say that you can say dot slash public. Everything in the public folder is served statically. I wanted to quickly talk about this before we go to file.
JASON: And if I do something -- what would be easier? I've got some images in here, let's take one of these. And let's call it hat so I can type it faster. And now if I come in here and I say: Image source equals -- would it just be hat?
DEV: Yeah, I think so.
JASON: Do all equals hat. And we'll just set a width and a height so that it doesn't blow our entire layout. And there it is! Look at that!
DEV: Yeah. Assets folder, it's just a router. You can put in whatever you want.
JASON: Okay.
DEV: So, yeah, this was like one kind of router. Which is a it's pointed to a specific kind of server or an application or an assets router. And then we had our React router. This is what we can call the runtime router. Or a router that runs at request like -- at runtime, either on your server, which is going to look at your request method and like on your API server and decide what handler to call. Or on your React application, it's going to look at the browser URL and decide what React component to render. Right? Now we're gonna have a third kind of router which is -- which is like, okay, we are going to take this URL and point to a specific file in our folder, right? So, now we can import -- let's import from Vinxi/fs router. We're gonna -- we're gonna have a class here called base filesystem router. Yeah. So, we're gonna import base --
JASON: Camel case like this?
DEV: Yes.
JASON: Okay.
DEV: And we are going to create a class here. Class something extends base filesystem router.
JASON: Okay.
DEV: Whatever you want to call it, the router.
JASON: Call it extends --
DEV: Sure.
JASON: Base file system router.
DEV: This is our file system router, it's gonna have two path, yeah, two path. And then two route. So, the SRC here is the name of the file. Right? We -- what we need to do is take this file name and transform it into like a URL pattern that we can tell Vinxi that, okay, this is the URL pattern that's handled by this file.
JASON: Got it.
DEV: So, for that, we are going to -- should I copy/paste? We'll just walk through it. We need other info from Vinxi FS router called clean path.
JASON: Okay.
DEV: There we go. And we're gonna pass in the SRC here. And this dot config. As a second argument. So, we're gonna take like -- at some point this object is gonna have the configuration that we need. You're gonna pass this in and what this will return is -- like the SRC right now is the absolute URL to your files. Right? This clean path is gonna return, it's gonna remove all the file system stuff. For your index.tsx, only return slash index, instead of the whole filesystem dot tsx.
JASON: Got it.
DEV: From this, we are gonna do dot slice, remove the initial slash. Slice one, and then we replace index with an empty string because if it's an index.tsx, it's the path. So, we can replace. And yeah. Let's just -- they're gonna have one more thing. I'm gonna copy you this far, it's a weird regular expression. Can I put it in the chat here?
JASON: Yeah, should work.
DEV: That's the final replace call that adds a little bit of dynamic routing. The regular expression is just too much for me to talk about.
JASON: Okay.
DEV: It can remove --
JASON: Let's figure out what I did wrong there.
DEV: There's comments in there. Let me send it to you again. Because the comments didn't translate with the new lines properly.
JASON: Gotcha.
DEV: That should be better.
JASON: So, just dump all this, drop it in again. There we go, all right.
DEV: Yeah.
JASON: Now we're checking for if it starts with a slash?
DEV: So, we are basically like removing square brackets --
JASON: Oh, oh! So, this is --
DEV: Brackets for --
JASON: This would be for if we're doing parameters.
DEV: Got it, yes.
JASON: Got it. Okay.
DEV: The first one, dot, dot, dot, is a catchall route, we slice the first three, which is the dots and if it's not a catchall route, we put this is a route parameter. So, this sort of place expression just looks for square brackets. And if there are no square brackets, it doesn't do anything, right?
JASON: Got it. Cool.
DEV: Now we can return the path. But now we need to add initial slash to it. Yeah. So, this is our two paths. This is like probably the more complicated part. The second method, two route, so we're going to use the disc.two path to get that first. Const path -- yeah, pass the SRC and take the return value so this is our path. And return an object. If you're gonna use this object very soon. But we can pass -- we can create a property let's say dollar component. So, I'm kind of like copy-pasting what I have and what we're gonna be using soon. This is an object.
JASON: Okay.
DEV: And this is gonna have SRC. So, we just take that SRC and pass it back in.
JASON: Okay.
DEV: Yeah. And the second property is a pick. And this is an array. So, pick now -- pick now tells the bundler, it tells Vinxi, what -- what do we care about in the file? So, let's pass in a default as a string. This part gets a little weird. But now we are saying that take the default export from this file --
JASON: I got it.
DEV: -- and put it under dollar component.
JASON: So, we could, if we wanted to, we could make it like the -- you have to export a named thing --
DEV: Yes.
JASON: And that's what we would pick. If we wanted to grab a loader or things like that, we could grab all those pieces.
DEV: Yes, a second property that would be dollar loader and then you can pick loader. So, you can have as many of them as you want.
JASON: Got it, okay, okay.
DEV: And then we are going to have two more things in this object. Outside that. This component is done. The outside --
JASON: Outside is done.
DEV: Yeah. We're gonna have the path which we got from, and the file path which is SRC.
JASON: Okay.
DEV: Okay. So, that's pretty much -- this is a very basic like has a little bit of dynamic routing. And also, if you want to do -- so, for loaders and things, we configured the two route method. Right? If you happen to have named exports. And if you want to have things like layouts and pages like Next.js has a layout.tsx and page.tsx, you can do that in your route here.
JASON: Right.
DEV: You can have whatever file routing convention you want, as long as you express it in these two methods, you can replicate Next.js, Svelte Kit, Remix, NUX, whatever you want. It's all just configuration here and completely pluggable into what you want.
JASON: This is cool. To repeat this back, we're basically getting the file path here. Using a helper out of Vinxi to strip that down and get the immediate path, getting rid of my system file path and everything. We drop the leading slash. We swap out index for nothing because it's the default route. And then we check for any route parameters like, you know, square brackets ID or something.
DEV: Yeah.
JASON: And then we return that back. And than in the two route, we are saying based on this path, use the current component. Get the default export, that's what the component is going to be.
DEV: Yeah.
JASON: We could configure the loader and other things in here. And then we specify the path and where that is. Now to use this...
DEV: We can go down to our Vinxi configuration. We have our single page router, the router, we have type name, routes, plugins. We're gonna give it a routes property after. This is a function --
JASON: Oh... okay.
DEV: This function, yeah, it takes two parameters, the function itself routes. It has a router and an app. So, the router is this router itself and the app is the inter-application. And we can return new pages, I'm sorry, new file router and we just -- and this takes in three arguments. The first is an object. And then we just -- after the object, we just pass in router and app. So, as a second and third parameter. Right. So, now inside the object, you give it a directory. So, this is the directory where your file -- your file routeing lives.
JASON: And we're gonna call this source pages.
DEV: Yes. Now, this is expecting -- oh, we can also give it extensions. So, here are the file extensions that you need to look for. We only have TSX, but you can add JS, JSX, TSX, anything.
JASON: And is this globby, could I do something like that?
DEV: No, use path, has multiple strings. It might have lobby. The bundler part is something I don't fully understand yet.
JASON: Okay.
DEV: And you can remove the dot.
JASON: Remove the dot, it's just extensions, got it.
DEV: And for directory, I believe it looks for absolute paths. So, here we can import another helper from Vinxi called resolve. Yeah. From Vinxi, second one. And we can say resolve.absolute. That's the resolve.absolute is the method. We pass in SRC pages, and the second parameter, which is router.route. So, from -- like take the route at of -- why is it complaining?
JASON: What are you mad about? Expected two, we got one. That's a lie!
DEV: Now, that's complaining. Oh, you can just -- TypeScript there. Yeah. So, yeah, take the route of the current router, which is the spa router and go to SRC's pages and convert that into an absolute URL, right?
JASON: Okay. So, this is it?
DEV: That's the configuration. This is it for the bundler configuration. Now at runtime, we haven't changed our actual application code yet at all. Right?
JASON: Right.
DEV: If you run this, erg should still work as expected. As it was running up until now. And if you go to post, that's still there. Okay. So, now we have the runtime part. We need to add like one more -- we need to install one more package which is @vinxi/react which is again a small helper that Vinxi has built to make working with React a little easier.
JASON: @vinxi/react, got it. Okay.
DEV: And now we can go to our index -- our index.tsx file right in.
JASON: Here? Okay.
DEV: In here, let me quickly find it. Okay. So, new now we need to first -- yeah. So, from here, we are going to import lazy route. Because now we also want our routes to be lazy loaded. That was kind of like the big point behind going to filesystem router in the first place is because we were trying to lazy load everything. And we are going to import get manifest from Vinxi/manifest. So, we don't need to import anything from here. Yeah. You can remove like -- yeah. Those three. So, manifest is something that Vinxi builds after it's done building everything. And this is how you -- this is how like you request Vinxi for information about all the bundles that it has built. Right?
JASON: Okay.
DEV: And the final thing we need to import is file routes from Vinxi/routes. So, import -- it's a default export. Find routes. From Vinxi/routes. So, this is -- let's try this console logging file routes right here and see what we get. Yeah, let's go to our app. Pull up the console. Okay. Right now it's empty. Why is that empty? Hm.
JASON: Do I -- so, I do have like conflicting --
DEV: Oh, we did export something from pages, right?
JASON: We exported it, but we're not doing anything.
DEV: Let's go to our app.config. In the extensions, add JS, TSX -- add all of them because it might be some sort of a build issue. TS and JS. Let's see that if that works. Okay. There we go. Here's a route. Whatever like -- whatever we return from to route, we now have access to that at runtime. So, we can dynamically import that. We have like pretty much like whatever build configuration that we added there.
JASON: Oh, fascinating. Okay. So, then I have my -- let me get back out here. So, we have our file routes and our file routes give us that component and that component I could then insert wherever I want.
DEV: Exactly. Yeah.
JASON: Okay.
DEV: So, we're going to do one more thing with file routes. We're going to create a new like -- let's do const routes equals file routes.map. We're going to map it over. And get something out of it. Let's return -- yeah, let's return an object.
JASON: Okay.
DEV: So, it will have everything the same -- it will have like you can do a dot, dot, dot route.
JASON: Whoops, this one.
DEV: But all we got -- that's it. Yeah.
JASON: I'll get there eventually.
DEV: And what we want to do here. Cool. And we want to have a component property here which will have the actual component. So, component without the dollar sign. And here we're gonna use lazy route here from React. So, now we want to take like the dynamic import function that we have and turn it into an actual React component. It expects two more -- it expects two more parameters. The second one -- it just wants access to the manifest. So, the second is going to be get manifest. That be imported from Vinxi manifest. And we pass in client as a string. So, get manifest is like you can request the manifest for any single -- any router that you have. So, the client is what we named the router in configuration. Right?
JASON: Got it.
DEV: You can pass get manifest client again. Lazy loading is to be used during SSR, you give it the client and the server manifests, but we don't have SSR yet.
JASON: All right, I'm with you, we're going only -- now we have the routes.
DEV: You can have it in the application like you can do routes.map and you can have these route components from it.
JASON: Oh, I see.
DEV: So, you have slash -- now, let's -- yeah.
JASON: Okay, okay. So, I would want --
DEV: Curly braces, yeah.
JASON: Routes.map, route.
DEV: Yeah.
JASON: And for each of these, we're gonna do a route path equals, and it's gonna be route.path.
DEV: Yep.
JASON: And then the component --
DEV: And the component is route.component.
JASON: Oh, that's slick.
DEV: Yeah.
JASON: Okay.
DEV: So, the reason we did the other dot map outside the component is because if we do it inside the component, it's going to run it every single time it re-renders which obviously we don't want. But yeah. So, we mapped all of -- all of our like build outputs to a React component. Lazy route just uses React.lazy under-the-hood.
JASON: And so, now we can do --
DEV: Yeah, you can add all the links.
JASON: We'll do route.path. What we don't have that we didn't figure out is what we would actually call these. So, we would have to put that into our setup.
DEV: Yeah. And that's another thing that, okay, maybe if you want autogenerated navigation, you can just export a name or a title from your page and you can use that here. So, you can like build all of these fancy things in your --
JASON: Yeah.
DEV: -- in your framework. But right now you only have slash. And we can just start adding new routes.
JASON: Did I save this?
DEV: Um... k.
JASON: Let's.
DEV: There we go. So, now one more thing that we need to add is we need to fix the route matching. So, the way router does this route matching is different from how Vinxi is gonna do it. So, we need to wrap our routes with the top level router component. That's kind of like it's like a context provider. We need to actually wrap the entire application because links need to be under it as well. So, both the links and the routes. So, in router, this is an optional component. If you're using React router, you are going to need to add this.
JASON: Right.
DEV: But router makes it optional. And we need to pass it a parser. We need to create like a parser function. I'm also going to like just put that in the chat here for time's sake. But basically, what we are doing is we are telling router that here is how you're going to match your routes. And you can import path to RegExp from path to RegExp. It's already installed. Yeah, second one. Right now. You can add an import at the top, it probably doesn't recognize it. It should be from --
JASON: The router.
DEV: No, it's from path to RegExp.
JASON: It's from path to RegExp?
DEV: path.to-regexp.
JASON: Is that a peer dependency of Vinxi?
DEV: Yeah.
JASON: Got it.
DEV: Probably recognize -- yeah, just convert path to regexp. You can give it a way to map a route to an actual handler.
JASON: Did I do this right? Just passing it in as the parser?
DEV: Yeah.
JASON: Okay.
DEV: Now that about should work.
JASON: And it does. Got to figure out what I did here. It works on reload.
DEV: Do you have something in the console?
JASON: Component suspended while responding.
DEV: We need to wrap it with suspense, of course. We are using lazy loading. Wrap the whole top level with suspense.
JASON: The whole top level?
DEV: Yeah. I keep forgetting about that. Because the lazy React functions using React.lazy under-the-hood.
JASON: Got it.
DEV: We need suspense to handle its throwing a premise.
JASON: This is dope. So, we have been able to put quite a bit together in here, and we could even do, you know, we could do something -- I don't know if we would want to do this, but something like this.
DEV: Yeah. You have like both file routes and config routes if you want.
JASON: Yeah. And then I could do custom.
DEV: Yeah, that also works.
JASON: Wow. So, you can -- I mean, you could cause yourself a lot of pain with that sort of thing. But like it is pretty dope that this just works.
DEV: Yeah. And we can like replicate that with our API routes if we want. So, that we also get the same file based routing for API endpoints instead of just the frontend. But we don't have to do that right now. Like it will look very similar. Like we import. We got the file routes from Vinxi/file routes. And we just need to transform it over like in this case we want to pass it to lazy routes so that we can make it a lazy loaded React component instead of just a lazy import. And then we can just build our router out of it.
JASON: Really, really slick.
DEV: And again, this is completely decoupled from what library you're using. Here we're using router, you want to bring in tan stack router, React router, anything else. It down matter.
JASON: It doesn't matter because we're just passing in a component.
DEV: Yeah.
JASON: It's really, really nice. Dev, that's all the time we have for building today.
DEV: Yeah.
JASON: I have a million --
DEV: File routing takes a bit, yeah.
JASON: But this is slick that we were able to get this done. But so we see here that there is a possibility to set up SSR. You mentioned that you can take this further, get React server components going.
DEV: Yeah.
JASON: Anything else that we can do with this that we didn't already talk about?
DEV: Let me quickly add in two more -- two links here if I can find them. There we go. So, I have a couple blog posts here that someone -- another person who like -- who was building with Vinxi or excited with Vinxi, wrote about how to write a React framework, and has SSR and components. Because we weren't able to get to that here. But what he doesn't talk about in the blog post is like file routes and API routes and the stuff that we did. So, I think it's like a good -- like jumping off point from where we are leaving off today.
JASON: Yeah. This is -- this is very cool. And also, coincidently, I've known Brenly for years and years and years. It's cool to see his work coming in here. All right, y'all. This was very cool. I'm gonna go ahead and concur with fuzzy here that this was a one of a kind stream. I don't believe how much we got done. Just kind of looking at -- I know at the end we pulled some of the stuff outed too the file-based routing. But this was really cool that we were able to build all of this, what? We probably wrote -- here's 45 lines of code there. We got another 20 -- just under 200 lines of code, right?
DEV: Yeah, and a lot of it is already like -- it's kind of unnecessary because things like the SBI router, the API router, a lot of that can -- a lot of this code exists in every single Vinxi framework. So, Vinxi actually abstracts this out. It has its own kind of set of libraries that you can use to just bring in an API routers or bring in like API endpoints with filesystem routing. Like how simply we got server functions running you can get that same experience for API or React or even Remix for different filesystem conventions. All of these aspects of a framework, you might think, this is inherent to this framework, in a Vinxi world, it just becomes a library that you bring in. A plugin.
JASON: That's pretty cool stuff. All right. So, let me do one more quick shoutout. This episode, like every episode, has been live captioned. So, I'm just gonna throw that link up there one more time. This is where you can go if you want to -- if you want to see those. That's been done by Amanda who has been hanging out with us all day, thank you so much for being here, Amanda, from White Coat Captioning. That's sponsored by Netlify. Thank you so much, Netlify, for making this show a little bit more accessible. We have been hanging out with Dev. Go and give Dev a follow if you want to see more of those. mhuggins asking if I'm gonna refactor LWJ? Absolutely no. But this was a lot of fun, and I see opportunities to play. Let me drop a link to Vinxi in here in the chat. Another shout out to Nikhil for putting this all together. And one last note I will make, while you're checking out things on the site here go look at the schedule. We have so much good stuff coming up, I will be back on Tuesday, probably doing some live coding. Out next Thursday because Chain React is happening and I'm gonna be at the event. The week after that, though, we learn how to put videos behind auth. I'm really excited about this, it's a user-only video view their you can build on to your website. Really excited how that works. And then bring on the man, the myth, the legend, Matt Pocock, and Sara Vieira is coming on. That's up on the website soon. If there's something you want to see on the show or learn about, join the Learn with Jason Discord. There's a button that you can tell me who you want to learn it from and what you want to learn. Dev, thank you so much for hanging out with us. Any parting words from anybody before we call it had one done?
DEV: Go build your own frameworks and experiment with capabilities and your own opinions instead of buying into packaged opinions. It's fun.
JASON: Yeah. I think even if you're not gonna use it for everything, go and learn how it works. Because I feel like having that understanding of how all these pieces fit together makes such a big difference in your ability to get -- you just do better work with the tools that are already packaged up for you. Dev, this was great, thank you so much. And a little teaser for everybody. You're gonna see Dev again soon on my channel. So, stay buckled up. And we will talk to you next time. Later, y'all.
Learn With Jason is made possible by our sponsors: