Shuhei Kagawa

Goodbye, Textile

Jan 27, 2020 - Blog, JavaScript

Textile is a markup language that is similar to Markdown. This blog had had posts written in Textile for more than a decade—I feel old now! I removed the Textile files last weekend. This post is a memoir on the markup language.

I started using Textile on a blog engine called Textpattern. I don’t remember exactly when, but probably around 2004 or 2005. I was a university student. Movable Type was the most popular blog engine at the time, but it changed its license towards a more commercial one. Textpattern was a new open-source software. I fell in love with its minimalism. There were not many Textpattern users in Japan. Information in Japanese was very little if not none. I read documentation and forums in English and translated some into Japanese with a few fellows whom I had never met in person.

After a few years, Wordpress became a thing, or I realized it did. Even after I moved to Wordpress, I kept writing in Textile. I liked editing Textile more than editing rich text on WYSIWYG editor. I am not sure whether I had heard of Markdown at the time. But it was not as popular or dominant as it is now.

I started this blog with Textile on Wordpress in 2008 when I started my first job. And I migrated it to Octopress in 2012. I started writing in Markdown with Octopress because it was the lingua franca of GitHub where all the cool things were happening. I had a bit more than a hundred posts in Textile. I kept them in Textile because Octopress supported Textile as well. In 2014, I rebuilt this blog with a handmade static site generator using Gulp. I carried the old Textile files over. I even wrote gulp-textile plugin, which was just a thin wrapper of textile-js. It was my first npm package.

Since then, I implemented a few Markdown-only features like syntax highlighting and responsive table in this blog. The outputs of Markdown and Textile diverged. Last weekend, I wanted to implement responsive images. One more Markdown-only feature. Then I thought it was time to convert the Textile files to Markdown.

Converting Textile to Markdown

I didn’t want to convert a hundred posts by hand. I had tried tomd a few years ago, but I was not satisfied with the result. The old Textile files had raw HTML tags and some classes for styling. Also, I was afraid of missing some details that I don’t remember anymore. So I decided to write a conversion script.

I used textile-js to parse Textile. It turned out that textile-js output HTML string or JsonML. JsonML was new to me. It is basically HTML in JSON format. Each text node is represented as a string. Each element node is represented as an array whose first item is the tag name, an optional second item is an object of attributes and the rest are child nodes.

<a href="https://foo.com"><img src="foo.png" alt="Foo" /> Yay</a>
[
  "a",
  { "href": "https://foo.com" },
  ["img", { "src": "foo.png", "alt": "Foo" }],
  " Yay"
]

I wrote a switch statement to handle tags and added tag handlers one by one.

switch (tag) {
  case "img":
    return /* render <img> */;
  case "a":
    return /* render <a> */;
  default:
    throw new Error(`Unknown tag: ${tag}`);
}

I also added console.log for unknown attributes. In this way, I was able to make sure that all tags and attributes were handled. The script worked well to convert more than one hundred posts. The full script is on Gist.

Responsive images with a static site generator

Jan 26, 2020 - Blog, JavaScript

Responsive images

An img without width/height attributes causes a page jump when it's loaded. It happens because the browser doesn't know the dimensions of the image until its data is loaded—only the first part that contains dimensions is enough though. It's common to specify width and height of img tag to avoid the jump.

<img src="/images/foo.jpg" alt="Foo" width="800" height="600" />

But img tag with width and height doesn't always work well with Responsive Design because the dimensions are fixed. I wanted images to fit the screen width on mobile phones. So I left images without width/height and let them cause page jumps.

Recently, I came across a similar problem at work and learned a cool technique to create a placeholder of the image's aspect ratio with padding-top. If the image's aspect ratio is height/width = 75/100:

<div style="position: relative; padding-top: 75%;">
  <img style="position: absolute; top: 0; left: 0; max-width: 100%;" />
</div>

The div works as a placeholder with the image's aspect ratio that fits the width of its containing element. Because the img tag has position: absolute, it doesn't cause a page jump when it's loaded.

I decided to implement it on this blog. This blog is made with a custom static site generator. I'm not sure if it's useful for anyone else, but I write how I did it anyway…

Limiting overstretch

In addition to images that are wide enough to always fill the full width of the content area, I had images that are not wide enough to fill the full width of a laptop, but wide enough to fill the full width of a mobile phone. Not to stretch the image on laptops, I decided to go with another wrapper to limit the maximum width of the image. If the image's width is 500px:

<div style="max-width: 500px;">
  <div style="position: relative; padding-top: 75%;">
    <img style="position: absolute; top: 0; left: 0; max-width: 100%;" />
  </div>
</div>

Getting image dimensions

The placeholder technique requires image dimensions. I used image-size module to get image dimensions.

The following function gets dimensions of images in a directory and returns them as a Map.

const util = require("util");
const path = require("path");
const { promises: fs } = require("fs");
const sizeOf = util.promisify(require("image-size").imageSize);

async function readImageSizes(dir) {
  const files = (await fs.readdir(dir)).filter(f => !f.startsWith("."));
  const promises = files.map(async file => {
    const filePath = path.resolve(dir, file);
    const dimensions = await sizeOf(filePath);
    return [file, dimensions];
  });
  const entries = await Promise.all(promises);
  return new Map(entries);
}

Custom renderer of Marked

This blog's posts are written in Markdown, and its static site generator uses marked to convert Markdown into HTML. One of my favorite things about marked is that we can easily customize its behavior with a custom renderer.

I used span tags to wrap img tag because they are rendered in p tag, which can't contain block tags like div.

class CustomRenderer extends marked.Renderer {
  image(src, title, alt) {
    const dimensions = this.imageDimensions && this.imageDimensions.get(src);
    if (dimensions) {
      const { width, height } = dimensions;
      const aspectRatio = (height / width) * 100;
      return (
        `<span class="responsive-image-wrapper" style="max-width: ${width}px;">` +
        `<span class="responsive-image-inner" style="padding-top: ${aspectRatio}%;">` +
        `<img class="responsive-image" src="${src}" alt="${alt}">` +
        "</span>" +
        "</span>"
      );
    }
    return super.image(src, title, alt);
  }

  // To set images dimensions when images are changed
  setImageDimensions(imageDimensions) {
    this.imageDimensions = imageDimensions;
  }
}

And CSS:

.responsive-image-wrapper {
  display: block;
}
.responsive-image-inner {
  display: block;
  position: relative;
}
.responsive-image {
  position: absolute;
  top: 0;
  left: 0;
}

Result

Here are a few examples:

Yay, no more page jump! Well, web fonts still make the page slightly jump, but that's another story...

I skipped some details that are specific to my website. The full code is on GitHub.

Winter terminal (mostly Vim) cleaning

Dec 31, 2019 - Vim

In December, I spent some time cleaning up my terminal setup. Dust had piled up in a year, and my terminal was getting slower. It was time to dust off.

Here are highlights of the changes.

Faster text rendering

I noticed a non-negligible lag when I was editing JavaScript/TypeScript in Neovim. At first, I thought some Vim plugins caused it. But it was not true. Not only editing was slow, but also scrolling was slow. Text rendering itself was the problem.

I opened files of different types in Vim's vertical split and less in tmux's vertical split. And I scrolled down and (subjectively) evaluated the smoothness of scrolling.

It turned out that Vim was not the problem. With vertical splits of tmux, even less command was slow to scroll. Regardless of Vim or tmux, text rendering in vertical splits was slow on iTerm2. In retrospect, it makes sense because iTerm2 doesn't know about vertical split by Vim or tmux and can't limit rendering updates to the changed pane. iTerm2's tmux integration may have helped, but I didn't try that.

I tried Alacritty, and it was much faster! I had been using Alacritty before but switched back to iTerm2 for font ligatures. Now I didn't care much about font ligatures—ligatures look pretty, but glyphs for != and !== confused me in JavaScript. So I switched to Alacritty again.

Also, I stopped using flatlandia color scheme in Vim, and it improved the rendering speed a bit. I didn't dig into why, though.

fzf.vim

fzf.vim was a life changer. It provides a blazing fast incremental search for almost anything. I use it for file names (instead of ctrlp.vim), commit history and grep. Especially, incremental grep with a preview is amazing.

More Vim cleaning

  • Started using ale as a Language Server Protocol client. I was using ale for linting and fixing, and LanguageClient-neovim for LSP features. LanguageClient-neovim also shows a quickfix window when a file contains syntax errors and was conflicting with ale. I learned that ale supported LSP as well and made it handle LSP too.

    • Update on Jan 3, 2020: I started using coc.nvim instead of ale and deoplete.nvim for autocomplete, linting, fixing and LSP features. It makes Vim an IDE. Simply incredible.
  • Configured Vim to open :help in a vertical split. :help is a valuable resource when configuring Vim. The problem for me was that Vim opens help in a horizontal split by default. Opening help in a vertical split makes it much easier to read.

    autocmd FileType help wincmd H
    
  • Sorted out JavaScript/JSX/TypeScript syntax highlighting. Vim sets javascriptreact to .jsx and typescriptreact to .tsx by default. But those file types don't work well with the plugin ecosystem because plugins for javascript/typescript file types don't work with javascriptreact/typescriptreact and popular JSX/TSX plugins use javascript.jsx and typescript.tsx.

    autocmd BufRead,BufNewFile *.jsx set filetype=javascript.jsx
    autocmd BufRead,BufNewFile *.tsx set filetype=typescript.tsx
    
  • Stopped unnecessarily lazy-loading Vim plugins with dein.vim. I had configured file-type-specific plugins as lazy plugins of dein.vim without understanding much. The truth was that lazy plugins are meaningful only for plugins with plugin directory. Most of the file-type-specific plugins don't have plugin directory and are lazily loaded by default with ftdetect and ftplugin. :echo dein#check_lazy_plugins() shows those plugins that are ill-configured. I finally learned what those plugin directories do after using Vim for several years...

  • Reviewed key mappings and removed waiting time by avoiding mappings that prefixed other mappings. For example, I had mappings of ,g and ,gr. ,g was slow because Vim had to wait for a while to determine it was ,g or ,gr.

  • Tried Vim 8 but switched back to Neovim. Vim 8 worked well, but tiny details looked smoother in Neovim. For example, when syntax highlighting hangs up, Vim 8 hangs up while Neovim disables syntax highlighting and goes on.

  • Started documentation of my setup. I keep forgetting key mappings, useful plugins that I occasionally use, how things are set up, etc.