Vite Building a Markdown Plugin

Check out a free preview of the full Vite course:
The "Building a Markdown Plugin" Lesson is part of the full, Vite course featured in this preview video. Here's what you'd learn in this lesson:

Steve demonstrates how to create a Vite plugin that transforms Markdown files into HTML. They install the "markdown-it" package, create a helper function to read and render Markdown files, and then create the plugin function that intercepts the loading of Markdown files and returns the transformed HTML. They also mention that this technique can be extended to perform other custom transformations on files.

Get Unlimited Access Now

Transcript from the "Building a Markdown Plugin" Lesson

>> So with that, let's do one that transforms Markdown. Let's see. Where's my VT starter? So, let's do real quick, let's just make sure, whatever I did last in here. Just a second, let's make a new branch and we'll call it, what we'll do off of main, we'll call it Markdown plugin.

[00:00:43] Cool, and we do need to install something that processes Markdown. My favorite is re-mark and re-hype, and then there's this something called Unified, which does both of them, but there's a lot of setup and boilerplate in that one. It's a really powerful and really cool and it has a lot of, that's what's effectively powering this website.

[00:01:03] I'm gonna use the simpler one because watching me configure a really powerful Markdown parser is not what we signed up for. But we'll kinda just show what the pattern can look like here. So, do npm install. There's one called Markdown, it's simple. It's fine. And we'll go and we'll pull that in.

[00:01:30] And you can break this out into its own file. If you're gonna publish it on npm as a library per se, cause we know how to do that now. The convention is vite-plugin-, something that's not taken. Or you can do like namespaced at your username/vite plugin, whatever. But since it's just a function like the ones that we have in our code base, my day job.

[00:01:55] So I think it'll you like, it's an open source server. I just want to spin it up when I spin up my development server. So I have a plug in that basically, I just type in npm Dev and it spins up both the server that I'm like building the UI for that I need to talk to you as well as the development beat server at the same time.

[00:02:13] That's a plug-in that we wrote, right? Unique to my use case, now I don't have to open up two tabs and type npm start twice. Little things like that become super powerful. And so with this, I'm just gonna write it in this file because again, it is just a function at the end of the day.

[00:02:29] I'll say import Markdown from markdown-it. And that actually you have to instantiate a new class for reasons I don't fully understand. .So that's gonna be our Markdown parser. Cool, we're also gonna need to read that file from the file system, maybe. Actually, I don't think we do. Because I'm gonna replace the load function, because out of the box, Vite can only actually, it will try to treat everything as JavaScript unless you step in during a load function.

[00:03:08] So we'll just make ourselves a little bit of a helper function here. We'll say const renderMarkdown. Cool and that's gonna make it async function give me a file. We are gonna need to like read that file from the file system. And so I'm gonna pull in the promise version of that.

[00:03:28] So I'll import read file from fs promises, cool. And then what we'll do is we will say content equals read file. If it's import content from file name. That's what this argument is gonna be, right? It's gonna be the path, we've seen that source slash whatever in this case.

[00:04:02] So we will read file, whatever the file is. So we'll say file, and then I'm just gonna say utf-8, so I don't end up with a buffer and node, right? So read the file and then, we can really just empty that render, that content, right? That's just a little function that give it a file, reads the file from the file system, turns the Markdown into HTML, right?

[00:04:31] There with I think, remark, you can actually get an AST that you can manipulate and do all sorts of stuff with as well. I'm choosing not to do that I'm just gonna get a string that we can then use that in HTML somewhere if we wanted to very dangerously.

[00:04:48] Yes, if you're doing this for really real use something like remark, I'm using this plugin in the actual app that is MDX, but it's first felt same thing, right? But this is mostly to demonstrate what we would do to make it in the plugin piece, cool. So that's a render Markdown.

[00:05:06] That's just a helper file. And then our plugin is pretty straightforward. Let's call it function markdownToHtml. Now you'll notice that I used a regular old function declaration, and that is intentional. The reason for that is that if you use an arrow function, it's bound to whatever this is in the scope that you wrote it, but these actually have access to this dot inside of rollup.

[00:05:34] So you wanna make sure that you're not binding the wrong this, so just use a function declaration. You do need to give it a name, that's mostly for debugging purposes, we'll just call it markdown-to-html and then we'll say resolveId, and this is basically to say, is this one that we personally deal with.

[00:06:05] I just think it's got to return something. So if the id ends with .md, Then return it. I gotta return an object. I'm so used to writing arrow functions. I was like why am I getting squigglies? Nobody wants squigglies. Resolve the id, otherwise return null, or you can return undefined.

[00:06:38] It's basically saying we don't resolve this one. Format it a little bit better. And then finally, now we know that that returns a promise because it reads in the file system. So make this async function, we say load, that id. And we'll say, again, I'm gonna guard it with ends with.

[00:07:09] If you're importing a file that ends with a Markdown, then I want to deal with it. And so we'll say const rendered. Render Markdown for that id. The same way when we saw that JSON named exports, it was taking the JSON file and converting it into an ES module.

[00:07:32] It's effectively all we're really doing here, right? Is like, cool, return me something that the rest of Vite understands, and Vite understands ES modules, right? Could you move what I'm about to write into another file and interpolate it in? Absolutely, should you probably. This is short enough that I can write it in line.

[00:07:52] And so, I'll just say export default, and then interpolate it in to something that we can get stringified, that'll wrap it in quotes and everything along those lines as well. And so now, what'll happen is when we go to read a file, it's like, you want a Markdown file?

[00:08:17] Okay, I'm gonna try to read, I'm gonna intercept the one, I'm gonna read it from the file system. I'm gonna turn it into Markdown, and I'm gonna give you back a string of HTML in this case, right? Obviously, you could extend this in all sorts of ways. If you want to process this file in any way, shape, or form.

[00:08:34] There was a question in the chat earlier, what if I want to put a global import on top of every single file that has a file extension. So let's say, I want to import React and maybe I wanna import my component library at the beginning of every file that ends with a certain file extension, right?

[00:08:53] You could literally, at this point we're replacing the file, but we could theoretically just append to the current contents of the file as well. And or prepend and say like, put this thing at the very beginning or in development put a console log or something like that about what the file name is, right?

[00:09:13] Whatever you wanna do or like do something with the source map or replace a special string with something else. So you can do whatever you want to every file, again, most of the common ones have plugins, there's a ton of them and they're really super powerful. You should look at some of them because I'll give you some inspiration, but a lot of times if you have to go on the path of writing your own it is likely for a reason.

[00:09:38] Sweet, assuming that my talking while I typed it didn't create any issues, we should be able to pop this in as another plugin and that should be great. Yeah, it's a very simple one, only gonna make HTML. It's not gonna give me a lot of manipulation but there are other tools that can totally do that and super useful.

[00:10:05] Again, there's some file format that you already have a parser for. There's just no Vite plugin. You can use this technique and now have full ability to import it and use it in your app. You just got to turn out a some kind of ES module in the end, Dustin.

>> Could you exclude some files from a specific dependency?
>> I mean, we are checking for which ones we want to include. So, it's just JavaScript. Your logic can go either way, right? You can say, if not a JavaScript file, do this. Now, that's obviously a wider range than what you are actually looking for.

[00:10:38] But one of the ways that I have not used this yet, that we are at least talking about is like I said, we have two different builds of our app. Right now, there are conditionals and they're based on environment variables. What I kinda wanna do is look at, hey, when you import a file, if it's just the file and there's no maybe .oss or .cloud, pull in that file.

[00:11:01] But depending on the environment, go actually swap it out for this other file. There's a bunch of use cases that'll be very specific. I'm only opt to end .md, but you could say it's just up to you, whatever logic you want, right? And you will have the name of the file and with I think, I forget which one.

[00:11:25] There's another one, I think is maybe just regular resolve, where you get not only the file you are importing but the file that tried to import it, right? And so you can do interesting stuff along those lines as well