「Electron」を使って業務改善に役立つツールをつくってみた

「Electron」を使って業務改善に役立つツールをつくってみた

まさくに

まさくに

※この記事はLIGアドベントカレンダー2018の15日目です。

こんにちは。バックエンドエンジニアのまさくにです。

LIGにおいて逃れえぬ仕事のひとつに「LIGブログを書く」という作業があるのですが、僕の場合、この仕事の中に「マークダウンからLIGブログ用のフォーマットを作り出す」という非生産的な作業が含まれています。

僕はたいていの文章をマークダウンで書いていて、このブログ記事も一度マークダウンで書き出してから、WordPressに取り込み、それからLIGブログ用のhtmlに直して公開しているため、この置換作業をこれまでイヤイヤやってきました。

マ ジ な ム ダ。

そこで今回は、マークダウンからLIGブログ用のフォーマットへ変更するためのツールを考えたいと思います。

作りたいものと選定

単純に「マークダウンをhtmlに変更したい」っていうわけではありません。LIGブログには、たとえば下記のようなルールがあります。

  • aタグは内部サイトであれば同一ウィンドウ、外部サイトであれば別ウィンドウ
  • codeタグはpreタグで囲む

どうせならそこまで整形したい。世の中に出ている単純なマークダウンのパーサーではなく、独自でカスタマイズができなければなりません。

また、自分だけが使えるものであれば、たぶん僕の場合、Vim Scriptを書いたり、コマンドで置換したりするのが一番使いやすい気もしますが、どうせ社内でも困ってる人がいそうなものなので、みんなが使えるものがいいでしょう。

そこでElectronを使ってみることにしました。

https://electronjs.org/

前々から触ってみたかったですし、なんか色々そろってそうですし。

ついでに少し調べたところ、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サイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。

Webサイト制作の実績・料金を見る

この記事のシェア数

まさくに
まさくに バックエンドエンジニア / 伊藤 正訓

漢字で書くと正訓。バックエンドのエンジニアです。静岡と石川に住んだことがあり、現在は千葉に住んでいます。誰かが作ったシステムに対しては、正常系だけを通るように並列処理やデッドロックが起きそうな処理を避けて操作する職業病があります。好きな色は紫、好きなキーボードの位置は「i」、好きなご当地ヒーローはセッシャー1です。

このメンバーの記事をもっと読む