Svelte 3 with Rich Harris

John Lindquist
InstructorJohn Lindquist
Share this video with your friends

Social Share Links

Send Tweet
Published 5 years ago
Updated 4 years ago

This is a live stream rebroadcast with Rich Harris diving into Svelte 3.

John Lindquist: [00:01] Yeah, I didn't tell Rich exactly a topic to address. We're going to keep this free-flowing. There's no pressure to deliver anything amazing, or whatever. It looks like the first question is more about stores. Does the store example jump to the front of your mind?

Rich Harris: [00:28] I'm just bringing up the chat now. We can certainly cover the store examples that are in the tutorial, and go through those in a bit more detail if that was confusing to anyone.

John: [00:40] Yeah, that would be great.

Rich: [00:44] All right, let me bring up the tutorial and we can skip ahead to that chapter. Just to recap, the idea of having a store is that very often in an application, you're going to have some data which is relevant to multiple components.

[01:04] It's not just some local component state. It's not something that can easily be passed round as props. It's something that is a bit more cross-cutting than that. A good example would be some user data, for example, like the name and ID of the logged-in user.

[01:18] The way that this is done in Svelte is with a primitive called the store, which is really just an object with a subscribe method. The contract is very straightforward. If you have an object with a subscribe method and you give that method a function, that function will be called whenever the value of the store changes.

[01:37] Here is the writeable store tutorial. Is this big enough for everyone to read?

John: [01:45] Looks good to me.

Rich: [01:46] This is a simple application with a bunch of different components for changing the value of a writeable store. If I press these buttons, nothing happens right now because we haven't wired it up.

[02:01] The way that we create this store is by importing this writeable function from Svelte/store. Writeable is one of, I think, four exports from that file. We've got writeable, readable, derived and get. We just do count=writeable, and then we give it an initial value, in this case, zero.

John: [02:22] That's so awesome.

Rich: [02:25] Inside here we're calling this subscribe method. That gives us the initial value that we passed in, which is zero. Then we're assigning it to some local component state, which makes it visible down here. That's all it is. It's just a piece of local state like anything else.

[02:47] We can interact with that store from other components that also import it, for example our decrementer store could do count updates and minus one. Now, if I click that, it'll decrement.

[03:10] The update method just takes the current store value and returns a new value. That will be propagated to anything that is subscribing to the store.

[03:18] Similarly, in increment we can do count.update(n => n+1). Now we can go up and down. Then we also have a set method on writeable stores, count.set(0so now, =, -, and reset.

[03:40] That's all well and good, but this thing here is a little bit unwieldy, because we've got to have this whole subscription song and dance. We're not actually doing anything with this unsubscribe method. That is potentially dangerous because we could have a memory leak.

[03:57] This component is created and then it's removed. Then it's created again, then it's removed. We keep subscribing to the store, and then we're not telling it that we're no longer interested in that value. We run the risk over time, of developing a memory leak.

[04:10] We could do something with a lifecycle method. We could import onDestroy from Svelte. Then down here, when we destroy the component we just call that unsubscribe function. That's going to fix any problems with memory leaks.

[04:29] It's a bit boilerplatey, right? That's a lot of stuff to type, especially if you've got multiple stores in a single component. The reason that Svelte has this contract, this idea that subscribe will behave in this very consistent way, is so that we can do this instead. Just get rid of all of that and reference the count value by prefixing the name of the store itself with dollar, like that.

[04:58] It's exactly the same as before, but now the subscription and the unsubscription are taken care of for us. We can verify that is in fact what's happening by looking at the generated code. Go over to the JS output here in the record, and scroll down to where it's initializing this.

[05:14] It's actually using an internal subscribe helper, which handles the unsubscription as well. That's really all it's doing. It's just wrapping that process to save us from having to write all of that boilerplate.

[05:28] That's the writable store. That's the one that you're generally interacting with the most.

John: [05:34] The difference between a store and a component state. A store is the one you reach for when you have multiple components and you want to share data across your entire...

[05:45] [crosstalk]

Rich: [05:46] It's when you have data shared by multiple components, or it's when you want to express some domain-specific logic, which we can maybe get into in a moment. You can add custom methods to a store that will interact with it in various ways.

John: [06:02] Sweet.

Rich: [06:03] In fact, we could skip ahead to that right now. We could create a new function here -- export function create_count(). We'll do const store = writable(0In fact, let's destructure that, instead. Let's pull out the set, update, and subscribe variables.

[06:37] We'll return just a subscribe method, for now; count = create_count. You'll see that it still works, even though we're not calling writable directly here anymore because we're returning this subscribe function, and the subscription mechanism still works and this still behaves as you would expect.

[06:59] These don't do anything anymore, because we don't have a set and an update method on the store object. Let's fix that, but instead of using set and update, which are very generic methods that could apply to any data, let's have something that is specific to a counter store.

[07:16] We can have an increment method, which would literally just do update(n => n + 1), as we had before. We'll have decrement, same, - 1. We'll have that reset functionality from before as well, which just does set.

[07:42] Inside here, we can do count.decrement and get rid of all of that, count.increment, get rid of all of that, we don't need it anymore, and count.reset and get rid of all of that.

[08:01] Now we have the same functionality, but it's neatly encapsulated in this domain-specific logic. That's useful because you can wrap behaviors around stores. You can have persistence to local storage, for example. Things like that.

[08:21] What's also cool, which I didn't realize until John pointed it out on Twitter, is that the RxJS observable basically implements this contract. It's slightly different and we've tweaked the Svelte API so that it accommodates RxJS observables.

[08:38] Because, if you have an observable and you can call subscribe on it, you'll have the same behavior, you can use RxJS observables directly inside your components. All of the things that you would use RxJS for -- you can have very complex pipelines of asynchronous operations and all of that good stuff.

John: [08:56] That's been really fun to play with.

Rich: [08:57] It'll work quite idiomatically, which has been a nice, happy surprise.

Taylor Bell: [09:04] We had a question about swapping out a Svelte store with an RxJS behavior subject. Is that the same, but different?

Rich: [09:15] The way that you would use it is exactly the same. John, what would be the easiest way for me to import behavior subjects so that we could try that out?

John: [09:28] I'd have to pull up one of those examples I put on GitHub. You have to import the bundle.run package version, in here, at least, if we could find that GitHub issue. I guess I could revisit my Twitter timeline.

Rich: [09:59] I've found it. I'm going to recreate an example that John put together a little while back. Let's go over to the full REPL here and we'll just start a fresh application.

[10:16] First thing we're going to do is import a couple of functions from RX. We're going to have Ajax as ajaxMethods and the operators from -- and you would never write this in normal code but because we're in the constraints with the REPL, we have to jump through some hoops -- rxjs.umd.min.js.

John: [10:46] If this was on your local machine, NPM installed this stuff, this would be normal RX imports. This is just REPL-type imports.

Rich: [10:54] We've got some RxJS utilities here. Now we can create an observable. I'm told this is a slightly controversial convention, John, that we put $ at the end of the variable name. I've sort of inherited it from you.

John: [11:19] Yeah.

Rich: [11:21] I've sort of inherited it from you.

John: [11:22] OK.

[11:23] [crosstalk]

Rich: [11:26] This is going to get a list of GitHub users. We're just going to get the first five, and then we're going to pipe that into the pluck operator, which pulls the response property out of the returned data. Because we don't have any errors when you try and render this, we'll start with an empty array.

[11:53] Now we can use that directly in our marker like so. Prefix with a dollar because of Svelte, postfix with a dollar because of RX. Then, user.login. There we go.

[12:17] These are the OG GitHub uses, these are the original five.

John: [12:21] It must be user ID 04 or whatever.

Rich: [12:27] What's cool about this, about having all this stuff integrated is that we can use a reactive declaration in Svelte to observe this user's property whenever it changes by just doing console.log($users) like that. Now we can see what other information is on here. If we wanted to start adding avatars, if you wanted to link to the pages...

John: [12:57] That's so cool.

Rich: [12:58] then that's just all right there.

John: [12:59] I see you're still writing that OR operator with the console.log() following it. I love this, the label of the console.log(). That's awesome.

Rich: [13:08] It's nice and disposable because once I'm done with that, I can just get rid of it and it's just that.

John: [13:18] Could you refactor that to using fetch just so people are...the non-RX way of making...

Rich: [13:24] Let's do that.

John: [13:25] Because someone asked about XIOS integration or some sort of making...

Rich: [13:31] XIOS is a nightmare to get to use in a browser REPL environment for some reason. I'm just going to use [indecipherable] fetch. I'll create an async function that's going to do this.

[13:43] In fact, tell you what, I'm going to do this in the onMount method, and there's a reason for that. Anything that happens in this script block here runs immediately when the component is rendered either on the client or on the server.

[13:58] Typically on the server, you don't want to be doing anything asynchronous because at the moment in Svelte, server-side rendering is completely synced. That fetch operation, even if you have fetch installed in node, it's not going to do anything.

[14:12] If we put this inside onMount, then we get a nice guarantee that this will only run in the client. Let's make this an async function and then we can do const res = await fetch(), and then I'm just going to grab that URL from down here. Then the response data would look like this. Then I'm going to inspect that data to see what's going on.

[14:48] It's the exact same set of objects as before. I'm just going to get rid of all of this and replace this with each users as user, begin with users as an empty array, and then I'll just replace that with users = await res.json(). That's how you would do it without using RX.

[15:10] For an example like this, that's probably the right way to do it. Just use fetch because it's a very simple task. It's not until you have chains of operators and you're manipulating the data in more interesting ways that RX really begins to shine.

John: [15:30] It kind of reads the same -- start with an empty array, await for the response, fill it out. The other Q&A question was how would you pass in arguments to the store methods, say storing the response of an API call?

Rich: [15:45] Sorry, run that by me one more time?

John: [15:47] How would we pass in arguments to the store methods, say storing the response of an API call? Maybe because how you did increment and decrement, you hid the events. You invoked it without passing in anything. Maybe that caused confusion. Is it OV, has that...?

Rich: [16:13] If we go back to the custom store example, and then let's make this have an argument, so we can decrement and increment by a certain amount instead of just one.

[16:32] Instead of n+1, we're going to have n+d, and instead of n-1, we're going to have n-d. Then the increments are, instead of just referencing the event handler directly, we'll wrap it in an arrow function. Then we'll do count decrement by 10, say.

[16:52] We'll have the same thing for increment, increment by 10 instead of 1. Now we go up and down more quickly. Is that what the question was getting at?

John: [17:05] Yeah, he said that's right. The event isn't referenced in there, but there is an event in there if you want to get the...

Rich: [17:13] There is an event, yes.

John: [17:14] x value or whatever, increment by the x value.

Rich: [17:19] That's fun. Yeah, let's do that. e.client(x).

John: [17:28] That works.

Rich: [17:30] Yeah.

John: [17:34] There's a question about multiple stores and how prop() method name conflicts are handled. Maybe start with a basic multiple store. I don't know. It's a lot to ask you on the spot.

Rich: [17:55] Let's say we have a user object, and firstname 'John,' lastname 'Lindquist'.

John: [18:14] I know that guy.

Rich: [18:17] We'll pull that in. Oops, typo there. Now we have two stores referenced in the same component. We might want to have a way of changing that user information. Let's add an input.

[19:01] I can change this, but nothing's happening. It's not updating the store because we need to use the bind directive. Now we can change the name and it will be updated wherever it's referenced in the entire application. If you change this to Andrew, and then...

John: [19:42] It's so cool. I honestly hadn't done the import from a store and not even redefined it as something, bring it in a template like that.

[19:57] With forms being this simple, with the bind and binding the stores, do you see any reason to use a forms library? It seems like every other...

Rich: [20:10] It's a good question. I don't know. I don't build a lot of apps that have a lot of forms. I haven't hit the pain points that would drive someone to use a form library. It is one of the questions that we get quite a lot, "What are the best form validation libraries?" "Is there an equivalent of Vuelidate?" All of these things.

[20:31] I'm kind of expecting that the community will settle on some best practices around that in the near future.

John: [20:38] Usually it's generating dynamic forms and lots of stuff around validation; async validation, all that stuff. The guts are there, for sure. Building a validator around a store seems pretty straightforward. It's just JavaScript.

Rich: [21:03] Yeah, you can do isValid =. There is a firstname and there is a lastname.

John: [21:14] I haven't thought about doing it that way.

Rich: [21:16] You can put whatever logic you want in there. That's all you need for basic purposes, I think, but I haven't hit those pain points, so I could be wrong.

John: [21:32] If I wanted isValid to invoke a function when it goes invalid, with that reactivity syntax -- does everyone understand what that line is doing, with that dollar-colon label? Is there anyone confused? If you've been through that tutorial, you know what that means.

Rich: [21:57] If we were to get rid of the lastname, because we've got this dollar label here, that means that this statement needs to rerun whenever these values change. Because we changed user.lastname, it's rerunning that statement, isValid is becoming false and you're updating this, as a result.

John: [22:43] But there's no subscribe method on isValid, it's not a store.

Rich: [22:51] No. You could have a function, doSomething(isValid). That function will be reinvoked whenever that value changes.

John: [23:20] If you wanted to send a request to your server saying someone entered something invalid, is that the approach you would do?

Rich: [23:28] Right, that is probably how I would do it, yeah.

John: [23:32] That's so easy. It boggles my mind that it's that easy.

Rich: [23:42] What else would be good to cover? We can do a bit of transitions, or some SVG, or some Sapper.

John: [23:49] We got two questions about routings. Maybe Sapper would be a great segue here.

Rich: [23:59] We've also got a vote for SVG in the chat, there.

John: [24:04] Yeah. That's your bread and butter, right?

Rich: [24:08] I do spend a lot of time doing SVG. Let's see if we can rip up something very quickly and then we'll move on to Sapper.

[24:18] I'm going to use the GitHub API again to build a little chart widget that is going to show commit activity on GitHub repos.

John: [24:36] I'm really excited for this, by the way.

Rich: [24:39] I'm going to create a rapid div for reasons that will become apparent soon enough. First thing we need to do is get some data, in onMount, once again, a good practice to begin. Get another async function. This time, we're going to get...Oops, I forgot the await. Going to get the commit activity on a specified repo. For now, repo can be this.

[25:50] Because the repo reruns every time we make a keystroke, more or less, I'm going to be spamming the GitHub API if I do this. I'm going to store this value and then copy it into a separate module, for now.

[26:15] That didn't work. I know why -- it's because I'm not invoking this component.

John: [26:24] I was going to say there's that console trick where you can right click on something and store as a global.

Rich: [26:40] Really? Right.

John: [26:41] Yeah, you can store as a global. That gives it to you as temp1, I think, so you wouldn't have to...

[26:47] [crosstalk]

Rich: [26:47] That's really handy. I'm going to use copy. This is another useful thing. I don't know if people know this, but when you're in the console, you have access to this copy command, which will copy a JSON version of whatever it is you pass in.

John: [27:04] If you would have done copy(temp1), it would give you the same thing.

Rich: [27:08] That is very cool. Literally, just so that we're not spamming the GitHub API, we'll do activity = data, and then we'll create a placeholder variable to put that data. Let's give our chart a width, let's say 300 for now, and a height, let's say 200.

[27:43] We'll add some styles. We'll make our div be 200 pixels wide and our SVG can take up the entire space -- width 100%, height 100% -- so that we can actually see it. I'm going to give it a background color as well.

John: [28:06] On line 22, is that supposed to have a height before that?

Rich: [28:10] Yes, thank you. This is where we're going to be rendering our chart to. Let me get rid of the console, we don't need that anymore.

[28:21] The first thing we're going to want to do is declare the bounds of our chart. We'll give it some padding and we'll say x1 is that value. x2 will be the width of the chart minus that padding. y1 is going to be padding, also. y2 will be the height minus the padding.

[28:58] Then we can start to add some stuff, like an axis. We'll add a line here. x1 will be the x1 value. y1 is going to be the y2 value, because it's at the bottom. x2 = (x2), y2 = (y2). We can't see it because, by default, nothing in SVG has a stroke. Let's give that a stroke so that we can actually see it. That's the beginning of our chart.

John: [29:35] It's great.

Rich: [29:36] For anyone who hasn't used this GitHub API before, what it gives you is an array of the commit history for a repo for the last year. You get 52 weeks in the response.

John: [29:51] Can you pull up activity.js, real quick?

Rich: [29:54] Yeah.

John: [29:55] That has the data.

Rich: [29:57] Yeah, it's an array. Each one looks like this. You have a "days" property and you have a "total" -- which is what we're going to be using, which is total number of commits -- and a timestamp for the week.

[30:08] I'm going to create some text. x1 is going to be in the same place. y = (y2 + 20), to give it a bit of an offset. Oh, it's not x1, it's just x, like that.

Rich: [30:46] We can't actually see it because the SVG clips its contents by default, so we'll just make that overflow. We could change the padding. In fact, maybe we'll have to. Another way is to add overflow visible, like that.

[31:00] Now I have another one of these text labels. It will be x2, and that will read today. Now it's hanging off the edge of the chart at the moment, and that's no good, so I'm going to add a text-anchor, like that. Equals, and like that. That's going to be the bounds of our chart.

[31:23] Now, if we're going to render some data, we need to project the points in that array onto the chart. The easiest way to do that is to have a scaling function. I'm going to create a little scaling function.

[31:38] Export function scale. What a scale function does is it takes an input domain and an output range, and it maps values from that domain to the range. It's pretty simple. It looks a little bit something like this. d is going to be the size of the input range, r is going to be the size of the output range.

[32:06] It's going to return a function that takes a number and maps the domain to the range by doing this. Just looking at my other screen, which is why I keep looking over there.

John: [32:22] OK.

Rich: [32:24] We'll import that scale function. Then we'll just say x = scale, and then the domain will be zero, as in the first week, all the way up to 51 which is the last week, because there are 52 weeks in a year.

[32:45] The domain is going to be x1 up to x2. The y is going to be scale zero to some value that we don't know yet. It's going to go from y2 to y1 instead of y1 to y2. The reason for that is that the numbers get higher as you go down. Typically when you're doing a chart, you want the higher numbers to be at the top, so we have to switch those around.

[33:17] What's this going to be here? This series of question marks? We need to have a maximum value for the chart. We're going to have zero at the baseline, and it's going to go up to the maximum. We could set that. We could hardcode it. We could make it a prop on the commit chart. What I like to do is infer it automatically from the data.

[33:38] I'm going to create a max here...

John: [33:41] Like Math.max.

Rich: [33:42] It's going to be Math.max, and then we're going to take all of the items in that array and grab the total like that. Then use that as the second half of our domain. Now we have these...

John: [33:58] I have never spread an array into Math.max before.

Rich: [34:02] Oh, it's super handy. Another thing you want to do if you have a very large array, but when you've only got 50 or so items, it's probably the easiest way.

John: [34:09] Oh, that's cool. That's one of those, I've never thought of that.

Rich: [34:12] Yeah. All right, we can add some y-axis labels now. After that, we'll be able to draw the dang chart. First off, we'll have one for the zero. Now we can use our scaling function like that, and we're going to give it...Actually, we won't do that yet.

[34:41] Let's see what it looks like. There's our zero, it's just a bit on the chart. Then we'll do the same for whatever the max value is. We need to give it a little bit of an offset, so I'm going to add six to that because that's about what the font size is.

[35:05] Right, so now the zero is lined-up with the bottom axis. We're going to copy that text and anchor attribute so that it's on the left hand side of the chart. So far so good. We don't have any actual data.

[35:20] What we want is a path. The way that path attributes work in SVG is a little bit convoluted, if people haven't seen this stuff already. You had a d attribute which is composed of all these cryptic commands, which specify which coordinates we need to go to, and you can do curves and arcs and all those kind of thing.

[35:50] The way that I like to do it is with a reactive declaration. Again, do path =, and then there's going to be a big old string here. This is going to look a little bit confusing if you're familiar with SVG. It takes some getting used to. Hopefully, it will make some kind of sense once you see it in action. We're going to map over all of those weeks.

[36:22] We're going to return a string, which is a coordinate pair, like that. Then we join them with the line to command, like so. We just need figure out what these are. We're going use our x scale function again, and we'll use the I because that's the number of the week. That's the thing that we're progressing along horizontally. Then for the y value, we just take the week.total.

[36:58] Then we need an initial command. M stands for move to. Here, if we...

John: [37:04] It blows my mind that if the data was coming in live, this would already react to it. The way you wrote that.

Rich: [37:11] That's the neat thing about this, everything is reacted by default. We have some data in our chart now. You see this 109 corresponds to the top of the chart there? It looks a bit tacky because it's got a black fill. That's no good. I'm going to get rid of that. Fill none, stroke, let say make it green.

[37:35] Stroke width two. It looks a little bit better. Get rid of our background color now. We don't need that anymore. We can actually go one step further. We could have a fill as well. class="fill" class="stroke".

[37:57] Then give that a fill and a fill-opacity. It looks a bit silly because we're missing something where we're going to need to make some changes before that's going to look good. I'm going to get rid of this move to command, and then put that inside the attribute for reasons that will hopefully become apparent.

[38:32] If I'm going too fast and this doesn't make any sense, please do stop me. What I want to do is start with the very bottom left of the chart and then start drawing the chart. Then go to the bottom right of the chart, and then draw a line back to the start, and then we'll have a good looking chart.

[38:53] Start at x(1), go to y(0You can see it's starting to come together. I don't know if that's visible on your screens. That will make it a bit more visible. Then do the same thing at the end.

John: [39:22] Bravo.

Rich: [39:24] So far so good, but this chart isn't responsive. It would be nice if it was responsive. OK, we've got these hard-coded values. That's fine, but we can do better. We could take the dimensions of the container element and bind to them.

[39:41] The same way that we bind to form elements, to their values, we can do bind:clientWidth=(w). bind:clientHeight=(h). Now our chart is...Oh is it not responsive? It looks like perhaps I have broken something.

John: [40:06] What's w? What did you set w to?

Rich: [40:14] I set s initially to 300, but it's supposed to be binding to...

John: [40:18] That's right, the client width.

Rich: [40:19] The client width of this thing. Just to debug that, I'm going to see if these are getting reset. They are, OK, that's interesting. We might have found...

John: [40:46] I've cookied anyone in the chat who can spot the problem.

Rich: [40:53] There is one line I made earlier. Is it this one? No, it's not.

John: [41:04] You know, it's funny that anyone else in the world, if you ask them to build a chart, would have done npm install d3 and just forgot about it. Like, "I got this."

Rich: [41:14] I just want to move on to showing a slightly improved version that does use d3 because it's really useful to be able to understand what's actually happening when you use SVG. Once you get used to the weird path syntax, it starts to make a bit more sense.

[41:31] You feel like you can do a lot of the stuff that d3 does without needing to install it. When it comes making really nice shapes, d3 is just an absolute fountain of good stuff.

[41:49] My boss basically reads all of these incredibly pointy-headed academic papers that describe all of these mathematical discoveries that are completely out of reach to us lot. Then he converts it into code that anyone can use, it's a really miraculous thing.

John: [42:06] You work on...The things you do on "The Times" are amazing, great stuff.

Rich: [42:11] Thank you. What's the easiest way to send some information from one computer to another?

John: [42:23] USB drive?

Rich: [42:25] Actually, you know what John, I'm trying to DM you right now.

John: [42:28] Drop it in the chat I guess.

Rich: [42:32] I sent you something on Twitter.

John: [42:33] OK, I'll drop it in chat. All right, copy...Oops, that's not right. OK, it's in the chat now.

Rich: [42:54] It decided to open in Firefox instead. Go away Firefox, not that I have anything against you.

Zac Jones: [43:01] Someone in chat mentioned do we need to put the reactive, or make w and h reactive?

Rich: [43:08] No, because they are actually components. Because they're bound to the development, it should just work. I'm not sure, I probably missed something obvious. Let's see if this one works. That's broken too. It might be because there are some wrinkles around what you can get away with inside an iframe that has sandbox attributes.

[43:38] One of the things that we've discovered is you can't do the element resize listener trick that you can do in a normal application because in order to do that, you would have to have access to the window object.You would therefore be able to reach up to parent frame and grab all sorts of sensitive user data.

[43:58] Because this is a REPL and because people can share snippets with other people, there's a risk that if you give people access to the top window object, then we could start grabbing all sorts of private user information.

[44:10] We do have some restrictions there, and so it's not going to work right now. I will tweet out a link later and hopefully we'll be able to get it working another way.

[44:23] Last thing that I wanted to show you on the chart...

John: [44:25] So you are just saying that if this was done locally, if they were just to...

Rich: [44:31] Depending on what the nature of my mistake was, how bad my error was. Hopefully, it's something that would go away if we clicked the downward link here and ran it locally. What I'm doing here is I'm exporting the repo as a prop.

[44:51] Then I'm duplicating the component several times. Then it's going away and it's fetching data for each of those repos as that we can see [indecipherable] activity for these different repos. We're importing some helpers from d3-shape, which allows us to make this path generator.

[45:13] Then our path is using that path generator. You can see how it's got these nice, smooth curves whereas our previous one looked a little bit [honking noise] ugly. This one's...

John: [45:29] Do that again.

Rich: [45:30] This one's a bit nicer. I did see a question in chat just a minute ago about Gatsby and Nuxt, which would segue nicely into Sapper, if we want to move onto that.

John: [45:44] OK, question real quick. Can you show in the REPL, writing hello or something, inside of App:Svelte. Then walk us through the JavaScript code that's generated, or the JavaScript code that's generated from this?

Rich: [46:06] Yeah, OK. [indecipherable] .

John: [46:08] This is really cool. I love this, the whole concept of this is amazing.

Rich: [46:13] Yeah, I should have started with this really.

[46:16] Just to recap for anyone who wasn't too familiar with this stuff. The idea behind Svelte is that you get to write this high-level declarative component-type code, but instead of having to interpret that at run time, the compiler takes it and it turns it into the kind of procedural imperative code that you would have written five years ago with jQuery.

[46:41] Right now, if you're really trying to hand up to [indecipherable] something and eke out the maximum amount of performance. The idea is that it does that for you in such a way that it spares your brain having to figure out all of the different combinations of things that could go wrong when this DOM element is changing and this piece of state is changing.

[46:58] I'm going to do the classic button counter thing. Count = 0This is sort of the hello world of component framework. We got a button here, and we've got a value that it's referencing. I am going to add a handleClick function, which increments that count.

[47:33] Button onClick=(handleClick). Now when you click that, this happens. There's two things that are going on here. Firstly, when we change this state, it's somehow telling the system that it needs to redo everything.

[47:55] Secondly, this value here is being reflected as a result of that. This is what that looks like. If we go to the JS output tab, and first go down to where the component is being instantiated, we can see that this code is basically identical to what we've got up here, let count = 0and function handleClick.

[48:18] Something that you might notice, the let name = 'world', that's been removed from this instance block because the compiler has detected that it can reuse that between instances because it never changes, so it hoists it out of the block and then reuses it between instances.

John: [48:33] Awesome.

Rich: [48:34] The count, this is local state which could change, so I just declared inside this instance function, which Svelte calls when the component it's created. Then when we call handleClick, and we have this count += 1 statement here, it wraps it in a function called invalidate.

[48:53] The job of invalidate is to say, "Hey component, this value has changed, and at the end of the event loop I'm going to ask you to re-render yourself with any of the data that's changed." It passes in the new value so that it can check that it has, in fact, changed.

[49:09] Once the end of the event loop arrives, just in case it has any other changes, it waits so that it can come back to everything. It will then call the update function on the component's fragment. The update function looks like this.

[49:28] This is all the code that's running, essentially, whenever there is an invalidation. This change.count value is true because of this call to invalidate. This change.count is true. We just use this set-data utility with the new value. That is the thing that is updating the button.

[49:50] The t5 is a text mode, which is created here and which is appended to the button here, and then set_data is really doing t5.data = ctx.count, but it's collating to a string in case you have non-string data.

[50:06] That's all there is to it. There's not a lot of machinery, it's just doing the DOM update in the most idiomatic and performant way that you can. This is the reason that Svelte does pretty well on benchmarks. Also, it's quite small, right?

[50:23] This entire component is basically self-contained. Apart from these little helper functions, which are just wrappers around document.createElement and stuff like that. The reason that we use those wrappers is to make it more minifyAble.

[50:36] Apart from those helper functions, this is the entire application. Rather than having to ship all of the code that is responsible for taking your virtual DOM and then your new virtual DOM and then seeing what changed, it's all there.

John: [50:51] It made me question if I was going to write even a simple form, would I do it in Vanilla JS or would I just do it in Svelte, because it's going to write what I was going to write anyway, but it's going to save me time on what...

Rich: [51:06] That's the goal. It's not completely idiomatic. If you're writing this by hand, you would do this in a bit more of a sensible way than the compiler is doing it. The hope is that the compiler will get smarter over time, and so as you install new versions of Svelte, your application will get smarter and smaller and faster without you needing to rewrite anything.

[51:26] That's the hope [indecipherable] .

[51:29] OK, shall we do a quick whirlwind tour of Sapper?

Stanley: [51:32] Yeah, let's see it.

Rich: [51:34] Sapper is a companion framework to Svelte. If you've seen frameworks like Next and Gatsby for React, and Nuxt for View, Nuxt is basically a take on Next. Sapper is like that but for Svelte. It's the application framework for the Svelte component framework.

[51:57] The reason that we have that is because a component framework by itself gives you the base primitives to build an application, but there's a lot of stuff that you need to have around that. You need to have server-side rendering if you want to have the best possible startup performance.

[52:11] You need to have code splitting if you don't want to bundle all of your JavaScript into a single blob. You want to have rich, client-side navigation, all of that kind of thing. It's pretty difficult to set up. Sapper is an opinionated way of doing all of that in a way that hopefully makes it a lot easier for you to build an application.

[52:34] The first thing I'm going to do is clone the project template, which exists on GitHub Sveltejs/sapper-template. Instead of using getClone, I'm going to use a little utility called dGet, which has some advantages. It will do off-line caching. It gets the tar, so it's faster than cloning. It'll set you off with a clean get history as well.

John: [53:05] I didn't know that it cached. That's awesome.

Rich: [53:08] Oh, yeah. It does, yeah. I'm going to make a new project, my-fun-project, then cd into that. Then I'm going to use MPX so that I don't need to worry about having stuff installed globally. We'll just do the repo slug and we're going to use a branch, because there are versions of Sapper for rollup and for web pack. Let me use the rollup version for now.

[53:36] It's going to clone that, and it's done. Now, npm install. Make a cup of tea.

John: [53:52] This one wasn't cached?

Rich: [53:56] The project template was cached, but it still had to npm install a bunch of junk.

John: [54:01] Oh, yeah. Sorry.

Rich: [54:04] If I do yarn dev --open, it will build the app and it will open a development server. It's going to do it in Firefox again, isn't it? No, it didn't. It did it in Chrome. No, it didn't. It did it in Firefox. Go away Firefox. I just want one browser at a time, localhost:3000.

[54:27] Here is our base Sapper application. If I now open my [indecipherable] , and I have that on one side and I'll have my browser on the other, we can start seeing how this thing ticks.

[54:53] Inside the project folder we have a source folder which contains our routes. This is basically the structure of the application, the file system matched to the URL structure of your application.

[55:08] This index.svelte is the home page and I can change that Hello egghead.io and it'll refresh the page. If we navigate to another page, this is about.svelte, that means the /about routes corresponds to the contents of this file. Click on that.

[55:39] You'll notice that it didn't reload the page. I didn't need to go back to the server and get some fresh HTML. It does purely client-side navigation which is the fastest possible, slickest user experience, but if I do refresh the page, then it does go back to the server.

[55:57] If we view source, then you'll see that it is in fact generating HTML for that page, so you get all of the SEO benefits of having server rendering, you get all of the progressive enhancement benefits of having server rendering.

[56:12] But if someone is on a bad network, they don't get your JavaScript, or if they're not running JavaScript for whatever reasons, then they still get a hopefully usable website.

[56:25] We've got a few blog posts which explain what it is. You can click around this and navigation is instantaneous because once the JavaScript kicks in, the client side takes over.

[56:40] We also have error pages, which have an error status and a message. We have a layout component which is responsible for essentially wrapping all of the individual routes in the common page structure that you typically have. You'll often have a navigation bar on every page. You might have a footer on every page. That stuff goes in your layout component.

[57:10] You'll notice that in our source folder over here, we have node_modules inside source, which is quite unusual. The reason for that is that Sapper, in the same way that Svelte builds Vanilla JavaScript out of your component framework, out of your components, Sapper builds the server application out of what's on the file system. It generates a route manifest out of what's inside your file system.

[57:41] This is an example of the kind of code that it's generating. It's giving us an array of routes which are just regular expressions. This is how it knows where to go when you click on a link. It's able to do this without having a full-fledged router.

[58:04] Again, it's very small. The amount of code that it takes to build something in Sapper and ship it is pretty slim. In this case, because there's nothing dynamic about the pages that have been generated, it's coming from a static source.

[58:29] Our blog pages are literally just an array in this file. It could be coming from some markdown files. It could be coming from the content management system. It could be coming from the database .It's not something that is going to vary per user.

[58:45] What I mean by that is, there's no request time computation happening. It's going to give the same response for every business who uses a specific URL. Because of that, we don't actually need to have a node server at all. We can turn this into a purely static site that you could host on Netlify or GitHub Pages, or anything like that.

[59:06] All we need to do -- get out of that -- is run Sapper Export and it'll build the project. Then down here, it's crawled the site. It's looked at the home page and it's followed every link that it can find. From that, it has generated a set of static files that represent the entirety of our application.

[59:34] We can see what it's generated here. If I go inside the export folder we've got our index.HTML. It's all compressed, so it's not easy to see what's going on. You can see that we've got some JavaScript that's been generated. We've got an about page. We've got pages for each of the blog pages.

[60:00] Also, the data that it needs to update those blog pages if you navigate to them on the client. If I were to upload this to Surge, for example. If you're not familiar with Surge, it's like Netlify. It's a static web server that's very easy to use. I'm going to...

John: [60:22] It's been around much longer than Netlify.

Rich: [60:25] Yeah. It's pretty cool. my-fun-project.surge.sh. Let's hope that it hasn't been taken yet.

John: [60:37] I'm surprised it hadn't, OK.

Rich: [60:40] We can now visit that URL. There we go. We've got a fully SEO'd, fully client-side routed up site. It's very small. It's pretty straightforward to understand how the pages are built, and everything.

[61:02] If we look at the JavaScript, it's not loading very much JavaScript at all on the first load. This is basically the entirety of the JavaScript required. That's 11.7 kilobytes for all of this stuff. If you compare that with the hello world for a Next site or a Gatsby site, or a Nuxt site, it's about 10 times as much JavaScript just to get started.

[61:33] This is a really lightweight way to build progressive web apps that fall back nicely to a progressively enhanced server-ended application.

John: [61:44] Standing ovation. Let's stand up and clap.

Rich: [61:48] If anyone has any questions about any of the stuff, how to...?

John: [61:53] Let's wrap it up so you can get back to your day. Are there any questions that Rich can answer just by saying words rather than showing demos?

Taylor: [62:02] One of the questions is applying CSS to the whole app instead of scoped to a component.

Rich: [62:11] There's a couple of ways that we could do that. One is to use the global modifier inside Svelte itself. I could do global(.body) {background-color:purple;}, and then...Oops, we're not actually in that mode yet. Let's get that up and running again.

[62:45] Now we've got...Oh, that's terrible. Let's change that to something a bit less offensive. We can do it that way. The default project template uses a global.css file, which is just a static file that we can use to do CSS the old-fashioned way. Nothing wrong with that. It doesn't seem to like my Comic Sans MS. Wonder why not. Never mind.

[63:22] [crosstalk]

John: [63:22] That's the export folder, I think.

Rich: [63:26] Oh, yeah, hold on. I am completely losing my marbles.

John: [63:32] After half an hour, my mind just goes and I'm trying to present. You're doing great.

Rich: [63:38] There we go. Much better. Comic Sans, love it. You can do it that way. I do because I'm old-fashioned and I like to have a single basic level style sheet and then all of my component styles are inside the components.

[63:53] But you can do whatever you want. You can use CSS-in-JS. If you have a preferred CSS-in-JS solution, you can import the CSS file directly in here. In fact, let's try that right now.

[64:11] Extra.css transform scale(2), and then...In fact, let's import it from this client.js file, which is the thing that starts the application. There we go. We've made the website unusable by blowing it up. You have multiple options for the CSS. Just whatever works.

[64:46] I think there's too many different opinions and best practices for Sapper to have too strong an opinion about the best way to do it. It lets you make that decision.

John: [64:56] In the package.json, what does the scripts say for the devs [indecipherable] ?

Rich: [65:04] It's actually just calling Sapper dev directly. Sapper is a command-line tool that has three commands -- dev, build, and export. Dev just starts the dev server, runs Svelte in development mode and not minified JavaScript. You get all of the extra dev time warnings compiled in all of that stuff.

John: [65:27] Is that looking for the rollup config and just going by whatever that says?

Rich: [65:32] It is, yeah. You've got flexibility about which plugins you want to use. If you want to use, I don't know, maybe there's a WebGL stuff and you need a GLSL plugin or something like that, you've got total access to the config.

[65:48] Build will create a node server from your Sapper app that is optimized for production. Export, as we've seen, takes the site and turns it into static files, if it's possible to do that because it's not possible for every site because you might have database connections or whatever that can't be expressed statically.

[66:11] I see a couple of questions in chat. Abdullah's question about code splitting. Yes, it code splits on different routes. You can code split within routes as well if you want to use a dynamic import, it's possible. The easiest way is just code split on routes. That allows your application to grow pretty big before you need to worry about giving out the JavaScript that you're downloading.

[66:39] There's a question about Svelte stores. Yes, you can use them exactly the same way except that there's a wrinkle around how you have stores that match between the server and the client. There is a little bit of a trick to getting that to work, which I can probably go into in more detail another time. If you want to hop in the discord server, and we can talk about that further.

John: [67:12] Is that in Sapper docs?

Rich: [67:15] It should be, yes. The Sapper docs need a little bit of love, if I'm totally honest. They're not quite as polished as the Svelte docs. Sapper is a pre 1.0project whereas Svelte is version 3.0But if we go down to here, this store section explains which stores you have access to, that you can use consistently between server and client.

John: [67:48] Great.

Rich: [67:48] Fabian has a question about test loop. The Sapper template has Cypress integrated. Cypress is quite a popular tool for doing end-to-end testing of applications. It's just sitting there ready to go. But you can bring whatever tools you prefer.

[68:08] The Svelte community is still figuring out what the best testing setups are. I don't think we've totally nailed that down yet, but hopefully soon.

John: [68:20] For your logic, it's JavaScript, or importing JavaScript file. So, you can use whatever test tools you want.

Rich: [68:27] That's the hope, yeah.

John: [68:32] Texas, come on. Texas is one of ours. I apologize.

Rich: [68:38] Svelte is written in TypeScript. I'm a big TypeScript fan myself. I use it for just about everything. One of my personal bugbears is that I can't really use TypeScript inside my Svelte components.

[68:51] Svelte 3.0was largely about laying the foundations for TypeScript support because Svelte 2.0was not very TypeScript friendly and couldn't really become TypeScript friendly. The short answer is no. It is not TypeScript friendly at the moment, if that's what you mean by "compile to JS," but it's a high priority.

[69:14] I really want to make some progress on that fairly soon. I believe it's possible. I don't see any real technical reason that we shouldn't be able to do it. It's just finding the time to work on it.

John: [69:25] Texas, you could help out. He's our TypeScript expert.

Rich: [69:30] That would be great.

John: [69:31] Sweet. Let's call it. Thanks everyone for coming. Rich, you're awesome. This was incredible. [indecipherable] .

Rich: [69:38] Thank you for having me.

John: [69:41] Best of luck in everything you do.

Rich: [69:43] All right. Thanks.

John: [69:43] Thanks, everyone.

Rich: [69:44] OK, guys.

John: [69:44] Bye.

egghead
egghead
~ 13 minutes ago

Member comments are a way for members to communicate, interact, and ask questions about a lesson.

The instructor or someone from the community might respond to your question Here are a few basic guidelines to commenting on egghead.io

Be on-Topic

Comments are for discussing a lesson. If you're having a general issue with the website functionality, please contact us at support@egghead.io.

Avoid meta-discussion

  • This was great!
  • This was horrible!
  • I didn't like this because it didn't match my skill level.
  • +1 It will likely be deleted as spam.

Code Problems?

Should be accompanied by code! Codesandbox or Stackblitz provide a way to share code and discuss it in context

Details and Context

Vague question? Vague answer. Any details and context you can provide will lure more interesting answers!

Markdown supported.
Become a member to join the discussionEnroll Today