blogdownと絶対URL・相対URLの話・完結編(たぶん)

これの続きです。

おさらい

詳しい議論は前回のブログを参照してもらうとして、blogdownのサイトをビルドする3つの関数の違いについて再掲しておきます。Hugoのテーマのテンプレートに埋め込まれている{{ .Site.BaseURL }}に相対URLが入るか絶対URLが入るか、Rmdをどこまでknitするか、といった点に違いがあります。

関数 目的 {{ .Site.BaseURL }} Rmdをknitするか
serve_site() 手元でのプレビュー /(つまり相対URL まだknitしていないものだけknitする
hugo_build() 公開用 config.tomlで設定したもの(つまり絶対URL knitしない
build_site() 公開用 config.tomlで設定したもの(つまり絶対URL すべてのRmdをknitしなおす

で、結論としては、

解決策3. HTMLではなくMarkdownファイルをgit管理下に入れ、HTMLへの変換はリモートのHugoにやってもらう

が一番いいでしょう。でもどうやって??という感じでしたが、やり方がなんとなくわかったので荒く説明を残しておきます。

build_site()で独自のビルドスクリプトを使う

build_site()は、R/build.Rというファイルがあるとサイト生成前にそれを実行します。また、method='custom'だと通常のサイト生成手順はスキップされ、R/build.Rだけが実行されるようになります。

For method = 'custom', build_site() will not process any R Markdown files, nor will it call Hugo to build the site. No matter which method you choose to use, build_site() will always look for an R script /R/build.R (blogdown: Creating Websites with R Markdown)

method='custom'は引数でも指定できますが、blogdown.methodというオプションでデフォルトを設定できます。ワーキングディレクトリの.Rprofileに書いておきましょう。

if (file.exists('~/.Rprofile')) sys.source('~/.Rprofile', envir = environment())

options(blogdown.method = 'custom')

(参考: https://github.com/rbind/yihui/blob/68716cccb7c5b2bdff7a01b9e6321da43ca22807/.Rprofile#L4)

R/build.Rをつくる

これで通常のサイト生成処理は走らなくなったので、独自のビルドスクリプトをつくっていきましょう。単純化したものはこんな感じです。

# RMarkdownファイルを列挙
rmds <- list.files("content", "[\\.]Rmd$", recursive = TRUE, full.names = TRUE)

for (rmd in rmds) {
  # .Rmdを.mdに置き換えたファイル名
  wo_ext <- tools::file_path_sans_ext(rmd)
  md <- glue::glue("{wo_ext}.md")
  
  # mdがRmdより新しい場合はすでにknit済みなのでスキップ
  if (file.exists(md) && utils::file_test("-ot", rmd, md)) {
    message(glue::glue("skip {rmd}"))
    next
  }
  
  # RmarkdownをMarkdownに変換
  knitr::knit(input = rmd, output = md, encoding = "UTF-8")
}

# Hugoによるサイトの生成も走らないので自分でhugo_build()を呼び出す必要がある
blogdown::hugo_build()

rmarkdownではなくknitrを直接使っていることに気付いたでしょうか。rmarkdown::render()md_document()を指定するとかでもいいようなところですが、あとで直接チャンクオプションを指定するのでknitr::knit()にしています。

さて、これでMarkdownファイルの生成はできますが、これだとまだうまく動きません。順を追ってスクリプトを改良していきましょう。

local引数を受け取る

local引数は、サイトの生成を絶対URLか相対URLかどちらでやるかを指定するオプションです。build.Rにはこれが文字列として渡されますが、上のスクリプトだと受け取れないので、build_site()でもserve_site()でも絶対URLになってしまいます(つまり、ローカルでプレビューしようとしても本物のサイトに飛ばされてしまう)。

  on.exit(run_script('R/build.R', as.character(local)), add = TRUE)

(https://github.com/rstudio/blogdown/blob/8322ade5e38c80211ae856a9189ceb267e40dfc8/R/render.R#L37)

てことで、引数に取れるようにしましょう。

local <- commandArgs(TRUE)[1] == 'TRUE'

# (略)

blogdown::hugo_build(local = local)

(参考: https://github.com/rbind/yihui/blob/68716cccb7c5b2bdff7a01b9e6321da43ca22807/R/build.R#L2-L3)

base.dirbase.urlfig.pathを指定する

ちょっと説明が面倒なので以下を読んでもらうとして、このままだと画像がうまく表示できません。

これは、デフォルトだと画像はfigure/ディレクトリにつくられますが、これはHugoが感知するディレクトリではないので画像がリンク切れになってしまいます。static/ディレクトリの下に入れておけばコピーされるので、Rmdのファイル名に応じてディレクトリを掘って、そこに置くようにします。

また、base.dirbase.urlもうまい具合に調整します。base.dirstatic/にして、base.url/にしておくとうまくいくみたいでした。

knitr::opts_knit$set(
  base.dir = normalizePath("static/", mustWork = TRUE),
  base.url = "/"
)

for (rmd in rmds) {

# (略)

  knitr::opts_chunk$set(
    fig.path = glue::glue("post/{basename(wo_ext)}_files/figure-html/")
  )
  
  knitr::knit(input = rmd, output = md, encoding = "UTF-8")
}

あと、static/base.dirにした関係上、このままだとキャッシュもコピーされてしまうので、cache.pathも別の場所を指定しておいた方が無難な気がします。

これでローカルのプレビューも動くようになったはずです。私の場合、最終的に出来上がったbuild.Rはこんな感じでした。参考までに。

public/.gitignoreに入れる

これはやらなくてもいいんですが、public/はリモートのHugoによって生成させるので、むしろレポジトリにコミットがあると邪魔になります。git管理下から外してしまいましょう。

Netlify(とrbind?)を使う

で、「リモートのHugo」って結局なんなのよ?というところですが、それはNetlifyしかない気がします。このへんも以前書いたので参考にしてください。