DEV Community

Cover image for If you're writing in Markdown, I recommend Rocket, an SSG that uses WebComponents!
silverbirder
silverbirder

Posted on

If you're writing in Markdown, I recommend Rocket, an SSG that uses WebComponents!

※ This article is an English translation by Deepl of a Japanese article at https://silver-birder.github.io/blog/contents/intro_rocket/ .

Do you write your blogs and documents in Markdown?
Have you ever felt the frustration of wanting to focus on writing but not being able to get the itch to do so with just Markdown?

If so, I recommend Rocket, a static site generator (henceforth called SSG) that seamlessly integrates Markdown and WebComponents.

target audience

  • People who want to focus on writing (e.g. blogging).
    • People who use Markdown for writing
  • People who publish their written content in SSG
  • People who want to reduce the cost of SSG migration as much as possible

What is Markdown, anyway?

Markdown is used in many services such as Qiita, Zenn, Hatena blog, and so on (called "writing services"), and also in README.md for Git repository.

What is the purpose of Markdown?

The following is a quote from Daring Fireball: Markdown.

Markdown is a text-to-HTML conversion tool for web writers. Markdown allows you to write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML).

Markdown is a PlainText to HTML conversion tool developed for Web writers.
It is also important for Markdown to be easy to write and read.

A web writer is someone who writes content for the web, such as blog posts or online advertising text.
Markdown is a tool for writing.

Markdown is a tool for writing, so using it for blog posts and Git repository documentation is a good fit.
On the other hand, using Markdown as a kind of data file for its structural features, or using Markdown for applications such as shopping or games, defeats the purpose.

Markdown and HTML

Markdown has a notation (syntax) for headings, bullets, tables, and so on.
You can use these notations to write articles in a structured way.

What if you don't find the notation you want in your writing?

This is from Daring Fireball: Markdown Syntax Documentation.

For any markup that is not covered by Markdown's syntax, you simply use HTML itself. there's no need to preface it or delimit it For any markup that is not covered by Markdown's syntax, you simply use HTML itself.

You can use HTML in Markdown. If you try to write HTML in Markdown, a writing service, you will probably be able to use it.

Given that the purpose of Markdown is to convert to HTML, I think it makes sense that HTML can be used.
However, use of HTML makes reading and writing a bit worse, so heavy use of it should be avoided.

HTML is not enough.

As you will see when you use the writing service, it provides roughly the following features

  • Embedded content.
    • When you write a URL, it will display the description, title, and images.
  • Table of Contents (TOC) generation
    • Generates a table of contents by collecting the headings of the text.

These features make the content you write easier to read and improve the efficiency of your writing.
As a matter of course, such features do not exist in Markdown.
Markdown only defines a notation, so we don't want to extend Markdown's functionality.

However, as you write, you will inevitably want those features.
Even without those features, I can use Markdown notation to display embedded content, and I can generate a table of contents manually.
However, it's inefficient to manually update the table of contents every time there are more headings, when you should be concentrating on writing.

What should we do about this inefficiency?

Extend the functionality of the Markdown to HTML conversion process.

In the Markdown to HTML conversion process, we can extend features such as embedded content and table of contents generation.
I'll use the table of contents generation as an example, because I think it's easier to understand if we talk about it in concrete terms.

For the sake of clarity, I'll write the conversion process myself, but originally, I'm assuming Hugo, GatsbyJS, MDX, etc.

I'll refer to Markdown to HTML - JavaScript Primer #jsprimer as it was just easy to understand.

Let's assume that Markdown and the transform.js for the conversion process are as follows

<! -- README.md -->
# Header1
Hello, World
Enter fullscreen mode Exit fullscreen mode
// transform.js
const fs = require('fs');
const { marked } = require('marked');

const markdown = fs.readFileSync('README.md', { encoding: 'utf-8' });
const html = marked(markdown);
console.log(html);
Enter fullscreen mode Exit fullscreen mode

transform.js is very simple: it just converts README.md to html and outputs it to the standard output.
Let's run it.

$ node transform.js
<h1 id="header1">Header1</h1
<p>Hello, World</p>.
Enter fullscreen mode Exit fullscreen mode

As expected, HTML has been output. The next step is to generate a table of contents.
In Hatena Blog, if you write the marker [:contents], the table of contents will be generated there.
As a digression, there is a tool called remark that will convert the contents to Markdown.

Here is a sample code to generate a table of contents.

<! -- README.md -->
[:contents]

# Header1
Hello, World
Enter fullscreen mode Exit fullscreen mode
// transform.js
const fs = require('fs');
const { marked } = require('marked');

const markdown = fs.readFileSync('README.md', { encoding: 'utf-8' });
reMarkdown = markdown
             .replace(/\[:contents\]/g, '<div id="toc"><ul><li>Header1</li></ul></div>');
const html = marked(reMarkdown);
console.log(html);
Enter fullscreen mode Exit fullscreen mode

I think it's very silly code, but it does what I want it to say, so it's fine.
Let's run it.

$ node transform.js
<div id="toc"><ul><li>Header1</li></ul></div>

<h1 id="header1">Header1</h1>
<p>Hello, World</p>
Enter fullscreen mode Exit fullscreen mode

As expected, a Markdown table of contents has been generated.
This is a simple example, but as we extend the functionality, more processing will be done in transform.js, and more markers will be written in README.md.

Extending functionality to the transformation process like this has the advantage of letting the transformation process take care of the functionality.
However, it also means that Markdown is dependent on the conversion process.
This incurs a migration cost when migrating to a different conversion process.

Also, it's a bit uncomfortable to bury markers in Markdown itself that are not in Markdown notation or HTML.

Suggestion 2: Use WebComponents to extend the functionality.

WebComponents is a web standard technology that allows you to customize HTML elements (Custom Elements).
For example, let's say you have developed an HTML element, <generate-toc>, for generating a table of contents using WebComponents.
Let's say this HTML element is just WebComponents that collects all the heading text and displays it as bullets.

The Markdown image would look something like this

<! -- README.md -->
<generate-toc />

# Header1
Hello, World
Enter fullscreen mode Exit fullscreen mode

If you convert this Markdown into any HTML (even with transform.js), you will get the following result

<generate-toc />

<h1 id="header1">Header1</h1>
<p>Hello, World</p>
Enter fullscreen mode Exit fullscreen mode

Since Markdown allows HTML, <generate-toc /> will be output as HTML as is.
If this is not the case, browsers will not be able to identify generate-toc. Therefore, you need to load the code that defines generate-toc, i.e. WebComponents.
For example, you can load the following code

<script>
  class GenerateToc extends HTMLElement {
    constructor() {
      super();
      const shadow = this.attachShadow({mode: 'open'});
      shadow.innerHTML = `<div id="toc"><ul><li>Header1</li></ul></div>`;
    }
  }
  customElements.define('generate-toc', GenerateToc);
</script>
Enter fullscreen mode Exit fullscreen mode

Now that the browser can identify generate-toc, the table of contents will be displayed as expected.

The advantage of using WebComponents is that it is independent on the conversion process and dependent on WebComponents. There is absolutely nothing wrong with relying on standard browser technologies.
Even if you migrate the conversion process, the same behavior can be achieved with WebComponents code.

Also, as a restatement, the following text in Markdown does not violate the Markdown specification

<! -- README.md -->
<generate-toc />

# Header1
Hello, World
Enter fullscreen mode Exit fullscreen mode

Considering the purpose and specification of Markdown and the web platform, I think the combination of Markdown and WebComponents is a good match.

Finally, Rocket is here!

Sorry for the wait, but Rocket is finally here.

Rocket is an SSG that allows seamless integration of Markdown and WebComponents.
There is a project to support the development of web standard technologies called Modern Web, and rocket is a subproject of that project.
Other sub-projects are test runner and development server, modern-web for development server, and open-wc for WebComponents development, testing, and linter.

Some examples of Rocket include.

Rocket is technically a Wrapper for an SSG called Eleventy.
Eleventy converts Markdown to HTML, and Rocket mixes Eleventy with Modern Web technologies (WebComponents, TestRunner, DevServer).

What is Modern Web?

When developing with Javascript, there are a lot of tools to deal with: Babel transpilers, ESLint linters, Jest testers, Webpack builders, and so on.
Developers know that the complexity of these tools leads to a decrease in agility when they should be focusing on development.

Therefore, the Modern Web aims to reduce the complexity of development by using Web standard technologies such as WebComponents and ESModules.

There are also test runners like JSDOM that do not test by mocking the browser API, but by using the browser that is running.

Modern Web supports the development of such web standard technologies.

Features of Rocket

On Rocket's home page, you can find six features of Rocket.
However, I think I should explain the integration of Markdown and WebComponents in the flow of this article, so I will only introduce the following one feature and skip the others.

  • Meta Framework
    • Build on top of giants like Eleventy, Rollup, and Modern Web.

I think Rocket's appeal lies in the fact that it rides on the shoulders of giants like Eleventy, Rollup (which I hadn't talked about), and Modern Web.

You may be thinking, "Why do I need Rocket when I can just use Eleventy to convert Markdown to HTML and load WebComponents? Who needs Rocket? In fact, I think those two are enough.

However, if you have a project support called Modern Web, your development agility will be improved.
Specifically, it provides automatic reloads for Markdown and Javascript changes, Eleventy's image conversion process, Markdown link checking rocket.modern-web.dev/docs/tools/check-html-links/).
Well, it's not essential and Eleventy and WebComponents are fine, but I'll use Rocket.

Markdown Javascript

Let's talk about the integration of Markdown and WebComponents.

Rocket has a feature called Markdown Javascript. It internally uses a library called MDJS.
Here is an InfoQ article about MDJS, if you want to read it.

Markdown Javascript allows you to write Javascript into Markdown, with the ability to run it interactively.
For example, let's say you wrote the following Markdown

```js script
console.log('Hello, World');
```

When you write this and run it in Rocket, you will see Hello, World on the console screen of your browser's development tool.
You can also apply this to define WebComponents.

```js script
class MyDiv extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
shadow.innerHTML = Hello, World;
}
}

customElements.define('my-div', MyDiv);
```

<my-div></my-div>
Enter fullscreen mode Exit fullscreen mode

When you run this in Rocket, you will see Hello World on the screen.
As you can see, you can define WebComponents on Markdown and execute it interactively, so you can use WebComponents immediately.

This is fine if you are using disposable WebComponents, but there are times when you need to use them all the time.
In such a case, it is good to define WebComponents in the common place.
If you write WebComponents in the script header of Numjucks, you can use the defined WebComponents from any Markdown.

Support for Bare Import

Rocket uses Modern Web's Development Server internally. The development server has support for Bare Import.

Here is an example of Bare Import.
Assuming you have installed npm install canvas-confetti beforehand, the following Markdown will be executed by confetti().

```js script
import confetti from 'canvas-confetti';
confetti();
```

As you can see, you can use Bare to specify relative or absolute paths without being aware of them.

Using libraries from the WebComponents community

If you don't want to write your own WebComponents, you can use one of the following WebComponents community sites that looks good.

For example, let's say you want to use a WebComponents called emoji-picker-element. emoji-picker-element element resembles the UI of an emoji keyboard, which can be displayed by pressing command + control + space key on a Mac.

The usage is simple.
To use it, simply install it with npm install emoji-picker-element as before, and then you can use <emoji-picker-element> by writing the following Markdown.

```js script
import 'emoji-picker-element';
```

<emoji-picker></emoji-picker>
Enter fullscreen mode Exit fullscreen mode

Advertising

An introductory book about WebComponents is available on Amazon for 500 yen.
It doesn't write about Rocket this time, but it mentions the testing of open-wc.

I have also created my portfolio page in Rocket. This blog is also written in Markdown. Please check it out if you like.

In closing

I'm sorry that the introduction of Rocket is so far behind. It may have been too long a preamble.
I hope it will be of some help to someone.

Top comments (1)

Collapse
 
dannyengelman profile image
Danny Engelman • Edited

HTH,

constructor() {
      super()
        .attachShadow({mode: 'open'})
        .innerHTML = `<div id="toc"><ul><li>Header1</li></ul></div>`;
    }
Enter fullscreen mode Exit fullscreen mode