※この記事はLIGアドベントカレンダー2018の15日目です。
こんにちは。バックエンドエンジニアのまさくにです。
LIGにおいて逃れえぬ仕事のひとつに「LIGブログを書く」という作業があるのですが、僕の場合、この仕事の中に「マークダウンからLIGブログ用のフォーマットを作り出す」という非生産的な作業が含まれています。
僕はたいていの文章をマークダウンで書いていて、このブログ記事も一度マークダウンで書き出してから、WordPressに取り込み、それからLIGブログ用のhtmlに直して公開しているため、この置換作業をこれまでイヤイヤやってきました。
マ ジ な ム ダ。
そこで今回は、マークダウンからLIGブログ用のフォーマットへ変更するためのツールを考えたいと思います。
作りたいものと選定
単純に「マークダウンをhtmlに変更したい」っていうわけではありません。LIGブログには、たとえば下記のようなルールがあります。
- aタグは内部サイトであれば同一ウィンドウ、外部サイトであれば別ウィンドウ
- codeタグはpreタグで囲む
どうせならそこまで整形したい。世の中に出ている単純なマークダウンのパーサーではなく、独自でカスタマイズができなければなりません。
また、自分だけが使えるものであれば、たぶん僕の場合、Vim Scriptを書いたり、コマンドで置換したりするのが一番使いやすい気もしますが、どうせ社内でも困ってる人がいそうなものなので、みんなが使えるものがいいでしょう。
そこでElectronを使ってみることにしました。
前々から触ってみたかったですし、なんか色々そろってそうですし。
ついでに少し調べたところ、markedというマークダウンのパーサーが色々カスタマイズできて便利そうでした。
ただ、そもそもjsを書く機会があまりないんですが、できるのでしょうか。
とりあえず最新のNode.jsを入れる
nvmで最新のNode.jsを入れておきます。
$ nvm ls-remote
# 最新のLTSを確認
# 2018/12/03時点、v10.14.1だったのでインストール
$ nvm install v10.14.1
$ nvm use v10.14.1
Now using node v10.14.1 (npm v6.4.1)
# 使えるようになりました。
$ node --version
v10.14.1
プロジェクトを作る
次にプロジェクトのディレクトリを作ります。
# プロジェクト名はmd2lb(マークダウン to LIG Blog)にしました
$ cd ~/Desktop/works/tmp && mkdir md2lb && cd md2lb
npmでpackage.jsonを対話的に作ります。
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (md2lb)
version: (1.0.0) 0.0.1
description: Markdown to LIG Blog Format
entry point: (index.js) main.js
test command:
git repository:
keywords:
author: masakuni
license: (ISC)
About to write to /Users/ito_masakuni/Desktop/works/tmp/md2lb/package.json:
{
"name": "md2lb",
"version": "0.0.1",
"description": "Markdown to LIG Blog Format",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "masakuni",
"license": "ISC"
}
Is this ok? (yes) yes
最後にElectron本体と、マークダウンをパースしてくれるmarked、Electronでログ出力するための electron-log 、Electronをビルドするためのelectron-packager もインストールしておきましょう。
この辺はいろいろ進めていくうちに必要になったモジュール群ですが、いまのうちにインストールしてしまいます。
$ npm install --save-dev electron
$ npm install --save marked
$ npm install --save electron-log
$ npm install --save electron-packager
コードを書く
次にコードを書いていきます。
はじめに読み込まれるmain.js
とりあえずこのコードはElectronのQuick Startで書かれていたものと同一です。
const { app, BrowserWindow } = require('electron')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow () {
// Create the browser window.
win = new BrowserWindow({ width: 800, height: 600 })
// and load the index.html of the app.
win.loadFile('index.html')
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
アプリケーションの画面index.html
本当にただのhtmlで、この程度だと特別なことを意識する必要はないのですね。テキストエリアとボタンをひとつ、粗雑に付けてあります。テキストエリアにマークダウンを入れてボタンを押すと、クリップボードにLIGブログのフォーマットでコピーされている、という動作です。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Markdown to LIG Blog Format</title>
<script src="./js/index.js"></script>
</head>
<body>
<h1>Markdown to LIG Blog Format</h1>
<div>
<textarea id="text" rows="8" cols="50"></textarea>
</div>
<div>
<button id="replace">replace & copy</button>
</div>
</body>
</html>
画面からイベントを取得するjs/index.js
画面のローディングが終わって、テキストエリアにマークダウンが書かれ、ボタンが押されたとき、markDownのモジュールを実行するようにEventListennerを作っておきます。
'use strict'
const electron = require('electron')
const clipboard = electron.clipboard
const remote = electron.remote
const markDown = remote.require('./lib/markDown')
document.addEventListener('DOMContentLoaded', () => {
let button = document.getElementById('replace')
button.addEventListener('click', function () {
let markdownText = document.getElementById('text').value
if (markdownText) {
let html = markDown.toLigBlog(markdownText)
clipboard.writeText(html)
}
})
})
マークダウンからhtmlを返すlib/markDown.js
marked はとても優れたモジュールのようなのですが、LIGブログのフォーマットに合わせる必要があります。Rendererクラスを上書きすることによって、各タグで返す文字列を変更できるようでした。
下記ではhタグとlinkタグ、codeタグをマークダウンからhtmlに取得するルールをカスタマイズしています。
'use strict'
const url = require('url')
const marked = require('marked')
const renderer = new marked.Renderer()
renderer.heading = function (text, level) {
return `<h${level}>${text}</h${level}>`
}
renderer.link = function (href, title, text) {
let aTag = ''
// liginc.co.jp内のリンクなら同一ウィンドウへ、それ以外は別ウィンドウへ
let parsedUrl = url.parse(href)
if (parsedUrl.hostname === 'liginc.co.jp') {
aTag = `<a href="${href}">${text}</a>`
} else {
aTag = `<a href="${href}" class="link" target="_blank">${text}</a>`
}
return aTag
}
renderer.code = function (code, language) {
return `<pre><code>
${code}
</code></pre>`
}
const markDown = {
toLigBlog: function (markdownText) {
let html = marked(markdownText, {
renderer: renderer,
headerIds: false
})
return html
}
}
module.exports = markDown
テスト実行してみる
ここまで書くとElectronのアプリケーションが実行できるようになりました。下記一行をpackage.json内の scripts 内に記載してください。
"start": "electron .",
下記コマンドで実行できるかと思います。
$ npm run start
この上なく雑な画面ですね。
使ってみる
ここでテキストエリアに下記マークダウンを入れて、replace & copy をクリックしてみます。
# h1タグだよー
## h2タグだよー
LIGブログは[ここ](https://liginc.co.jp/)です!
*コードも書ける*
```
if (true) {
return false;
}
```
そうするとクリップボードに下記文言が入ります。
<h1>h1タグだよー</h1><h2>h2タグだよー</h2><p>LIGブログは<a href="https://liginc.co.jp/">ここ</a>です!</p>
<p><em>コードも書ける</em></p>
<pre><code>
if (true) {
return false;
}
</code></pre>
改行が……。
この辺は調整が必要そうですが、思い通りのhtml構造にはなりました。
パッケージ化する
最後にElectronをビルドして、自分以外の環境でも使えるようにします。package.jsonに下記一行、やはり scripts の中に記載してください。
"build": "electron-packager . md2lb --platform=darwin --arch=x64 --overwrite",
カレントディレクトリをmd2lbというアプリケーションでビルドするコマンドになります。これを下記のように実行すると、カレントディレクトリに md2lb-darwin-x64 というディレクトリができあがり、その中にアプリケーションが置かれています。これを実行すると、先ほどテスト実行したアプリケーションが立ち上がるはずです。
ビルドのオプションでプラットフォームにlinuxやwinも追加することができます。またアーキテクチャとしてia32などを選べるようですが、対象マシンを今回は必要としないので作っていません。
ちょっとした便利ツールをパッと作れる
正直JavaScriptについては不勉強な部分が多いのですが、簡単にアプリケーションを作ることができました。これは実際便利に使えそうなので、もうちょっと改良すれば役に立てられそうです。
またこういった「ほんのちょっとのことだけど、解決すると少し時短になりそう」というものは、実はみんな困ってそうなので、一度作って配布すれば業務改善になりそうです。実際にこの記事も一度マークダウンで作って、置換してみたものです。
そう考えると解決したいものがたくさんありますね……。2019年は業務改善ができるような年にしたいです。
それでは、まさくにでした。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。