A look into Preview.js, an open-source extension for Visual Studio Code, IntelliJ and WebStorm that lets you preview individual React components instantly, updated as you type.
Improving Developer Happiness with Preview.js
AI Generated Video Summary
Francois, a developer happiness engineer, discusses the challenges of slow builds and their impact on productivity. He explores the implementation of showing unavailable menu items in a food delivery app and demonstrates the use of Storybook for component design. Francois introduces Preview.js, a tool for previewing React, Vue, and Solid components, and explains how it simplifies the development process. He also highlights the benefits of using PrivyJS with Storybook and VIT for faster and more efficient development.
1. Introduction to Developer Happiness Engineering
Hi, I'm Francois, a developer happiness engineer. I work on refactoring, fixing tests, and creating tools to make engineers faster. Today, I'll talk about Preview JS and the challenges of slow builds and its impact on productivity.
Hi, my name is Francois. I'm a developer happiness engineer from Sydney, Australia. You might be wondering, what is a developer happiness engineer? What it means is that I'm a regular software engineer who likes to work on refactoring stuff or fixing flaky tests, or making CI faster, but sometimes it's also about creating new tools to make other engineers faster.
So one of these tools is called Preview JS, and I'm going to be talking about that now. You might be familiar with this XKCD comic. I think it was thrown quite a while ago, but it's still somewhat relevant today. I'll give you a few seconds to read through it.
2. Handling Unavailable Menu Items
Let's take the example of a food delivery app called Humbry. Users have provided feedback that they are surprised when the menu doesn't contain expected items. Currently, if an item is unavailable, it is completely removed from the menu. However, the app designers have received a feature request to show unavailable items but make it clear that they are not available. The first way to implement this is by running the app locally.
So let's take this example, this is a food delivery app that I called Humbry and it's really just for the purpose of this presentation. So here we have a bunch of restaurants and let's say I feel like some French crepes, so I'm going to say crepes.
Okay, there's a restaurant called Crêperie François and I'm going to go there and check out. Okay. So there are some really yummy things, caramelized banana and chocolate. That sounds good. Fresh and candied orange. Okay. That sounds good. Okay. That sounds good. Okay. That too. Okay. I'm going to get everything, maybe two of each. Cool. Great. This is pretty much what you would expect from a delivery food app. And we're in the early days of releasing that to users.
And the first piece of feedback that we get is that some users are surprised that sometimes the menu doesn't contain the items that they expected. So that's because sometimes if an item is unavailable, right now the API just removes it from the menu. So let's say this restaurant ran out of chicken. This first item, Kung Pao chicken, will actually be missing entirely. Which surprises users, because they're like, well, did this restaurant remove this from the menu forever? Should I just completely boycott this restaurant because they removed my favorite item? No, that's not the case. It's just unavailable today. And really that's our responsibility as designers of this app, to make it clear.
So instead, we get a feature request from our product manager, which says, now we should still show unavailable menu items, but just make it clear that they're not available. So you know how here we have this counter, instead of this counter, we'll just say, unavailable today, right? This should be a lot less confusing for users. Of course, the first way that we can implement this is by just running the app locally. Right? So we're going to go to our terminal, run Yarn Dev, and wait a little bit.
3. Modifying API and Code for Unavailable Menu Items
I'm running the app locally and waiting for it to spin up. I need to find a restaurant with an unavailable menu item, but the API doesn't return unavailable items. I can either change the API to return them or mock it up in my code base. I'll modify the code to mark a menu item as unavailable and update the component to display it as such.
Okay, so it's running at local host, cool. I do need to wait a little bit for this to spin up. I'm actually not entirely sure why, but it's something in here that takes quite a while. This is exactly what I meant by spending the time that you spend waiting. Like I might just go on Twitter and completely forget about this and go back here and just maybe have to wait again because I need to reload the page or something.
Okay, cool. So that's our app. It's running. Now I need to find a restaurant that has a menu item that is not currently available, so I'm going to go through and try to find one. But wait, I said earlier that the API actually does not return menu items that are not available, so that's not going to work. All of them are by definition available because they're returned by the API. So I have a couple of ways to approach this. One is if I'm myself an engineer who works on the API, then I might change the API to return unavailable items, although I have to be careful not to return them to versions of the app that don't support that yet. Or if I don't know anything about the API, I'm going to have to mock it up. I'm going to go into my code base, and find... Okay, so it's the restaurant page... Oops, restaurant page, and I'm going to go into that. Okay, so that's using this restaurant details page state, which is a MobX state. At the end of the day, all it does is fetch. Okay, so it's fetching that, it's getting the data, it's restaurant details response. Cool. At least this is well-typed. And that returns a menu item, and menu item has an available which is an optional Boolean.
Okay, so I'm gonna hack this up and say, data.menu.zero.unavailable equals true. And I'm gonna go back to here. And so, this first one, because number zero should be unavailable, but of course we haven't implemented this, so right now, it still shows the counter. And now, I do know that this component is called restaurant menu item, so I'm gonna go in here and say... Okay, so here, instead of the counter, we want to say that if unavailable, then we show unavailable instead. So, menu unavailable, unavailable. Cool.
4. Running the App Locally and Using Storybook
Nice. Cool, done. Running the app locally is a lot of times the right solution. Option two, we could use Storybook. It's pretty much a gallery app for all of your UI components. Each story is a specific version of your components.
Nice. Cool, done. That's it. Now, of course, I need to remember to remove this because if I don't remove this, it's gonna break the experience for users, but other than that, we're done.
So, luckily, this is a pretty nice good base to work with. Thank you Next.js. And yeah, we're pretty much done.
So, running the app locally is a lot of times the right solution, right? It's super straightforward. It doesn't require any additional setup. The only thing is that mocking data can be hard depending on what is the specific use case that you're working with. And iteration speed can be slow, again, only if you're working on a really complex code base or something that is not very well configured. But that's more common than you would think.
Option two, we could use Storybook. In case you're not familiar with Storybook already, it's pretty much a gallery app for all of your UI components. It's called Storybook because you define stories for your components. Each story is a specific version of your components. So in our case, for example, we have the unavailable story and we have the available story.
So let me show you what that means. I'm going to go in here and run your Storybook. And this can take a while depending on how many components you have. Okay. In this case, very fast, cool. And you can see, okay, we have this counter story. We have the no match story, the restaurant header story, restaurant list, and so on and so on. So what we're interested in here is restaurant menu item where we have two different stories, default and picked. So you can see that when it's picked and there's already a counter created. And I can go back in here and let me show you how that's implemented. So we have this file next to restaurant menu item, which is the restaurant menu item to the stories. And it has a little bit of boiler plate for storybook. And then each story is this.
5. Creating a Story for Unavailable Menu Item
We define args for stories, such as default and picked. We want to create a new story called unavailable with an unavailable menu item. After simulating the experience, we revert the change to try another approach.
So we have args that we define for story. So here we have two stories, default and picked. And default and picked are here. So you can see the difference is with the args that we pass, which are pretty much the props. So our case, we want to create a new story called unavailable with a menu item that is of course unavailable. Oops, forgot a comma, available, true. That's pretty, okay. And now it should appear. Okay, perfect. We have it here. But of course we haven't reverted the change already. So let's do that again. Just simulating the experience from scratch, menu item.unavailable, unavailable. Okay, perfect. And it updated pretty quickly which is great. And let me revert that again because we're going to do this again yet another way.
6. Benefits of Storybook for Component Design
The nice thing with Storybook is that it encourages a good design, not just from a UX design perspective, but also from a software design perspective. It pushes you to create dumb components that are agnostic of the state, making them easy to test and reuse across code bases.
Cool. The nice thing with Storybook is that it encourages a good design, not just from a UX design perspective, but also from a software design perspective. So because you want stories for each of your components, you need to think about what components you want in the first place and how you want them to be designed in terms of props that it takes, etc. And the way that Storybook structures stories kind of means that you are kind of pushed in the direction of creating dumb components that are kind of agnostic of the state, so they're not capable to redux or anything like that. And usually that's a pretty good thing because it means that your components are really easy to test, really easy to reuse as well across code bases, so lots of good things there.
7. Preview.js for React, Vue, and Solid
Storybook acts as documentation and allows you to preview components. Option three is to have a preview for React components like markdown previews. I couldn't find a suitable solution, so I built Preview.js. Preview.js works with React, Vue, and Solid, and is available on VS Code and IntelliJ.
It also acts as documentation, which again, is super useful, so for, if you want to say, tell your UX designer, hey, I have this component that is currently in this particular implementation, you can check it out at this URL. That's really super useful and reconciling that versus the box that your designer is working with. It can be a little bit slow to spin up in that it depends on how many components you have, and really is something that a storybook team is working on improving anyway, so it's not really something I would worry about too much.
It does require a little bit of boilerplate, so we saw that you really can only look at components that have stories defined for them, so first you need to define the stories. If you find a component and you're wondering what it looks like and there's no story for it, you're going to have to write a story first.
Cool. Is there an option three? You know how when you write markdown in your ID, let's say in VS Code, you can actually see a preview of it. Let me show you. Let's create a readme and say, hello world. I'm on a max. I'm going to press command K and V, I think. Yep. Just going to say this is instantly updated with a nice preview. Now I can see this is pretty cool. Cool. So what if we had something like that, but for React components. And I kept looking for that, but I never could quite find the right solution. There were a couple of contenders, but the problem is that they only work with components that don't take any props or that are like super, super simple. So I decided to build it. And it's called Preview.js. It was actually earlier called React Preview. Because it was for React components. But then I realized that it could also work with other frameworks like Vue and Solid. So I renamed it to Preview.js. And also in the process, pretty much all of it. So Preview.js works with React, Vue 2, Vue 3, and Solid. It's available on VS Code and IntelliJ. So let's look at what Preview.js looks like. Let's go back to VS Code. We have our Restaurant Menu item here, which I reverted back to the previous state.
8. Previewing Restaurant Menu Item in Preview.js
I can see a preview of the open Restaurant Menu item in Preview.js without writing any code. It automatically generates a valid value based on the props and type of menu item.
And I'm going to here have this button, open Restaurant Menu item in Preview.js. So I'm going to click on that. And oh, proof! That's pretty cool, okay. So I can see a preview of that. And I, again, did not have to write any code for this. This is just out of the box. Basically, what it did is it looked at the props of this. It's, so, oh, okay. This specific, first, this is a React component, and therefore I can't show this thing. But also, it says, okay, that takes menu item count on update. And it can actually look at the type of menu item and kind of generate a valid value for it. Well, this, as much as it can guess.
9. Implementing the Unavailable State
Let's make it more realistic by implementing the unavailable state. I provide an example value and instantly see the live updates. With instant feedback, I make it prettier with padding, rounded corners, drop shadow, and a red border. Despite the typos, it's cool.
So here, let's make it a little bit more realistic. So let's say brownies. And let's use cute kitten pictures. Although we're not gonna eat the kitten. So, yep, this is really just for illustration. And now, this is a little bit more realistic. Now, we want to implement the unavailable state.
So what I'm gonna do is provide an example value. Okay, unavailable true. And now, I should be able to see what it looks like. So, div unavailable. Oh, cool. That actually updates live. That's pretty cool. And I'm gonna make it prettier, then, given I have instant feedback. That's pretty cool. Okay. A bit of padding, maybe rounded corners. Yep. Maybe a drop shadow. Drop shadow, yep, cool. Maybe a border, as well. Border 2, border red. Oops, sorry. Typos, lots of typos. Okay. That's what you get for recording a presentation at 11 pm. Okay. Yep, one more typo. Okay, cool.
10. Using PrivyJS with Storybook and VIT
I keep pressing save when I actually don't even need to save because PrivyJS doesn't require you to save. PrivyJS works well with Storybook. They don't compete with each other. PrivyJS is possible thanks to VIT, which is faster and simpler than Webpack. Use VIT.
I keep pressing save when I actually don't even need to save because PrivyJS doesn't require you to save. Okay, so that's pretty cool. Okay, unavailable right now. Okay. How does it look when I make it wider? Okay, cool. That's fine. Okay, maybe just unavailable. Okay, cool. Done. That's PrivyJS in a gist.
Now, you might wonder if you use PrivyJS should you still be using Storybook? And the answer is yes. Storybook is pretty much still a great tool for documenting your components and making sure that you have documented every variant of it. In fact, PrivyJS actually supports displaying your stories. If I go into restaurant menu item stories, you can see that I see the same preview link for each of the stories, and I can see them like that, which is perfect. I can also look at the restaurant header, all these different things. So if you're just exploring a code base that uses Storybook and you use PrivyJS, you have it set up, you probably don't even need to spin up Storybook. And this would probably be a lot faster to just use that instead. Just like that.
In a future version of PrivyJS that I'm hoping to release in June or July, there will actually be also a feature to save your props as a new story. Although I'm probably going to make that part of the PrivyJS Pro version, which is paid, just because at the end of the day I would like to be able to make a little bit of money from that so I can spend all my time working on PrivyJS as opposed to having to squeeze that in my free time. But that's the gist. PrivyJS works really well with Storybook. You can use both. They don't compete with each other. PrivyJS is only possible thanks to a couple of recent web tooling innovations, especially one called VIT, which is pretty much an alternative to Webpack that is significantly simpler to set up and as well, significantly faster. Your developer experience with VIT is that you update a file and within literally milliseconds, it will update. That's why PrivyJS is able to do Privy as you type. The only reason that's possible is because it uses VIT under the hood. If you want to take one thing out of this talk, aside from use PrivyJS, of course, it's use VIT. Try it out.
11. Setting Up PrivyJS and Configuring Components
PrivyJS is easy to set up and can save you many hours per week. Use Visual Studio Code as the compatible IDE and configure a wrapper component to display your components properly. Make sure to import styles.globals.css for a better appearance. If you encounter bugs, report them on GitHub. Visit PrivyJS.com for more information and follow me on Twitter for feedback.
It's easier to set up than you would think, and it might save you literally many, many hours per week. If you want to set up PrivyJS with your project, there are a couple of steps to go through. To be honest, right now, it's not the smoothest experience, unfortunately. Unless you use Create React App or something super simple, in which case it should hopefully just work out of the box.
The first thing is make sure that you use an IDE that is compatible, so either Visual Studio Code or IntelliJ slash WebStorm. If you are not sure which one to use, use Visual Studio Code because the IntelliJ version is kind of buggy, to be honest. Working on that.
And then the next thing is you have to configure it to display your components properly, because by default, what PreviewJS will do is just display the component in a completely empty app, which is not exactly perfect. If, for example, you need some global CSS to be there. The way that you do that is by configuring a wrapper component. And let me show you what that looks like in my hungry app. It's here previewjs.wrapper.tsx, and that's pretty much all the config that we have. That's it, that's all the config. The important thing here is this import styles.globals.css. Without this, it would look super ugly, so let me just show you what I mean. Okay, so I have this, and I'm gonna remove that just for fun. And it's gonna be, yep, really broken. Because, of course, in globals or TSS, in this case, I have all of Tailwind set up. And I also have a wrapper that sets up a screen container, so I'm just adding a little bit of margin to make it prettier. But you might, for example, need to set some react context, some, you know, initialize your redux state, or whatever else you need. And if you use aliases, you know, import aliases, you might also need a couple of lines of config as well.
As I mentioned earlier, PrivyJS is quite new, it just came out in January. So, if you run into any bugs, please consider reporting them on GitHub. I'd really appreciate that. I'm expecting after this talk maybe a couple of hundred bug reports. Hopefully not that many. Go to PrivyJS.com if you want to check it out. If you're on Twitter, you can follow me and share your feedback. I really hope that PrivyJS can make your daily workflow a little bit better and a little bit faster.