<

If you're writing in Markdown, Rocket, an SSG that uses WebComponents, is recommended!

Are you writing blogs or documents in Markdown? Have you ever felt frustrated that you can't reach the itchy parts with just Markdown when you want to focus on writing?

For such people, I recommend Rocket, a static site generator (hereinafter referred to as SSG) that seamlessly integrates Markdown and WebComponents.

Target Readers

  • People who want to focus on writing activities (such as blogs)
    • Using Markdown for writing
  • People who publish written content with SSG
  • People who want to minimize the migration cost of SSG

What is Markdown in the first place?

Markdown is used in various services such as Qiita, Zenn, Hatena Blog (hereinafter referred to as writing services), and writing README.md as a manual for Git repositories.

What was Markdown made for?

Quoting 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 tool for converting PlainText to HTML developed for Web writers. Also, ease of writing and readability are important in Markdown.

Web writers are people who write web content such as blog articles and online advertisements. Yes, it's writing. Markdown is a tool for writing.

Therefore, using Markdown for blog articles and Git repository manuals is appropriate for its purpose. Conversely, using Markdown as a kind of data file, or using Markdown in applications such as shopping and games, goes against its purpose.

Markdown and HTML

Markdown has syntax such as headings, bullet points, and tables. With these syntax, you can write articles structurally.

What should you do if you don't have the syntax you want in writing?

Quoting 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 to indicate that you’re switching from Markdown to HTML; you just use the tags.

You can use HTML in Markdown. If you try writing HTML in the Markdown of a writing service, you should be able to use it.

Considering that the purpose of Markdown is to convert to HTML, it makes sense that HTML can be used. However, using HTML can make it a little harder to read and write, so you should avoid using it too much.

HTML is not enough

If you try using a writing service, you will find that it offers roughly the following features:

  • Embed content
    • If you write a URL, it will display the description, title, and image
  • Table of Contents (TOC) generation
    • It collects the headings of the text and generates a table of contents

These features make the content you write easier to read and improve the efficiency of writing. Of course, Markdown does not have such features. Markdown only defines the syntax, so it does not expect to extend the functionality to Markdown.

However, as you continue to write, you will definitely want these features. Even without these features, you can display content that looks like embedded content by making full use of Markdown notation, and you can manually generate a table of contents. However, it is inefficient to manually update the table of contents every time a heading is added, when you really want to focus on writing.

What should we do about this inefficiency?

Case 1. Extend functionality in the conversion process from Markdown to HTML

Extend the functionality of embedded content and table of contents generation in the conversion process from Markdown to HTML. To make it easier to understand, I will explain using the example of table of contents generation.

I will write the conversion process myself for easy explanation, but I am actually thinking of Hugo, GatsbyJS, MDX, etc.

Converting Markdown to HTML · JavaScript Primer #jsprimer was easy to understand, so I will refer to it.

Let's assume that the Markdown and the conversion process transform.js are the following:

# Header1

Hello, World
// transform.js
const fs = require("fs");
const { marked } = require("marked"); // markdownをhtmlへ変換してくれる

const markdown = fs.readFileSync("README.md", { encoding: "utf-8" });
const html = marked(markdown);
console.log(html);

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

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

As expected, HTML was output. Next is the table of contents generation. In Hatena Blog, if you write the marker [:contents], it becomes a table of contents. By the way, there is a tool called remark that does the conversion process for Markdown.

Let's write the sample code for table of contents generation.

[:contents]

# Header1

Hello, World
// transform.js
const fs = require("fs");
const { marked } = require("marked"); // markdownをhtmlへ変換してくれる

const markdown = fs.readFileSync("README.md", { encoding: "utf-8" });
reMarkdown = markdown
  // TODO: replaceの第2引数の固定を動的に設定
  .replace(/\[:contents\]/g, '<div id="toc"><ul><li>Header1</li></ul></div>');
const html = marked(reMarkdown);
console.log(html);

I think it's a ridiculous code, but it's okay because it's written what I want to convey. 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>

As expected, the table of contents of Markdown has been generated. This is a simple example, but as you extend the functionality, the processing of transform.js increases, and many markers are written in README.md.

Extending functionality in the conversion process has the advantage of being able to entrust functionality to the conversion process. However, it means that Markdown depends on the conversion process. This will incur a migration cost when migrating the conversion process to something else.

Also, embedding a marker in Markdown itself that is neither Markdown notation nor HTML feels a bit strange.

Case 2. Extend functionality with WebComponents

WebComponents is one of the web standard technologies, and it has a feature (Custom Elements) that allows you to customize HTML elements. For example, suppose you developed a HTML element, <generate-toc>, with WebComponents for generating a table of contents. This HTML element is a WebComponents that simply collects all heading texts and displays them in a list.

The image of Markdown is as follows.

<generate-toc />

# Header1

Hello, World

When you apply any HTML conversion process (even the previous transform.js) to this Markdown, you get the following result.

<generate-toc />

<h1 id="header1">Header1</h1>
<p>Hello, World</p>

Since Markdown allows HTML, <generate-toc /> is output as is in HTML. As it is, the browser cannot identify generate-toc. Therefore, you need to load the code that defines generate-toc, that is, WebComponents. For example, you load the following code.

<script>
  class GenerateToc extends HTMLElement {
    constructor() {
      super();
      const shadow = this.attachShadow({ mode: "open" });
      // TODO: 見出しを収集し、箇条書きのHTMLを構築する処理
      shadow.innerHTML = `<div id="toc"><ul><li>Header1</li></ul></div>`;
    }
  }
  customElements.define("generate-toc", GenerateToc);
</script>

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

The advantage of using WebComponents is that it depends on WebComponents, not on the conversion process. There is no problem depending on standard browser technology. Even if you migrate the conversion process, if you have the code for WebComponents, you can achieve the same operation.

Also, as mentioned earlier, even if the following sentence is in Markdown, it does not violate the specifications of Markdown.

<generate-toc />

# Header1

Hello, World

Considering the purpose and specifications of Markdown and the platform called the Web, I think that the combination of Markdown and WebComponents is a good match.

Finally, Rocket Appears

Sorry to keep you waiting, finally Rocket appears.

Rocket is an SSG that can seamlessly integrate Markdown and WebComponents. There is a project to support the development of web standard technologies called Modern Web, and rocket is a subproject of it. Other subprojects include modern-web, which is a test runner and development server, and open-wc, which is for the development, testing, and linting of WebComponents.

Examples of Rocket include the following.

Technically, Rocket is a wrapper for an SSG called Eleventy. Eleventy converts Markdown to HTML. Rocket mixes Modern Web technologies (WebComponents, TestRunner, DevServer) into that Eleventy.

What is Modern Web?

When developing with Javascript, there are many tools to handle, such as Babel's transpiler, ESLint's linter, Jest's tester, and Webpack's builder, which makes things unnecessarily complicated and exhausts developers. Developers know that this complexity can lead to a decrease in agility, which should be focused on development.

Therefore, the aim of Modern Web is to reduce such complexities by developing with web standard technologies such as WebComponents and ESModules.

※ There is also a test runner that tests on the actual running browser, not by mocking the browser API like JSDOM.

Modern Web supports the development of such web standard technologies.

Features of Rocket

There are six features of Rocket written on the Rocket homepage. However, in the flow of this article, I think I should explain about the integration of Markdown and WebComponents, so I will introduce only one feature and omit the others.

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

I believe the appeal of Rocket lies in its ability to stand on the shoulders of giants like Eleventy, Rollup, and Modern Web, which we haven't discussed yet.

From our discussion so far, you might think, "Why not just convert Markdown to HTML with Eleventy and load WebComponents? Do we need Rocket?" Indeed, I think those two alone would be sufficient.

However, having the support of a project like Modern Web can improve development agility. Specifically, there are features like automatic reloading due to changes in Markdown or Javascript, Eleventy's image conversion process, checking of Markdown link destinations, and so on. Well, it's not essential, so Eleventy and WebComponents would be fine, but I use Rocket.

Markdown Javascript

I will explain the integration of Markdown and WebComponents.

Rocket has a feature called Markdown Javascript. This internally uses a library called MDJS. Below is an InfoQ article about MDJS, please refer to it if you like.

Markdown Javascript allows you to write Javascript in Markdown and execute it interactively. For example, suppose you write the following Markdown.

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

If you write this and run it in Rocket, you will see Hello, World on the console screen of the 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);
```

When you run this in Rocket, you will see Hello World on the screen. In this way, WebComponents can be defined in Markdown and executed interactively, so you can use WebComponents immediately.

This is fine for disposable WebComponents, but there are times when you may want to use WebComponents interchangeably. In such cases, it is better to define WebComponents in common places. By writing the WebComponents you want to share in the script header of Numjucks, you can use the defined WebComponents from any Markdown file.

Bare Import support

Rocket uses Modern Web's Development Server internally. The development server has Bare Import support](https://modern-web.dev/blog/introducing-modern-web/#highlights-1).

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

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

In this way, you can specify a relative or absolute path in Bare without being aware of it.

Using libraries from the WebComponents community

You do not have to write your own WebComponents library, but you can use a good one from the following WebComponents community site.

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

Using it is easy. Just write the following Markdown to use <emoji-picker-element>.

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

I am selling an introductory book on WebComponents on Amazon for 500 yen. Although I haven't written about Rocket this time, I do touch on the testing of open-wc.

Also, I have created my portfolio page with Rocket. I also write this blog in Markdown. Please take a look if you like.

In conclusion

The introduction of Rocket ended up being quite at the end. The preamble might have been too long. I hope this can be of some help to someone.

If it was helpful, support me with a ☕!

Share

Related tags