WordPress に関する投稿を表示しています

WordPressでプラグイン無しmarkdownを使って記事を書けるようにする

以前やろうとしたときはうまく動かなかった記憶がある。ので諦めて自分で用意した話。

記事を書くときに Markdown で書き、何かしらの手段を講じて、実際に見えるときに HTML である状態を作ります。
それにはいくつかのアプローチが考えられます。

入力画面で Markdown を使い WordPress へ保存するデータも Markdown にする

この場合 Post のメタデータとして Markdown であることを伝えて、テンプレートを表示する際に Markdown であった場合に PHP あるいは Javascript で Markdown から HTML に変換します。

PHP でやる場合 Composer でテーマ開発をする路線に乗り parsedown などを使うのがお手軽でしょう。
erusev/parsedown: Markdown Parser in PHP

そのほかにも Packagist を探すといくつかのライブラリが見つかります。
Packagist

Javascript でやる場合、テンプレートの出力を一度見えないようにしておいて HTML で整形できたら見せるようにすると綺麗です。
もちろんあえて Markdown を表示しておく、というのもアリかと。

ライブラリとしては marked があります。
markedjs/marked: A Markdown parser and compiler. Built for speed.

cdnjs を探すとほかにも見つかります。
cdnjs.com - The best FOSS CDN for web related libraries to speed up your websites!

PHP か Javascript かどちらを取るかというと、どっちでもよいと思います。
PHP の場合はサーバサイドの処理になるので、例えば nginx なり CDN なりで HTML にレンダリングしたものに対してキャッシュが利きます。
Javascript 上でやる場合、クライアントのリソースを使うので、大きなコンテンツであればあるほど HTML が表示されるまでに時間がかかってきます。
(ServiceWorker を使ってキャッシュを持つなどしてもよいかもしれません)

そしてこのパターンで PHP, Javascript 問わずに気を付けるべきは、もしも仮に第三者が記入できるエリアにも適用された場合に XSS などのバグおよび脆弱性につながりやすくことも挙げられます。
いやそもそもそういうことやりたいという事情はそんなにないか。

入力画面で Markdown を使い WordPress へ保存するデータを HTML にする

このアプローチは現在のごみばこいんでも扱っています。
WordPress 管理画面上の入力画面において Markdown で書いたものを HTML に変換するボタンを作っています。

具体的には以下のようなコードを functions.php に書いています。

// add media area buttons for admin
function hook_media_buttons() {
?>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.5.2/marked.min.js" integrity="sha256-zFUosuESzULu5P+SZdjRRtBZR8+1u5RZDlbt3Q5KL8U=" crossorigin="anonymous"></script>
    <button type="button" class="button Markdown-to-html">
        <span class="dashicons dashicons-thumbs-up"></span>
        Markdown to HTML
    </button>
    <script type="text/javascript">
        document.querySelector('.Markdown-to-html').addEventListener('click', () => {
            if (marked === null || marked === undefined) {
                alert("marked not loaded...");
                return;
            }

            let text = $('#content').val();

            // Markdown to html
            text = marked(text, {
                headerIds: false,
            });

            // More newline
            text = text
                .replace(
                    /(<\/(p|table|h\d|pre|ul|ol)>)\n?/g,
                    (_, a) => {
                        return `${a}\n\n`;
                    }
                )
                .replace(
                    /\n\n+/g,
                    '\n\n'
                );

            // Replace value
            $('#content').val(text);

            return false;
        });
    </script>
<?php
}
add_action('media_buttons', 'hook_media_buttons');

若干おまけもついていますが、これで Markdown を HTML にするボタンを記事編集ページに追加できます。
下書きを WordPress 上なり、別の場所なりで書いてから、実際に記事公開する前にポチっとやって様子を見てから公開するようにしています。公開アクションにフックして自動で変換されるようにすることもできると思います、今そうしていない理由は特にないです。

入力画面で Markdown を使わない

要は別の Markdown なエディタを使って、整形された HTML を WordPress にぶっこめば良いという話です。パッと思いつくのはこのあたり。

そのほか考えるとよさそうなところ

超速 web 体験を目指すなら、可能な限り前処理を済ませたほうがよいと思うので、クライアントの手元で動かすことをなるべく減らす = JavaScript 側でやるという選択肢ははずれてくるのではないでしょうか。
ServerSideRendering 的な話と捉えてもよいっぽい。保存するデータはどっち?という話は編集ページの利便性によってなんでもいいかなーと。

常に Markdown で書きたいなら Markdown で保存できるようにしたほうが良いだろうし。まあ何も気にせずプラグインぶっこむのがお手軽簡単でいいんじゃないですかね~~(結論)

WordPress 環境で nginx の proxy_cache を有効にする

おぼえがき。

Module ngx_http_proxy_module

キャッシュの名前、その保存先、サイズを設定する。

// /etc/nginx/nginx.conf
http {
    ...

    # cache
    proxy_cache_path /tmp/nginx/cache keys_zone=cache1:10m;
}

サイズについては公式ドキュメントにこう書いてあります。

One megabyte zone can store about 8 thousand keys.

10m という指定だとざっくり 8 万個のキーが保存できそうです。

どういうときにキャッシュするかを設定する

いくつかポイントがあるが、とりあえず今回の設定内容を出します。

// /etc/nginx/conf.d/server.conf
server {
    ...

    # cache
    set $mobile "";
    if ($http_user_agent ~* '(Mobile|Android)') {
        set $mobile "SP";
    }

    set $do_not_cache "";
    if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_" ) {
        set $do_not_cache 1;
    }

    proxy_no_cache          $do_not_cache;
    proxy_cache_bypass      $do_not_cache;
    proxy_cache             cache1;
    proxy_cache_key         "$mobile//$scheme://$host$request_uri$is_args$args";
    proxy_cache_valid       200 301 302 30d;
    proxy_cache_valid       any 10m;

    add_header X-Cache-Status $upstream_cache_status;

    ...
}

モバイル判定

WordPress のテーマとして、レスポンシブではなくサーバサイドでモバイルとの何か差分を作っている場合、キャッシュも分けないといけません。

WordPress では is_mobile() という関数が用意されていて、これをつかってモバイル判定ができるので、これに合わせて nginx も設定するとよさそうです。

vars.php in tags/4.9.5/src/wp-includes – WordPress Trac

今回はおおむね iPhone と Android だけという区別でいくので Mobile|Android という指定にしました。

set で変数を設定し proxy_cache_key にその変数を入れることで、キャッシュする内容をモバイルとそれ以外とで分けることができます。

キャッシュをしないとき

管理画面にログインしていたり、コメントフォームに投稿した名前が記録されていたりなど、ユーザのもっているクッキーに応じて、テーマ上で値が出し分けされる場合、その内容をキャッシュしてはいけません。
(ログインしていないのに管理バーが出ちゃうよ!)

comment_author_|wordpress_(?!test_cookie)|wp-postpass_ として示されるクッキーがあるかを判定すると良いみたいです。

ここもモバイル判定と同様に変数に入れます。

この変数は proxy_no_cache と proxy_cache_bypass で使います。
proxy_no_cache はレスポンスをキャッシュに保存しない設定で proxy_cache_bypass はリクエストに対してキャッシュを使ってレスポンスを送らない設定です。
どちらも 0 ではないときに有効になります。

キャッシュの有効期限

proxy_cache_valid を使うことで有効な時間を設定できます。

ステータスコードをスペース区切りで書き、その後ろに有効期限を記載します。使える単位は ms/s/m/h/d/M/y です。

Configuration file measurement units

ここでは、ステータスコードが 200 または 301 または 302 のときに 30 日の間保持するようにしています。

また any という記載はそれ以外のステータスコードすべてに対して適用されます。ここでは 10 分にしています。

キャッシュが使われたか確認する

$upstream_cache_status という変数を見ると、キャッシュを使ったかどうかがわかります。

Module ngx_http_upstream_module

値として、おおむね HIT か MISS 、期限切れの場合は EXPIRED 、キャッシュしない場合は BYPASS がそれぞれ HTTP ヘッダーに出ているはずです。それ以外はちょっとわかんないです。

nginx の再起動

設定した内容に問題ないかを確認し nginx の再起動やりロードをしないと設定が反映されません。

service nginx configtest
service nginx condrestart

このときに proxy_cache_path に設定したパスが存在しないと自動で作られることはないので、自分で作っておく必要があります。

キャッシュの削除

このままでは WordPress で新しい投稿をしてもキャッシュはが有効な間は、新しい投稿についてはキャッシュ上のコンテンツからたどることはできません。
ここでは 30 日が有効期限なので、月 1 回しか更新することができなくなってしまいます。

手動でキャッシュを消す

proxy_cache_path に設定したパスにキャッシュファイルがもりもり作られるので、そのファイル群を消せばキャッシュはなかったことになります。

rm -rf なんかでどばーっと消すと良いでしょう。

毎回手動でキャッシュを消すのも面倒なので、 WordPress から消せるようにします。

WordPress で投稿を保存した時に自動でキャッシュを消す

// functions.php
function clean_nginx_cache($postId) {
    exec('rm -rf /tmp/nginx/cache/*');
    file_get_contents(get_permalink($postId));
}
add_action('save_post', 'clean_nginx_cache');

手動でやるものを WordPress のアクションフックに設定して自動化したものです。
投稿を保存した時に自動でキャッシュを消し、なんならその投稿のキャッシュを作成することができます。

プラグイン API/アクションフック一覧/save post - WordPress Codex 日本語版

 

プラグインもありそうなので、そういうのを使ってもよいと思います。

“nginx cache” の検索結果 — WordPress プラグイン

nginx の設定でなんとかする

今回の環境では利用できなかったのですが(モジュールがないっぽい…?) proxy_cache_purge を使うと nginx 上でキャッシュ削除の設定ができるようになります。

Module ngx_http_proxy_module

おわりに

nginx + WordPress の環境で proxy_cache を有効にした際の覚書です。

有効にした結果ですが、レスポンスに 500ms 掛かっていたページが 50 - 100ms ほどに収まるようになりました。あまりプラグインなども入れていない簡素なテーマを使っているところなのですが WordPress って結構時間かかるんだなあ…。

今回のものはわりとシンプルなテーマ、プラグインの状況だったので設定することや考えることが少なかったのですが、マルチサイトやプラグインによる大幅な機能変更がついてくると、もっといろいろなことを考えないとうまくキャッシュすることが出来なくなりそうな予感がしています。

特に他人の情報が見えちゃうよ!とかはキャッシュあるあるかつ激ヤバ案件なので、そういった時には入念にテストも重ねて回避したいですね~。

WordPress と Twitter を連携するテスト

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

テストだよーん。

====

追記してみるよ。

今まで Simple Tweet を利用していたのですが、Twitter アプリの見直しをして、うっかり全部消してしまい、再度アプリを作成しても使えなくなっていました。
PIN コードの入力…、PIN どこ… と、いうような状況です。

Simple Tweet — WordPress Plugins

調べたところ WP to Twitter がよさそうだったので、これを入れました。

WP to Twitter — WordPress プラグイン

設定画面が日本語化されていますし、流れで設定していけるので簡単に利用できます。

WordPress にプラグインを使わずに JavaScript だけで目次機能をつけてみた

この記事は公開されてから1年以上経過しており、情報が古い可能性があります。

目次機能が出来るプラグインとかってあるけど、別に自分で作ってもいいよねって思ったので作った。そのうちモリモリっとテーマやプラグインの整備に力を入れていくので、そのあたりに介入されると困るので~~ってくらいの理由で作った次第。

これこれ!この目次! h2 タグを使うんだよ

2階層までなら対応するよ! h3 タグだよ

ここは h3 タグ

ここは h2 タグなので 1 階層目になる

ここは 2 階層。 h3 タグだからね

デモおわり

ここまで見出しだらけで見にくいのは仕方ない。

仕組み

  1. h2 タグと h3 タグを投稿箇所から探す
  2. それぞれのタグに ID をつける
  3. それぞれへのリンクを生成する
  4. 目次用の HTML を作る
  5. 一番最初の見出しの直前に HTML を挿入する

タイトルこそ WordPress って言ってるけど JavaScript が差し込めるブログサービス的なものなら、投稿箇所を探す部分を調整する必要はあるけど、どれでもできるんじゃないかな。
jQuery を使わなくていいように作ったので jQuery ないんだけど、みたいな環境でもいいと思う。いやそもそもそういう環境はまた違う問題っぽいけど…。

JavaScript

(function() {
	setTimeout(function() {
		var headingIndex = 1;
		Array.prototype.forEach.call(document.querySelectorAll('.post_content'), function(postContent) {
			if (postContent.innerText.length < 100) {
				return;
			}

			var headings = postContent.querySelectorAll('h2,h3');
			if (headings.length <= 0) {
				return;
			}

			var indexHtml = '
    '; var lastElement = 'H2'; Array.prototype.forEach.call(headings, function(e) { var id = 'heading-' + headingIndex; e.id = id; if (lastElement != e.tagName) { if (lastElement == 'H2') { indexHtml += '
      '; } else { indexHtml += '
    '; } lastElement = e.tagName; } indexHtml += '
  • ' + e.innerHTML + '
  • '; headingIndex++; }); indexHtml += '
'; var wrapper = document.createElement('div'); wrapper.classList.add('content-index'); wrapper.innerHTML = indexHtml; headings[0].parentNode.insertBefore(wrapper, headings[0]); }); if (location.hash.length > 1) { setTimeout(function() { var element = document.querySelector(location.hash); if (element != null) { window.scroll(0, element.offsetTop); } }, 10); } }, 10); })();

CSS

.post_content .content-index {
	border: 2px solid #eee;
	border-radius: 20px;
	padding: 20px;
	background-color: #fefefe;
}

.post_content .content-index:before {
	content: '目次';
	display: block;
	font-size: 24.5px;
	margin-bottom: 15px;
}

他のスタイルとのバッティング回避した部分を除くとこれだけ。シンプルに線を引くことと色味の調整くらい。

SEO的な観点とか

目次足すと SEO に効果あるのかわからんけど、ふつうに見ていて、長めの投稿だったら合ったほうが便利だよね、とは感じる。

一応 Fetch as Google をして Google Bot にこの目次が認識されていることは確認したので、まあいいんじゃないかな。