Build a custom code editor using the WebContainer API
It's mind-boggling what you can accomplish in the browser these days. Gabriel Daltoso joins to teach us how to build a simple code editor using the WebContainer API with StackBlitz.
Links & Resources
Click to expand the full 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, boop crew. And welcome to another episode of Learn with Jason. Today on the show, we have Gabriel Daltoso, Gabriel, thank you for joining us. How are you doing today?
GABRIEL: I'm really good. I'm really excited about it. Thank you, Jason.
JASON: Yeah, I'm super-excited. This is something that I've had a lot of interest in, but not any time to learn. But before we talk about what we're going to learn today, let's talk about you. For folks who are not familiar with you and your work, do you want to give a background on who you are and what you do?
GABRIEL: Okay. So, I'm Gabriel Daltoso, right? And I am actually a frontend engineer at StackBlitz and I have been on frontend, I don't know, for the last 8 years. Around 8 years. And -- yeah.
JASON: Very cool. Yeah. And before we got on, you were also showing like you're the creator of React 95, which is the kind of Windows 95 emulator for React which is very funny, very cool. And, you know, that's like -- what -- so, I have a question for you actually. What got you into trying, like -- where did that idea come from if what inspired you to create the -- a Windows 95 theme for React?
GABRIEL: Well, this is a fun story. I was hired for a company to build their design system. But I didn't know how to do that. Like, I --
GABRIEL: I had no clue how to do it and where to start. And to -- I don't know. To learn and to understand more about this ecosystem and packages and build and a lot of things. To shake -- oh, my goodness, so many words in this field. I was like, I need something fun to make. To make me -- to motivate me, you know? And then, of course, this -- this React 95 isn't used by the company, you know? But --
JASON: Sure, sure.
GABRIEL: But I used it to learn. And then I built the -- their design system. And I worked on -- with design systems for about 4 or 5 years then. So, React 95 has a really good context for me. And yeah. It give me a really good background in this field.
JASON: You know, that's actually like -- that's something that is very year and dear to my heart is this idea of when you have something that you want to learn, it's such a slog if you just try to sit down and do it in the most, like, raw academic sense, right? But if you try to find something that makes you smile, that's fun, that makes you laugh, it's so much easier to stick through that learning and to actually get over the hump of making it work. Because you know -- you get this like huge payoff of: Look at this funny thing that I built. I had a similar thing. I was trying to learn how ESLint rules work. Where I made one called ESLint Reply Guy. It was a set of contradictory ESLint rules, it would tell you you were wrong, and you changed it, it would tell you you were wrong again and change it back to the way that it was before. That kind of thing. I never would have sit down and built an ESLint rule. I had this idea of a reply guy and I was able to stick through that with the learning. Has that been a pattern in your career? Have you found fun projects to learn throughout your whole life span as a Dev?
GABRIEL: Yes. And I do tell people to do the same. Because it worked so well for me that I keep doing it. And it's good when you -- when you saw your code. And it's fun. You know? And when it runs, and you can share with someone. And the people that saw your work would also have fun with that. So, it's kind of makes you much more like, oh. Oh, I want to keep doing this. Because -- yeah. It's been good.
JASON: Yeah, no. I think that's -- that's great. Okay. Well, so, speaking of fun, we're going to be building a code editor today, right? We're gonna kind of figure out how to build one from scratch. And along the way, we're gonna learn about how WebContainer API works which I'm really excited about. How about I let you explain it. Can you give a quick overview of what the WebContainer API is?
GABRIEL: I think the best way to explain what is the WebContainer API is start explaining what is WebContainer itself. You can see WebContainer as a small -- a small operational system inside your tab browser. It's -- it's just for you to have an overview what -- of what it is exactly. But it's not as operational system with all the --
GABRIEL: Things that one have. You have like inputs. You have outputs. You have a process and you have a lot of things that you could use. And for you to use, we've used this WebContainer API that could help you to make use of WebContainer.
JASON: Okay. And so the --
GABRIEL: That's the -- let's dig into it a little more.
GABRIEL: WebContainer is something that -- it's a technology from StackBlitz.
GABRIEL: And it is something that -- it is our engine, let's say. It's our StackBlitz -- in StackBlitz we use WebContainers to build and to run our code editor.
GABRIEL: And our WebContainer makes you able to run Node.js inside the browser, basically speaking.
JASON: And I think that's probably like the -- the big wow moment for a lot of us is that you've found a way to not, like, route Node.js back through a server somewhere and then kind of return the output to the browser, but you're literally running a Node app in the browser itself. Which means for somebody like me who wants to build a little proof of concept or, you know, try something out as a learning exercise, I can -- I don't have to start a local development server and I don't have to have a service that will run that Dev server for me in the cloud. I can just run it right in my browser and try out this Node package or library to see how it feels to use before I commit to setting up a local Dev environment with it. Is that kind of an accurate statement?
GABRIEL: Yes, it is. And I would just add that you can work offline. Because it is everything on your browser. So, you can just work offline.
GABRIEL: Yes. And for me, one of the big features that it came from in my mind right now is when I am working on something and we are sharing the project, let's say, Jason. And I ask you for help on something. And, well, you can imagine that I have a lot of changes in my project. So, I need to stash it somehow, you know? And to be able to help you and with WebContainers I can just click and open it on a new tab and the whole environment is set for me. And this helps a lot. A lot.
GABRIEL: I had to say.
JASON: It's very, very cool tech. And so, this is something that you -- you mentioned is -- this is how StackBlitz works. This is sort of the underpinning technology that makes StackBlitz function. So, it WebContainer something that is like proprietary or is this an open standard? Is this something that like, you know, can you kind of poke under the hood and see how it works?
GABRIEL: Our -- WebContainers is a technology from StackBlitz. And it's in -- heavily in development right now. And because we work close to our community. So, everything that we add to the WebContainer is because we got feedback and how the community is using. So, right now I think the WebContainer itself and its API is in closed repository.
GABRIEL: But yeah. You can use it for free for projects.
GABRIEL: It -- it can show the power of WebContainers really well. Like we can have -- we can give some inputs and also how to listen to changes from the WebContainer and how to show these changes somewhere in your application. You know? And I think this -- this is really a good example on how we can use WebContainer.
JASON: Excellent. All right. Well, I think that is -- is good a time as any to switch over and actually start building because I cannot wait to see this in action. I think it is such cool tech and I just to want play with it. Let me switch us over into the pair programming view here. And we are currently... what's happening? See? I knew -- this thing is being weird. I built this new feature and like it kind of works sometimes. But it... and then it just goes away after a while and I don't know why that's happening. Anyways. Yes. So, we are talking to Gabriel Daltoso who you can find on Twitter @gdaltoso. And this episode, like every episode is live captioned. You can see the closed caption button, we have Amanda from White Coat Captioning here today taking down the words, made possible through the support of our sponsors, Netlify, Nx, New Relic and PluralSight all kicking in to make this show more accessible to more people, which I very much appreciate. Gabriel, if we're ready to go here and I want to build this editor, where should I start? What's the first thing that I should do?
GABRIEL: You can start whichever environment you would like to. But I would strongly recommend you to use StackBlitz to do that.
JASON: Okay. So, I'm gonna head over to StackBlitz. And sign in with my GitHub.
JASON: Okay. So, here we are. I am in StackBlitz and I'm going to assume I need to create a new project?
GABRIEL: Yeah. Yeah.
JASON: Okay. What should I -- what should I use? Like I'm just gonna click through these so that people can see how many options there are. So, a ton of frontend options, a ton of backend options. You want to go fullstack, they have the fullstack frameworks, you want to go Vite, they've got all the flavors. Different doc setups is very cool. Oh, GSAP stuff with the different -- so, like GSAP with React, GSAP with Vue, GSAP Svelte Kit. Mobile setup. Oh, nice. Mobile setup is cool. And plain old vanilla whatever you want. Where would you recommend we start?
GABRIEL: Should we start with the -- the zero environment possible? The most zero environment? Yeah?
GABRIEL: So, I would -- I would ask you for us to use Code Flow instead. So, on your URL could you like append -- I will show you here. Let's say you can append the URL.
JASON: Just one of -- whoops. One of these? Like that? Like that? Nope. Wow. Not like that. Like this.
GABRIEL: Let's see? Yes. Is this new for you?
JASON: Okay. And there we go. We got an empty project.
GABRIEL: Yeah. We have an empty project with a WebContainer powering our VSCode editor. Good?
JASON: Yeah. So, this is just VSCode in the browser?
GABRIEL: Yes, this is just VSCode in the browser. We can -- we can like -- this is an empty project as you would have in your machine. So, at this point, we can just start with -- I don't know. You can open the terminal.
JASON: And all my shortcuts work. I love it.
GABRIEL: Yes. It all works. So, let's -- let's start with just plain npm init to give us --
JASON: Okay. Npm init. Okay. And we'll say simple code editor. No is fine, we'll leave the description blank. Do I need to do anything with the entry point or keep it as default?
GABRIEL: No, keep it as default. Good.
JASON: Okay. We'll deal with the rest of that later... okay. So, we have our basically initialized, simple npm project.
GABRIEL: Yeah. So, and then let's -- let's install two dependencies to help us a little. So, let's start with Vite. So, npm Vite-d.
GABRIEL: And then the --
JASON: Vite --
GABRIEL: WebContainer API itself.
JASON: Okay. What's the package name for that?
GABRIEL: It is @WebContainer/API.
JASON: There it is.
GABRIEL: And Vite, I think, I don't know if everyone knows what it is. But in this case, it will help us to have like a little server to help us like with all like reloading thing. And let's see if it -- if it is working. Okay? So, let's --
GABRIEL: Create a index terminal file. And let's -- yeah? Great. And let's add in the script tag.
JASON: Script tag.
GABRIEL: Type module.
JASON: Oh, type module, got it.
GABRIEL: Type module. Yeah, let's just create a file for that. That module and SRC to index TS. So, let's edit it on other file. TS and then create another file. TS. And I don't know --
JASON: Let me see, this one...
GABRIEL: Yes. And in our TypeScript file. So, in our TypeScript file, hello world message. Just something to know if it is working. So, now we can --
GABRIEL: Yeah. We can now type NPX Vite. And yeah. It's not working. You can open it on a preview or in your browser.
JASON: All right. Can I open up a new tab?
JASON: Oh, wait. I'm doing something wrong.
GABRIEL: No. It's all good. We just need to check the DevTools. Let's see --
JASON: No, this one -- no, I know what I did wrong. I tried to just open up localhost directly instead of using the live tunnel. And so, then if I go into my DevTools...
GABRIEL: Yeah, hello world.
JASON: There's our code!
GABRIEL: Yeah. It is happened, right? So, we have our Vite server running. And now the fun happens -- starts. Right? So, let's start. We can -- we can start it by simply getting our -- our WebContainer instance created as soon as we got the -- the window load.
GABRIEL: Okay? So, we need to import our WebContainer from WebContainer API package. So, yeah.
JASON: Is that a top level or a named?
GABRIEL: Yeah, named. WebContainer, yeah. This one. Good.
JASON: Autocomplete is working in the browser. Ah, this is nice. It feels just like working in my editor locally.
GABRIEL: Yeah. Good. And then on our -- when the window load, we can start doing things. Window load...
JASON: OnLoad, right?
GABRIEL: You can add an EventListener also.
JASON: Yeah, that's probably a better call. We'll do a load.
GABRIEL: Load and then -- yeah... yes. And then we need -- when your page will load, we need to boot our WebContainer instance. Okay?
GABRIEL: So, let's create a variable to hold this -- the instance for us.
GABRIEL: Yeah. And let's -- let's call WebContainer.boot.
JASON: Web -- oh, boy -- container.boot.
GABRIEL: Boot. Yeah.
GABRIEL: This will work. But "boot" is an async function. So, we need this load callback to be async.
GABRIEL: Async. And I'll wait until the boot happens. So, yeah. So, just see if it works. So, let's just add a log like loading and booting, before and after.
JASON: Oh, I gotcha. Loading and booting. It says, loading and then it doesn't --
GABRIEL: This is good. This is happening for a good reason. You know?
GABRIEL: And this is also a thing that I would like to explain to you more. A couple of details here and for everyone who is listening to us. Well, WebContainer to run, it needs to run in the secure environment. You?
GABRIEL: The -- it runs in -- you can check the elements on the page, Jason, for a second.
JASON: Okay, we've got --
GABRIEL: Yeah, we've got --
JASON: An iFrame.
GABRIEL: The WebContainer instance is running magically in this web iFrame element.
GABRIEL: So to make this happen, we need to isolate everything into a really trusted environment. And to do that, we need to set some -- some configurations in our Vite server.
JASON: In Vite, okay.
GABRIEL: Yeah. On our Vite. To do that, we need to create a Vite config file. So, it's Vite.config.js. And we need to import define config from Vite. Define config, yeah. And export the configuration -- the full configuration, export the full, define config, and let's add some configuration there. We need to config our server to have some predefined headers to it. And I believe it is easier for me to send you those headers. Yeah? Send to you. So --
JASON: Okay. Here are...
GABRIEL: And to explain a little what those headers -- what they do. They let's say I have a book, Jason. And I like it really a lot, that book. And I would like you, Jason, to read that book, you know? I -- there is two ways for me to share this book with you. Like, I can open the page -- all the pages for you to read. Or I can lend you the book. You know?
GABRIEL: But I can lend you the book if you are a friend of mine. And also, you can see the pages if you are a friend of mine. So, those --
GABRIEL: Is doing that for us. Like our application is holding the book to the WebContainer. You know? So, they can --
JASON: Got it.
GABRIEL: Talk to each other.
JASON: Got it. Okay. Great. So, basically, what we're telling Vite here is that the -- the scripts we write inside our application should be allowed to operate inside of the iFrame that is being generated by the WebContainer.
JASON: Okay. Okay. Makes sense to me.
GABRIEL: Yeah. And I think that for this change to -- yeah, we need to restart, yeah, the Vite server.
JASON: We are restarted.
GABRIEL: And now loading and booting.
JASON: Loading and booting -- yeah!
GABRIEL: We have it -- easypeasy! Good. So, I think for now that we have our container. We can -- let's spawn a comment. Could be --
JASON: Say that again?
GABRIEL: Can we spawn a comment?
GABRIEL: Yeah, comment. Like let's -- you can spawn like a process.
GABRIEL: Into our instance. So, let's do -- you said container, right? You created it as a container. So, spawn...
GABRIEL: Yes. And it has two arguments. The first one will be the comment that we want to spawn, which is like --
GABRIEL: Let's say we want to check the Nodes version. So, let's type Node. And the second argument is everything that we would like to pass to this comment, you know? So, slash Vite. Good. So, I can say to you, Jason, that this is working. But we need to see that working, right?
GABRIEL: So, the -- the spawn function. It will return a promise that we need -- yeah. And we need to -- some ways to handle it.
GABRIEL: Could you declare a variable? In the way for that -- yes. And if something bad happens with this promise, we need to say, like, well, something bad happened and let's go away from here. We do this not by using catch. But using --
JASON: Got by using catch.
GABRIEL: Not by using catch, but using response.exit. So, we just need to check if process is .exit, it's like sure thing, you know? Every process in Node --
GABRIEL: Will return zero if right. First is exit --
JASON: I got it.
GABRIEL: Is also promised. Yeah. Yeah. So, if --
JASON: Is it called like that?
GABRIEL: No. You don't need to call it.
GABRIEL: Yeah, you're good. And if the return from this process returned anything like rather than zero, we need to handle that in a proper way. But let's consider that we -- we have and we know our application very well, right? So, let's just... handle -- yeah. That's good. That's how we should do it.
JASON: This is -- this is how I do error handling is I just say, we'll do that later.
GABRIEL: Good. Yeah. Well, when we spawn a process to our instance, it isn't a async thing. We don't have the return from the process. Like all the chunks we need to show. You know? It's an open string of, well, the returning processing that we -- WebContainer --
GABRIEL: Is doing. So, to get those responses, we need to pipe our -- the output that we got from response. So, we do it -- we do it by simply the response.output. Output. This is a string. So, we need to pipe to... .pipe to, yes. And a new writable string.
GABRIEL: And this writable string, we can add -- yes. We can add a object as argument to it. Saying, when we write -- right, let's say -- we have a function that every chunk that we received we need to send to console log.
JASON: Okay. So, we'll get a -- like the first thing we get is a chunk?
GABRIEL: Yes. It's just the data that's coming from the WebContainer. And we log that chunk.
JASON: Oh, it is not a constructer. What did I do wrong? Loading, booting... okay. So, I missed something in here somewhere. Output pipe to...
GABRIEL: Let's see... and response and output... write a string --
JASON: Immutable string... okay. And then we pipe to...
GABRIEL: New --
JASON: Right. You know what? Let me just stop and restart it in case I did something wrong here.
JASON: No, it doesn't like -- so, I've made a mistake. The mistake that I have made. I'm gonna just try... okay. So, we're getting an error in here, it looks like. So, the way that I handled this... okay. So, that part works. But when I try to handle the error, it fails.
GABRIEL: Oh. I see.
JASON: Do I need to do one of these? There it is!
JASON: I forgot to wrap this await. So, it was trying to --
JASON: Okay. Okay.
GABRIEL: Okay. Good. I think we don't need that equal, equal zero. We just need to -- every number that comes -- yeah.
JASON: Oh, I guess that's true. Because anything that's not zero would be --
GABRIEL: Yes. So, yeah --
JASON: Okay. Great.
GABRIEL: So, this is --
JASON: The Node version, we're using Node version 16.
GABRIEL: 16. Yeah, that's a really good version also.
JASON: It's a good year.
GABRIEL: So, now we can -- we have like -- we know thousand spawn process into -- into our WebContainer. And we could do it like... let's -- let's refactor this. And turn this into a run comment function. Okay?
GABRIEL: So, we could do this by -- let's -- inside this event -- load event, let's just create another function like -- function run. You know? Function run. They will -- the run function, we will wrap all this error and pipe to a thing. You see?
GABRIEL: So, we can -- the run comment function, you accept the same -- the same args as we have in spawn. So, we have comments --
JASON: Same --
GABRIEL: Comment and args. Args, yeah, good.
GABRIEL: We can have our call container outside of it. Because our run --
JASON: Okay. So, the container outside.
JASON: All right. And we'll just keep these up here as a good indicator of what is going on.
GABRIEL: What is going on, yes.
GABRIEL: And let's try it now, yes. Let's refactor this. Let's add comments and args instead. And let's -- let's run our run function -- yeah. With just, well, we could show the same -- same functionality like showing -- yeah. Node version. Good.
JASON: Okay. Just make sure that it does all the same thing.
JASON: There it is.
GABRIEL: Good. Now that we know how to show -- send comments and instructions to our WebContainer instance and get the data from it, we can start doing our code editor. Why not?
JASON: All right. Let's do it.
GABRIEL: Let's do it. We can do it. So, we need to populate that index.html a little more. We need -- do you have new idea on how you would like to have it on your screen? Like you would like the editor to be in the left side... what would you like to do?
JASON: Sure. Yeah. Editor left side sounds good to me.
GABRIEL: Okay. So, let's start simple, then. With just an input and a send button.
JASON: Okay. So, we've got an input and we'll have a button that says "Send" and then we got to wrap that into a form. I'm gonna block this out and then you can kind of guide me through how you want to do the rest of this. Okay. So, we've got our -- we've got our form. Does this need to have a, like, post method, get method? Does it matter?
GABRIEL: Well, let's prevent the default behavior for now.
GABRIEL: Let's just have the input and the send button. We can attach some IDs to them. Just to make it easier for us.
JASON: Okay. Okay. And then is this one gonna be like a text input? Do we want it to be a text box? Or...?
GABRIEL: Let's start simple, right?
GABRIEL: In my head, let's start with just -- instead of the code counting itself, let's just send some comments right now. Okay?
GABRIEL: So, we can type Node.v into this input and send to the WebContainer.
JASON: Oh, I gotcha.
GABRIEL: Let's do it all in the same file. I think it would be easy to try it.
JASON: Great, yeah. Works for me. Okay. So, we've got our... I moved this down to the bottom so we can kind of see what's going on here. So, we've got our form up at the top. And we're going to... I guess we can --
GABRIEL: We can get rid of this too. So, let's create a div as output.
GABRIEL: Div, yeah, an ID. Output, yeah. Sounds good. Or even better, let's create a code instead of div.
JASON: Use a pre so it stays level honest.
JASON: And then what we can do up at the top here is we can just say the output equals document.querySelector and we'll get the output. And then here we can do output.inner --
GABRIEL: Text content.
GABRIEL: Yeah. Works.
JASON: It's gonna be like a plus equals. And we'll do like... we can do the chunk. And then we'll just do a line break so that we can keep track of what's going on here. And what we should see -- there's that.
JASON: So, you have to --
GABRIEL: The version.
JASON: That's good.
GABRIEL: Yes. Now we can get rid of this. This is just the TypeScript complaining about -- yeah. Exactly.
JASON: A lie. This does exist. Why wont this exist?
GABRIEL: I don't think it exists in the element, probably. But yeah.
JASON: Text content, that's fine. Now what are you yelling at? I absolutely did not break this. How dare you? Booting.
GABRIEL: Yeah? We can see it.
JASON: Okay. And then if I make it stop yelling at me... that worked. I don't know. I must have saved with a typo in place. Okay. Now we've got like a basic output going here. And then the next thing that we'll need to do is we'll get the form. Document, querySelector, and then we'll say, editor.
JASON: And then... here I guess we -- we can just kind of -- we want to parse the inputs, right? So, we would --
JASON: Have an async function of handleSubmit. And that would -- I guess we don't need to do that, we can do a form.addEventListener. And make it on the submit. And we'll get the event. So, we can event that prevent default, and then we'll get the data... is event.formdata. Okay. Is that not it? Target.formdata?
GABRIEL: You mean the target, yeah. That's it. It's just the TypeScript complaining about -- it might be undefined. Yeah.
JASON: That off target... how about now? What? Event.target is not --
GABRIEL: So, let's cast. Event.target as HTML form element. Yeah? Support.
JASON: There grow. Now hush. Okay. Good. So, then, what we'll get is the command is data.get command --
JASON: So, that'll get us this value here. And then we need to what? We need to const -- or hold on. We called this --
GABRIEL: Just the first, yeah. The first word to be our comment and the rest to our args.
JASON: Okay. So, we'll do text.split on spaces.
GABRIEL: Yeah, we need to --
JASON: Split is not --
GABRIEL: The TypeScript doesn't see text as a string. Now it does.
JASON: Okay. Then we can await run command args.
GABRIEL: Boom! And then we need to async this callback, yeah. Good!
JASON: So, we'll get our Node version to start. And then we should just be able to send in additional commands. Don't you yell at me. I did this right.
GABRIEL: Maybe just refresh.
JASON: There we go.
GABRIEL: Yeah. So, let's --
GABRIEL: We can do echo LS, I don't know --
JASON: Yeah. Let's do echo -- oh, this is gonna explode this that's not gonna work. Let's do it like this. That's not right.
GABRIEL: It split. It didn't get the command.
JASON: Data.getcommand, that's correct... data.get -- I know what it is. We didn't give it a name.
GABRIEL: Oh, the name.
JASON: That should do it. Hey!
GABRIEL: Hey! So, let's go -- let's -- let's try to install something! We can do it. Yeah.
GABRIEL: We have a WebContainer.
JASON: Just install anything.
GABRIEL: Yeah. You can install anything.
JASON: Okay. So, is npm like pre-installed here?
JASON: Okay. So, we're gonna npm install Vite. [ Laughter ] Oh, my god, that's cool.
GABRIEL: That's cool. Yeah. Let's install like a package just to print a cool thing for us and try it. Let's try --
JASON: Okay. Npm install Cow Say. There it is. Okay.
GABRIEL: And let's run with npx, Cow Say and, hi. So -- yeah. We have our dependency running.
JASON: This is good and cool.
GABRIEL: That's freaking cool, right? And then we have -- look at what we have right now. We have a form that we can submit comments to it we have a output. We could have a -- style it better, right?
GABRIEL: And we can work on that. But I was wondering... well, we can now have the -- you have installed Vite, right?
JASON: I haven't installed -- I installed Vite, yeah.
GABRIEL: So, we have Vite as our dependency. That's cool. Because we can start a server inside our WebContainer instance, right?
JASON: So, we're now doing Vite-ception. We're running Vite to run this WebContainer which is about to be running Vite...
JASON: I love it. I love it.
GABRIEL: That's cool. Well, let's try it. Npx Vite.
GABRIEL: There is nothing there. Like there's no index at all. There is nothing, no index, yes, as well. And as we can see, there's nothing, right? Because --
GABRIEL: The output from Vite is kind of different.
GABRIEL: And our instance handled it differently. So, let's add the right EventListener for it, okay?
GABRIEL: So, on our -- on our -- you said as a container, right? Container variable. So, we can listen to changes to server ready.
JASON: Okay. So, container --
GABRIEL: Dot on -- because we are listening to some changes for a specific event. Server ready. And we will have a callback function with two arguments.
GABRIEL: It will have the fork as the first one and the URL in the second one. So, for now, let's just -- let's just log the URL, right? As we have in -- in all extras project, right?
JASON: Okay. So, this should now console log --
GABRIEL: Yes. You see this URL? So, this is the WebContainer as you said, inception, right?
JASON: Yes. It looks like...
GABRIEL: For us to see that, we need to port.
JASON: I need -- sorry, what?
GABRIEL: The port.
JASON: Oh, the port! Oh, the port! Yeah, of course I need the port! Okay. So, to get the port, I'm going to log the port. And now we'll npm install Vite. And then npx Vite. Okay.
GABRIEL: And now let's try to open it.
JASON: I guess I should have formatted that. But... did that not just work? What did I just do?
GABRIEL: Is it working?
JASON: Here. There. And this is --
GABRIEL: It's working!
JASON: This is our empty, running, nothing in it Vite project.
JASON: This is too cool. This is incredible that this is just like working.
GABRIEL: Yes, it is. So, let's -- let's go further. And let's get this URL inside our preview instance. So, let's have our editor in our side. Let's now we need to -- to have our editor and our preview, right? So --
JASON: Okay. So, we're gonna say... we'll give this a section of like... output. All right? And then we've got our form. And then we have our div ID preview.
JASON: And then we'll close up that section. And then what we can do... is... let's... let's add a little bit of styling first so that this is usable. So, we'll say, for output, we want it to be display flex. I guess we'll go with display grid. And then we'll do grid, template, columns and we'll say, like, 300 pixels. 1fr. And what that should do for us is give us this editor on the left and our output on the right. And let's just double check if that works. We'll say... here. Okay. Yeah. So, there is our -- this is gonna be our output. Oh, but I broke --
JASON: Something -- oh! I doubled up this. This is gonna be -- call this interface instead.
JASON: We want this to be interface as well. There -- and now we get our terminal output at the bottom, we've got our editor on the left and we've got our preview on the right. And I need to create an iFrame inside of this div --
JASON: That has the URL.
JASON: We got this. We're going to set this up as an iFrame. And the iFrame is gonna have an ID of like Dev URL -- do I need any parameters or anything to make this work? Or can I just set the href on it?
GABRIEL: Yeah, we don't need. We don't need because we are going to set when the server is ready.
JASON: When the server is ready, okay. And so, to do that, I'm going to document.dot -- whoops -- document. Dogument is what Kent would write, Kent C. Doguments. We're going to document get query selector. And we'll grab the preview -- no. Dev URL. And then we're going to set attributes.
JASON: SRC and then we're gonna set the URL. if I can use the right pieces here. Colon, and port. Okay. Now, if I did that correctly... what ends up happening is once we start Vite, this should become our server. So, let's try it.
GABRIEL: You can try create React app.
JASON: Oh! I guess I could just do npm create Vite...
JASON: And it's going to create --
GABRIEL: Just to have something in our preview, right?
JASON: Yeah, just so I don't have to -- oh, man, I'm gonna have to create an index file or something. Okay. Oh! You know what we're missing, though? It's got interactive outputs. So, what's the -- do you know the command to make it take all the defaults?
GABRIEL: It's clear, right?
JASON: Is it... npm create Vite and then is it like do I have to put in dash yes? Or is it like this?
GABRIEL: Yes, yes, I think so. Yeah.
JASON: Okay. So, this should start Vite... and if we inspect that... okay. I did something wrong. Why don't we have it... I don't think I'm getting -- I don't think I'm finishing the command. Like, I don't think this create is actually working. Because I think we're getting hung. Like if I -- if I run this here?
GABRIEL: You need -- can you -- could you refresh and restart again? Yeah.
JASON: Yeah. Refresh... there we go. There it is. So, we've got -- our version is working.
GABRIEL: It's working. Okay. And now let's install a dependency.
JASON: Okay. And I'm installing Vite, right?
JASON: Okay. So, there's Vite.
GABRIEL: There's Vite and to make it run, we just need -- let's check the iFrame content before running the Vite itself.
JASON: Okay. So, the iFrame content right now is just empty. There's nothing in it. There's no...
GABRIEL: So, let's npx Vite. Yeah. It is working.
JASON: Look at that. Look at it go. So, then the piece that we need to figure out is somewhere in here there is a way to run that command. It's like create scaffolding your first Vite project... is there not a way to just say, like... do all the defaults? What if we tried... let's see... I'm gonna create Vite, maybe it will just show us. So, the dash Y should just work. But maybe I have to give...
GABRIEL: Oh, maybe the create doesn't work, yeah. Because create is a comment from npm, right?
JASON: Yeah. Let me -- okay. So, I'm going to start this again. And I'm going to say npm create Vite help, that should give us output. It does. Okay. So, then what I want to do... is I want to npm create Vite, dash, dash yes. Which should have... hm.
GABRIEL: Yeah. Let's try another one.
JASON: You think it will work like that? Yeah, okay. All right. Let's try another one.
GABRIEL: Yeah. We can try a create React app, which is...
GABRIEL: Known by a lot of people.
JASON: Npm create React app. And are you doing stuff?
GABRIEL: Is there a dash after create? I can't see it.
JASON: There is -- oh, no. Like this, you mean?
JASON: Okay. Let's try that again. So, we're sending create React app... and it's not -- I feel like all of these are interactive now so we need to way to like bypass the interactivity if we want that to work.
GABRIEL: Yeah. We will need to install, I think, first.
JASON: Okay. Let's see. Chat, if anybody knows of a create command that doesn't -- that we can run fully from the commandline with no interactivity, that would be super-helpful right now. Let's look at create React app.
GABRIEL: Oh, Sam is saying that we don't need the iFrame port in the URL.
JASON: That just loads a empty page and not the Vite app -- okay. NPX create React app. Then you move into the app and then you have to start it. This is what I was worried about. We're not really -- okay. Let's just try that. We're gonna say npx create React app, test. We're going to reload. We're gonna let this run. Here is our... did you miss it? Try that again. Loading, booting. There's our version number. So, we're gonna create the React app in test. I don't know how long this takes to run. So, we're gonna try moving into test. No such file or directory. Okay. And this got an error.
GABRIEL: Let's log the error.
JASON: Yeah, let's log the error. This is just the whole response? Or is there a...
GABRIEL: Yeah, I think -- yeah. Good.
JASON: The version just changed that... okay. So, this I think is doing a thing? So, then we can see begin the test. And it's just saying... I think this is just our failure. Like the directory doesn't exist because that create React app command isn't finishing.
GABRIEL: Yeah. So, let's... let's try Vite, maybe? Because it's faster. We should probably --
JASON: Yeah, maybe --
GABRIEL: The output as well.
JASON: What if we do a --
GABRIEL: It's about installing --
JASON: Yeah. So, that was npm install Vite. Okay. So, there's our output. And then if we run npx -- actually, what if we just do like cat console.log "Hi." Or we can do it is an echo. And then... we can put that into index.ts. Which theoretically would cause Vite to do something, right?
GABRIEL: That's our output. Yeah?
JASON: Okay. Then you have index Vite... and we look at our output. Not that output. We want our preview. That gets a container. And that container has anything in it? HTML, I had a body. Nothing shows up in stream. I don't know how we would get a... Dev URL. Can we get into the iFrame?
GABRIEL: Maybe we can open it in another URL?
JASON: Yeah. Let's try it. okay. So, this is running. But nothing is -- does it need an index.html. This is tricky.
GABRIEL: Yeah, this is tricky. Let's try one of our viewers said -- asked us to run npx nuxi. And we need to remove the port on the URL.
JASON: We do need to remove the port?
JASON: Okay. So, we're going to remove that port if I can find where I put it... here.
GABRIEL: In the -- yeah. Here.
JASON: If that's the case, then I don't need these anymore. Okay. So, we're saving. We are running the npx nuxi latest input. This should hopefully have some output. Hm.
GABRIEL: It should, right? Let's say --
JASON: It should.
GABRIEL: Let's go for the SRC URL.
JASON: Okay. So, there's no... nothing is happening here. It's running LS. All of this is dead. What's going on? K. LSA shows up nothing. Let's run this npx nuxi again. I don't know how long this should expect to take...
GABRIEL: Is this working, right? Like sending the comments to the WebContainer?
JASON: It is working. Because when I run this LS-A, this works. But I think something is going on with the -- the npx command or something. Like let's try... no, this is working. So, I think things are interactive. This is -- my guess is that it's asking us a question. But because we can't see it, we can't answer the question to finish the install. So, that's my -- my current guess is that. Maybe what we can do... so, I wonder if we can just like force it? Like what if we just do a echo and then in here we can put in the contents of like an HTML file. Right? We can do one of these. Just kind of get rid of all this stuff. And I guess I would do it as... am I gonna have to delete all the white space in this to make this work? What if... I can probably get rid of some --
GABRIEL: You don't need all of this. Like you can just add a div and we are good.
JASON: Can we just send in the script, do you think? Like would that work? Or do we need to wrap it in something?
GABRIEL: Let's try it.
JASON: Let's see what happens. So, we're gonna echo this. And we want that to go into -- and we'll say, index.html. Okay. And then we also want to run a console.log.
GABRIEL: Console or echo. Oh, I see. I get it. I got it.
JASON: Index.ts, and then we should, theoretically, okay. So, then if I run my LS... it does nothing.
GABRIEL: Yeah. Our -- loading, booting and then -- yeah. LS...
JASON: It's loading and booting. And it's -- appears to have broken the whole thing.
GABRIEL: Yes. Is it running the LS now? It doesn't --
JASON: Okay. So, if I turn these off... and I run my LS... what is going on? Well, that worked. Okay. Maybe it's not just doing... let's save this. Run the install for Vite. Okay. And we're gonna npx Vite. Okay. And now it's trying to get this working --
GABRIEL: We do -- another way to add to files to the WebContainer, just forget. So, let's do it.
GABRIEL: In a easier way. I can show you. So, go to our code and try get our container instance. And --
GABRIEL: Let's try to do container -- a container.fs which is the module that handles all file systems behind, yeah? And then we have our path. Much easier, right? And then, yeah. Just send the data.
JASON: Then we can send the data. Okay. So, I'm gonna grab all of this. I'm gonna drop this in here. I'm actually gonna undo all those changes that I made, oops. You want those changes, okay. Then we can drop this in here. And then I'm gonna do this one more time. Do I need to include the, like, the care set or anything?
GABRIEL: Yeah, it's probably better.
JASON: coding and we'll say UTF.
GABRIEL: I think the UTF8 is the default one. But yeah.
JASON: Better safe than sorry, I suppose.
JASON: So, let me grab this one and drop this in here. And then this one is gonna be index.ts. And it is going to have console log hello. Okay. So, those, then, should be here. And then we can --
GABRIEL: We can remove those files.
JASON: Check this one to an LS attache, so we can see they have actually arrived. And they have not. Do I need to put this inside the run?
GABRIEL: Yes, await. Await because it's async.
JASON: Okay. We're gonna await and await. Okay. Hey! All right! Then we can -- Vite. And that should -- that didn't work.
GABRIEL: Go with npm init first.
JASON: Okay. Npm init. That's not gonna work. Okay. So, we're gonna npm init.
GABRIEL: And slash -- yes. Yeah.
JASON: Okay. There we go. And in a little bit we're going to --
GABRIEL: Install Vite.
JASON: There is Vite. And we will npx Vite.
GABRIEL: Yeah, there we go.
JASON: There is our WebContainer. And you know what I didn't put in here is anything that would indicate that this actually worked. But we can do that knew --
JASON: Because we have... the technology. Okay. Let's save this one more time. This is gonna be it. This is the one, I feel it.
GABRIEL: This is the one, yeah. I'm feeling.
JASON: Okay. Do the thing, do the thing, do the thing -- please do the thing.
GABRIEL: Install Vite.
JASON: Install dives, npx Vite. Come on...
GABRIEL: Hello! Ha, ha!
JASON: Oh, yes! Okay. I love it. I mean, this is -- the like -- what I love about this is like the hard part was not getting this to run.
JASON: This container within a container. The hard part was -- was figuring out how to execute a dang install command.
GABRIEL: Right. Yes. And init was -- make the difference there. And installing the dependencies in the right way, yeah. And also, the dash yes. Yeah.
JASON: Oh, yeah, we missed a double dash in there to pass things through. The thing that's really exciting about this, though, is that with what we've just done here, like this -- this is incredible. Okay. How are we doing on time? Because -- let's check. We don't have a ton of time. We've got about probably 10 minutes of active coding time left. Is there anything you wanted to make sure that we highlight before we run out?
GABRIEL: The thing that -- I don't know if it is something that we will do here and right now, but for the listeners we have, you could create a repository in the GitHub. And to not lose those changes. You know? Right here we are just in a playground. Playing and hanging with each other. But it would be nice if we could, you know, commit and push changes to it. And we could do all of this using VSCode, you know. And well, the thing that I would like to also to say is, well, WebContainers here, it's usable. It's easy. The -- as you said, the -- the bigger problem here is -- was us. Me and you.
JASON: Yes. As usual, the primary problem with this application was the developer.
GABRIEL: Yes. And I think the thing that I like to point is also, you didn't ask for a token, you know? You just install it and start using, and yeah. You were ready to go. And -- and I think if you think it's -- it has been hard coding using Code Flow, tell us. Tell us because we need this to be easy for you. Because it must be easy, you know? We are developers and we like easy things. You know? And, you know, for example, we have container.fs.write file. So, this mismatches what we have in Node. You know? So, it will be a learning curve. It will be like less curve. Yeah.
JASON: So, I have a -- you mentioned the Git stuff. If I wanted to share this particular project with folks as kind of a starting point for somebody who wanted to play around, is this something I can do quickly here? Like would we be able to set that up in the next 5 minutes or so?
GABRIEL: Yeah. We just need to -- to create a repl and then sync with our changes.
JASON: So, in order to do that, I would just run a -- a Git init.
GABRIEL: I don't know if we have a way to create a repository using VSCode. Do we have listeners, an easy thing? An easy way?
JASON: Initialize repository. What happens if I hit that button? Whoa... what just happened?
GABRIEL: It will work. Try it.
JASON: I think I need to first create a new file. We'll do a get ignore. And we need to ignore Node modules. Okay. So, then that should make this -- that's more like what we actually expected to see. So, let's save all of these... okay. And we'll say... put after episode. And then is it connected to me? Add a remote. Add a remote from GitHub. Wants to sign in.
GABRIEL: Yeah, you need to allow it to -- changes...
JASON: Here's Learn with Jason... here.
GABRIEL: Two factor.
JASON: Why don't we just go with my personal so I don't have to. Ah, dang it. Okay? Where is my phone at? Crap. Down here. And then I'm gonna find this one-time code. GitHub... oh, wait. I could have just done it like this the whole time... there it is. Okay.
JASON: Trying to authenticate another GitHub account? That is incorrect. I'm me. Log in -- okay. So, let's try that one more time...
GABRIEL: You would check which user are you logged in? Yeah. In --
JASON: I'm not signed into any accounts.
JASON: Can I... sign in?
GABRIEL: Yeah. We just need to -- to check what's going on. I think it's on our side.
JASON: Okay. I'm gonna try this one more time and see if it will let me do it. Hm. No, I don't think I want to do that yet. How do -- yeah, I think I've got myself caught in a weird space right now. Okay. So, it -- we'll have to figure out how exactly that works. But yeah. Okay. So, I mean, this is like -- this is incredible tech. And so, to reiterate for anybody who wants to try this, you go to StackBlitz and then do a tilde slash, and that puts you into Code Flow.
GABRIEL: Yes, pretty easy.
JASON: I mean, this is some really, really cool stuff. Where should somebody go if they want more information, if they need documentation examples, anything like that?
GABRIEL: Could you, Jason, open WebContainer.io.
GABRIEL: Yeah. This is the house of WebContainer's documentation. Everything you want is there. But if you want a quick playground as we just did here, you could go like WebContainers.new.
JASON: Oh! So, WebContainers.new...
GABRIEL: And boom! You have the same as we did here --
JASON: Very cool.
GABRIEL: But it will more -- we have more features. Because in this example, we do have our code editor and prebuilt running.
JASON: So, this has got -- this has got everything that like if we had more time, we would have ended up here.
GABRIEL: Yes. Yes. That's it.
JASON: This is extremely cool stuff. So, everybody make sure you go and check out -- let me... WebContainers.new. Very, very, very cool stuff. And let me do another shoutout to you on Twitter. So, make sure you go and follow Gabriel for more information.
GABRIEL: Thank you.
JASON: Check out -- if you haven't tried it before, also go check out StackBlitz. It's -- it's very cool. It's got a lot of power, a lot that you can do with it. Anything else that people should be checking out?
GABRIEL: Yeah. So, you can follow us in Twitter as well to know every news about WebContainers and the process we do. And you -- we have a blog where we post everything we -- we do to make WebContainer be able to run on our -- on our -- on the browser. And it has a really, really good stuff there. So, I appreciate it for everyone to go there. Just it's blog.StackBlitz.
JASON: Oh, blog dot, I saw a link.
GABRIEL: Better. The last one is really cool. We have a benchmarking explaining why and how question npm -- how npm can run five times faster in a tab -- browser tab than your local machine. And yeah. I -- yeah. We will take a look.
JASON: That is extremely cool. All right, y'all. This has been a blast. Let's see... nope, still doing the thing. Anyways, yeah, this episode like every episode has been live captioned, Amanda has been here all day, and made possible through our sponsors, Netlify, Nx, New Relic and PluralSight. We have sponsorship spots opening up, if your company wants to be one of these illustrious four, please let me know. Because I need to keep the show accessible and that's how I do it. Gabriel, thank you so much for all of the time you spent with us here today.
GABRIEL: I appreciate it.
JASON: We're gonna go find somebody to raid, y'all. I think that's it. We'll see you all next time.
GABRIEL: Yeah, yeah. Bye, bye.
Closed captioning and more are made possible by our sponsors: