Following the release of Deno v1.0 I got excited to try my hands at it. These are my first experiences writing a simple tool in Deno.
A super fast introduction to Deno:
Deno is the spiritual successor of Node trying to fix design mistakes that were made early on but recognized only late into the project. Deno supports TypeScript out of the box and relies on web-standards. In Deno you can import ES modules from any URL and use fetch like you would in the browser. To help unify the community on processes and workflows Deno provides a wide array of stdLibs and has build in solutions for bundling, testing and code formatting. You can read more in the Deno v1 release post.
To try my hands at Deno and collect some experience on how ready it is I decided to implement a simple cli. The goal: read all markdown files in the current directory, parse them to HTML, add a navigation and output them into a new folder. I also decided to use new.css as a class-less CSS library to style the pages.
You can go and checkout the finished repo or an example website created.
Organizing
First things first. A plan on how to implement the described tool. We will tackle it through the following steps:
- Walk through all files in the directory, getting a list of markdown files.
- Iterate over all those files and parse them into HTML.
- Add a navigation and header to each page.
- Output the files.
While following these steps we will see Denos features in action. Let's get started.
If you are a VSCode user I highly recommend the Deno extension for VSCode
Getting the files
First we need to walk through the files and find all the markdown ones. Lucky for us Denos fs stdLib includes a walk function that does just that.
The docs also tell us how to use this correctly:
async function printFilesNames() {
for await (const entry of walk(".")) {
console.log(entry.path);
}
}
But we only want markdown files and maybe set some more options for this.
After some digging I found the available options in walks source. Which makes this entire thing feel quite immature. But equipped with that knowledge I arrived at a function to get all markdown files from a directory.
const findMarkdownFiles = async () => {
const result = []
for await (const entry of walk('.', walkOptions)) {
result.push(entry)
}
return result
}
Feel free to correct my coding here (still new to generator functions).
On the way though I noticed that the docs were incorrectly not providing a path (e.g: "."
) as the first argument to walk
. Quickly opened a PR to update the docs so the next person won't face the same half hour of struggling.
Outputting the result we have our list of files that we want to work with, Step 1 ✅
Parse the markdown files to HTML
There are a TON of libraries on NPM that you can use to parse Markdown into HTML. Sadly they do not export ES Modules...
After spending some time digging through pika.dev which feel like as close as you can currently get to a central place of searching for ES Module dependencies, I was stuck. For some time I tried different libraries, importing them in different ways but usually they were missing some dependency. There is currently an initiative to support Node modules in Deno but it's still underway. And most NPM packages simply are not ES Modules.
In the end I found snarkdown which didn't have any active development for a year and only supports very basic markdown parsing. It will be good enough for this example project but no where near anything I would want for a real project.
Bringing it in via pika was a breeze and I also enjoyed playing around with the REPL feature on pika.
import snarkdown from 'https://cdn.pika.dev/snarkdown/^1.2.2'
const parseMarkdown = (mdFile: MDFile): HTMLFile => {
const html = snarkdown(mdFile.content)
return {
originalPath: mdFile.path,
originalName: mdFile.name,
path: mdFile.path.replace(/md$/, 'html'),
name: mdFile.name.replace(/\.md$/, ''),
content: html,
}
}
It's not sophisticated, it took waaaaay longer than I would want to find a module to use, but parsing is working. Step 2 ✅
Add a navigation
This is the part where native TS support becomes awesome. You might have seen in the code above that I created some types for the objects I am handing around. Thanks to that it is just super easy to know what exactly I have available later down the pipe.
On a side note a painful lesson I learned in an earlier project is that collecting all items is a necessity for supporting features like a navigation in a markdown to static website tool.
Equipped with that and some String Literals we can easily wrap the created HTML inside a whole HTML page with a nice title, navigation and inclusion of the new.css styles.
const addSurroundings = (headerCreator: (title: string) => string) => (
htmlFile: HTMLFile
): ParsedFile => {
const content = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${htmlFile.name}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/open-fonts@1.1.1/fonts/inter.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css">
</head>
<body>
${headerCreator(htmlFile.name)}${htmlFile.content}
</body>`
return {
path: htmlFile.path,
content,
}
}
And we have files with a navigation and styling, ready to be saved. A simple step, without dependencies. Greatly aided through TypeScript, Step 3 ✅
Saving new files
Here I started to get into the thick of things where in the end I searched for help on Denos Discord.
In theory Deno also has a stdLib to save files. It's also part of fs and called writeFileStr. The docs are also straight forward, just import it all from the mod.ts file. That is the current convention to export a module for Deno; create a mod.ts that exports everything.
Unfortunately just using an import as the docs say gave me an error at runtime. Reading through the docs I realized that:
All the following modules are exposed in mod.ts This feature is currently unstable. To enable it use deno run --unstable
Okay so I have to use --unstable
, that's gonna make my little tool look trustworthy to users. But, let's give it a go.
After trying that I ran into the next error....
This time, even though I am importing all the modules from master there seems to currently be an incompatibility. Luckily the stdLibs come versioned and the version can be added to the URL to fix all dependencies to the same. There is an issue about this, if you want to dive deeper into the topic.
Long story short: after some friendly discussions on and great help from Denos Discord server I opted to fix the versions of my dependencies and import them from the sub modules to not force my users to use --unstable
.
// We use:
import { ensureDir } from 'https://deno.land/std@0.51.0/fs/ensure_dir.ts'
// Instead of:
import { ensureDir } from 'https://deno.land/std/fs/mod.t'
It was an adventure that taught me a lot about Denos state and internals but finally, Step 4 ✅
And we can move onwards to using the tool.
Using it
With Deno installed you can try the tool right now.
deno run --allow-read --allow-write https://raw.githubusercontent.com/HoverBaum/md_website/v1.0.0/index.ts
Deno can simply run scripts from URLs which feels refreshingly easy to get started with. The above uses a version to make sure it keeps being the tool this post talks about.
You can also install the script and make it generally available on your machine using the following commands.
deno install --allow-read --allow-write --force --name mdw https://raw.githubusercontent.com/HoverBaum/md_website/v1.0.0/index.ts
This will install version 1.0.0. It also sets permissions that the tool needs to run. We need read and write access to read the Markdown files and write the parsed HTML. --force
makes sure to replace previous installations and --name mdw
aliases the cli to mdw
in your terminal.
Conclusion
On this journey I go to explore many aspects of Deno, learn a lot and build a fun little tool. All in all I enjoyed working with Deno. But let's take a closer look at where things are at.
Standardisation and Modules
Deno is just starting out with v1.0 being about a week old at time of writing this. And Deno opted to not use a central repository but instead distributed hosting. We will need to see whether federations of Module repositories arise that can leverage the potential of this distributed architecture or if Pika (or similar) will rise to be the NPM for Deno.
The bigger issue I found in my small project was the unavailability of ES Modules to support my usecase. That might have been something specific for me though. Generally I am a huge fan of going with Webstandards as they are here to stay and I think this issue will continue to grow smaller, as everyone races to build the "awesome X for Deno" Module.
Community
Denos community is currently hyper-active, as everyone is racing to build the cool things and new standards for X in Deno. I experienced the GitHub issues to be active and the Discord server to be a welcoming place.
There are a couple of discussion points that have potential to split the community, like distributed dependencies but all in all I feel Denos community is a great and growing one.
Last words
Developing my first little tool in Deno was fun. The entire ecosystem does not yet feel production ready, even though it reached v1.0. But Deno is surely a technology you want to keep on your radar and evaluate.
Top comments (0)