脱WordPressをしました

久しぶりに見た目を変更しました。そもそも更新がかなり久しぶりです。

WordPressは完全にやめてしまって、静的ブログジェネレーターを作成し、静的サイトとしてブログを上げています。
TypeScriptをまじめに触ってみたかったこともあり、TypeScriptで書きましたが、TypeScriptの性能を生かし切れてはいない気がするので普通のVanilla-node(こういう言い方する?)でもよかった気がします。

TypeScript

  1. const fs = require('fs').promises
  2. const fse = require('fs-extra')
  3. const pug = require('pug')
  4. const stylus = require('stylus')
  5. const CleanCSS = require('clean-css')
  6. const minify = require('html-minifier').minify
  7. class Blog {
  8. entryList = []
  9. constructor() {
  10. (async () => {
  11. this.entryList = await this.getList()
  12. await this.cleanDist()
  13. this.list()
  14. this.entry()
  15. this.static()
  16. })()
  17. }
  18. // dist
  19. async cleanDist(): Promise<number> {
  20. await fse.remove('dist')
  21. await fs.mkdir('dist')
  22. return 1
  23. }
  24. // 記事一覧の取得
  25. async getList(): Promise<any> {
  26. // 過去記事の取得
  27. const oldList1 = JSON.parse(await fs.readFile('entry/old/entry-01.json', 'utf8'))
  28. const oldList2 = JSON.parse(await fs.readFile('entry/old/entry-02.json', 'utf8'))
  29. let oldList = [...oldList1, ...oldList2]
  30. oldList.map(entry => {
  31. delete entry.id
  32. delete entry.date_gmt
  33. delete entry.guid
  34. delete entry.modified_gmt
  35. delete entry.type
  36. delete entry.link
  37. delete entry.excerpt
  38. delete entry.author
  39. delete entry.featured_media
  40. delete entry.comment_status
  41. delete entry.ping_status
  42. delete entry.sticky
  43. delete entry.template
  44. delete entry.format
  45. delete entry.meta
  46. delete entry.categories
  47. delete entry.post_meta.NoAMP
  48. delete entry.post_meta.ogpimg
  49. delete entry.post_meta.thumb
  50. delete entry._links
  51. delete entry.content.protected
  52. entry.listDate = entry.date.split('T')[0]
  53. entry.title = entry.title.rendered
  54. entry.css = entry.post_meta.css
  55. entry.noindex = entry.post_meta.noindex
  56. entry.ver = 1
  57. entry.color = this.randomColor()
  58. delete entry.post_meta
  59. return entry
  60. })
  61. // 新記事の取得
  62. let newList = await fs.readdir('./entry', {withFileTypes: true})
  63. newList = newList.filter(dirent => {
  64. return dirent.isFile()
  65. })
  66. newList = newList.map(file => {
  67. let entry = require(`./entry/${file.name}`)
  68. entry.slug = file.name.split('.')[0]
  69. entry.content.rendered = pug.render(entry.content.rendered)
  70. entry.color = this.randomColor()
  71. entry.ver = 2
  72. entry.listDate = entry.date.split('T')[0]
  73. return entry
  74. })
  75. let list = [...oldList, ...newList]
  76. list.sort((a, b) => {
  77. return (a.date < b.date ? 1 : -1)
  78. })
  79. list = list.filter((entry) => {
  80. return entry.status === 'publish'
  81. })
  82. return list
  83. }
  84. // stylus
  85. async stylusCompile(file: string): Promise<string> {
  86. const data: string = await fs.readFile(`stylus/${file}`, 'utf-8')
  87. return stylus(data).set('filename', `stylus/${file}`).render()
  88. }
  89. // ランダムカラー
  90. randomColor(): string {
  91. return `hsl(${Math.floor(Math.random() * (360 + 1 - 0)) + 0}, 85%, 65%)`
  92. }
  93. // html minfy
  94. htmlMinify(html: string): string {
  95. return minify(html, {
  96. removeAttributeQuotes: true,
  97. collapseWhitespace: true,
  98. removeEmptyAttributes: true,
  99. removeRedundantAttributes: true,
  100. })
  101. }
  102. // 一覧生成
  103. async list(): Promise<void> {
  104. const compiledFunction = pug.compileFile(`theme/list.pug`)
  105. const css: string = (new CleanCSS().minify(await this.stylusCompile('list.styl'))).styles
  106. const tagList = JSON.parse(await fs.readFile('tag.json', 'utf8'))
  107. let oldList = this.entryList
  108. let html: string = compiledFunction({
  109. metaTitle: 'q-Az',
  110. description: 'JavaScript、PHP、CSS、HTML、WordPress などウェブ制作の話を中心に解説したり実験したりしています。',
  111. css,
  112. oldList,
  113. tagList
  114. })
  115. html = this.htmlMinify(html)
  116. fs.writeFile(`dist/index.html`, html)
  117. // tag用の一覧
  118. await fs.mkdir('dist/tag')
  119. tagList.forEach(async (tag) => {
  120. const oldTagList = oldList.filter((entry) => {
  121. return entry.tags.includes(tag.id)
  122. })
  123. let tagHtml: string = compiledFunction({
  124. metaTitle: `${tag.name} | q-Az`,
  125. description: tag.description,
  126. css,
  127. oldList: oldTagList,
  128. tagList,
  129. tagActive: tag.slug
  130. })
  131. tagHtml = this.htmlMinify(tagHtml)
  132. await fs.mkdir(`dist/tag/${tag.slug}`)
  133. fs.writeFile(`dist/tag/${tag.slug}/index.html`, tagHtml)
  134. })
  135. }
  136. // 記事生成
  137. async entry(): Promise<void> {
  138. const tagList = JSON.parse(await fs.readFile('tag.json', 'utf8'))
  139. const oldList = this.entryList
  140. const compiledFunction = pug.compileFile(`theme/entry.pug`)
  141. const entryCss: string = await this.stylusCompile('entry.styl')
  142. oldList.forEach(async entry => {
  143. const css = (new CleanCSS().minify(entryCss + entry.css + `.mainHead{background-color:${entry.color}}`)).styles
  144. let description = entry.content.rendered.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,'').replace(/s+/g, '')
  145. description = description.slice(0, 150)
  146. let html: string = compiledFunction({
  147. metaTitle: `${entry.title} | q-Az`,
  148. description,
  149. css,
  150. entry,
  151. tagList
  152. })
  153. // 何記事かminify時エラーが出るので無視
  154. try {
  155. html = this.htmlMinify(html)
  156. } catch {}
  157. await fs.mkdir(`dist/${entry.slug}`)
  158. fs.writeFile(`dist/${entry.slug}/index.html`, html)
  159. })
  160. }
  161. // staticフォルダのコピー
  162. async static(): Promise<void> {
  163. await fse.copy('./static', './dist')
  164. }
  165. }
  166. new Blog(

とりあえず動けばいい精神で書いていたのでエラー処理等全く入れてませんし、似た処理なのに同じこと何度も書いたりしてますが、200行ちょいでブログができるコードが出来たので個人的には満足です。改良したいところ出来るところ多々あるんですが、まずは公開しないとモチベーションも下がってくるのでとりあえず。

フォルダ構成も書こうかと思いましたが、自分のブログでしか使えないコードなので、仮に(仮に)一般に配れる形までも持っていけたらその時GitHubにでも公開しよう(需要)かと思います。

方針とか

今回作成した方針としては

  • WordPressの過去記事をそのまま移行する
  • 記事はPugで書く
  • Stylusを使う
  • IEを完全に無視

という感じでやってみました。

WordPressの過去記事は、WordPressのREST APIをたたいて記事一覧を全部持って来る(json形式)、新記事はjsonだと融通が利かない部分や制限が多いので、1記事1jsファイルで管理

という普通しない方法で管理しています。この記事は「refresh.js」。jsファイルで記事管理をする謎な感じですが、ただのobjectでしかないので、見た目はほぼほぼjsonです。

テンプレート文字列が使えたりするのがjsだとjsonより優れているので、改行などを使って書きたい記事ではjsのobjectが圧倒的に使いやすいです。

過去記事を書き直したいときは、新記事でslugさえ合わせればそのまま上書きしてくれるので、古い記事が多すぎるので直したいところ。

IEに関してはGridのせいで全然違う見た目になっていますが、これはこれであり(?)な見た目になっています。

記事一覧にページネーションがなかったり(120記事全部出し)、なんか大事なもの含め色々欠けていますがこれから増やしていければよいかなと思ってます。

WordPressじゃないと出来ないもの

WordPressというよりはPHPなどによる動的処理が必要なものですが

  • 記事のコメント欄
  • お問い合わせ
  • 記事検索

は静的サイトでは基本出来ません。無理やりというか違うアプローチで出来るところもありますが、基本は無理なのでこの辺はどうしようか未来の課題です。

とりあえず公開の形まで持ってこれたので今回はこれでOKで。