11  blogdown建站

11.1 blogdown工作流

本节内容将介绍如何使用“三个火枪手”——blogdown+ github+ netlify——“三剑”合璧持续性打造个人网站。(提示:因为hugo的流程变化,一些技术细节可能已经失效。2021.03.06)

11.1.1 创建blogdown site

blogdown的设置比较简单,教程里基本也说得清楚。具体操作时,还是需要注意以下几个要点:

要点1:选取中意的网站模板,比如学术型模板

```{r}
# for example, create a new site with the academic theme
blogdown::new_site(theme = "gcushen/hugo-academic")
```

这个模板还内部支持数学公式显示!需要在Rmarkdown文档的yaml区域里设置

math: true

要点2:设置全局选项(global options)。

一个是设置默认的“作者”和“文件格式”。需要在工作目录下新建一个.Rprofile文件:

```{r}
file.edit('.Rprofile')
```

文件里面的内容配置为:

```{r}
options(blogdown.ext = ".Rmd", blogdown.author = "huhuaping")
```

另外就是要配置工作文件下的config.toml文件,主要就是修改网站的名字:

# Title of your site
title = "Huhuaping"

要点3:最后要记得装载hugo,最重要得是弄清楚hugo_version(后面netlify建站时需要配置环境变量hugo_version,否则会报错255,二者应该要一致,且有最低版本要求)。在Rstudio里判明版本号的办法是:

```{r}
# 安装hugo
install_hugo()

# 判明版本号
blogdown::hugo_version()
```

11.1.2 利用git管理github仓库

git版本控制工具,还是需要学会的。这里就列出一个很好的参考学习资源吧——git book(多国语言版)

此外,要记得在工作目录下的.gitignore里添加忽视文件(否则会影响netlify建站更新)。

public/

此外,还会引起master和dev分支的合并冲突!!

11.1.3 netlify关联github仓库

如前所述,这里很重要的一步就是要记得设置hugo_version的环境变量,版本号需要与blogdown里安装的版本号一致。

11.2 hugo和go语言

对于搭建个人网站,曾经有一句经典的忠告摆在我的面前: 永远不要使用复杂的模板,否则总有一天会掉入网站迁移漩涡,甚至万劫不复的境地。 ————nobody

这种事,难免大家都会赶上那么一遭。这里给出了一些hugo+blogdown建站使用经验和若干建议。相关内容会随时更新。

采用hugo-academic模板搭建个人网站,2020年9月3日以后,面临一次建站流程的全新大换代升级。

Convert an old Academic Kickstarter site If you have an existing site based on the Academic Kickstarter Template that was created before 3rd September 2020, it may need converting to use Hugo’s new modular system.

在学习测试两天后,总算是跌跌撞撞地完成了网站的升级更新。截至写这篇博文,针对哪些blogdown用户,网络上还并没有足够完整和清晰的升级操作攻略。这里先记录一二,或许对于急迫升级的人有些许帮助。

事先说明一下我的环境:

  • Win 10操作系统

  • R环境 R version 4.4.1 (2024-06-14 ucrt)。

  • 升级后hugo version: Hugo Static Site Generator v0.75.1/extended windows/amd64 BuildDate: unknown

  • netlify 关联 github的仓库,自动激活建站开发。

11.2.1 hugo大升级的背后

hugo academic的大换血,变革了什么?后面复杂的flow、原理之类东东我也没有完全明白。不过基本按照官方的提示,是可以完成网站升级的,完全可以忽略下面这些什么go module之类的工具或概念。

对于windowns党,你大概需要本地安装一些工具:Edit your site on your PC

不过按照官方的说法,此次最大的变革是实现了“模块化”建站流程。

就我个人后面的感受而言,比前一代思路确实更加简洁,最关键的是建站速度贼快。

倒是让我收获了一个很深刻的领悟:

平时我可能只关心hugo的建站工具性,从来不想了解背后的最基本的指导思想和运行原理(尽管可能还是会一知半解,技术细节确实可以完全忽视)。现在看来,既然把身家大计(网站)都托付给了它,起码不能完全对其漠然无知。工具性导向很容易误人误事!

11.2.2 本地开发预览

现在简单说一下blogdown用户,如何从早前一代的”hugo-academic”升级到新一代的”Wowchemy Hugo Module”流程。

先说一条思路——实际上我们完全可以用两种方法来实现academic模板的建站。如果按官方说明正确安装软件、工具和环境,则可以:

  1. blogdown+hugo建站。如果默认安装,hugo执行程序应该在C:\Users\yourname\AppData\Roaming\Hugo\hugo.exe

  2. Scoop+hugo建站。如果默认安装,hugo执行程序应该在C:\Users\yourname\scoop\shims\hugo.exe

两种方式应该可以同时建站。如果报错,则建议把其中之一的hugo.exe修改为不可识别的(如hugo.exex)。

我相信对于大部分blogdown用户而言,应该不太关心后一种。不过在目前的跨代升级中,这一条却是非常重要。因为前者的一些流程,需要后者来援助!

下面列几个要点,跳开这几个坑应该就可以完成blogdown+hugo建站升级过程:

要点1: 一定记得提前备份网站!(git用户表示窃喜中)

要点2:首先下载starter-academica模板到本地,解压缩备用Download Academic Template

要点3:处理旧版网站的项目文件。如下操作删除旧模板。

git submodule deinit themes/hugo-academic    
git rm themes -r

同时记得手动删除旧网站项目文件下的public/文件夹。

要点4:把新模板starter-academic中的如下文件,拷贝或替换旧版网站项目文件:

  1. 模板文件,整个exampleSite文件夹

  2. 配置文件,整个config文件夹

  3. 两个go文件,go.sumgo.mod文件

  4. 其他starter-academic有,而旧项目文件里没有的文件。

要点5:在win power shell命令窗口中执行go操作

  1. cd 命令进入到旧项目根文件夹下(此步骤自行脑补吧)

  2. go 命令执行建站:hugo serve(如果顺利,可以直接预览渲染的网站);或者hugo(如果顺利,可以得到渲染后的public/文件夹)

  3. 如果不能顺利进行go建站渲染,则按提示修改其中的升级跃迁断点问题(break issues)。我碰到的断点问题主要是:

  • callout问题。按报错依次找到文件,替换参数即可。

  • jpg图片问题。按照toml里参数的提示,把文件夹路径更换即可。

要点6:网站内容的维护和调整。具体包括:

  1. config.toml参数的更新调整(地址等)

  2. content/文件夹各个.md的核查

要点7:如果在go建站预览下如果基本没有问题,则可以如常使用blogdown的blogdown::serve_site()进行独立的建站渲染。

最后,blogdown可能还是会报出提示:

WARN 2020/10/05 21:08:20 Module "github.com/wowchemy/wowchemy-hugo-modules/wowchemy" is not compatible with this Hugo version; run "hugo mod graph" for more information.

不过,好在网站本地开发预览,总算是升级正常啦!

11.2.3 云端开发预览

因为hugo版本的大变化,往往会导致新旧更替中出现建站断点问题(break point)。此时,我们不仅仅只是需要把相关工具、hugo模板等更新就行,而是还需要解决各方面配套工作流中的检查、更替和完善,例如调整netlify服务的参数配置。

尽管前面的网站本地开发预览已经OK,但是云端自动开发流程我们还没有进行配置。——我甚至几乎又花费了一整个早上来查找问题,直到最后成功。

核心提示:如果仍旧采用旧版网站的netlify开发流程,则云端开发会一直提示fail。所以需要采用新的netlify开发流程。

netlify新开发流程,官方说是采用了CMS管理平台——里面的原理暂时没有深究。如果注意比较新旧流程开发同样的网站代码,会发现主要的区别是:

  • 开放工具链(tool chain)中,部分工具的版本新旧,如ruby、go等的调用

  • 开发速度上,新流程明显完成得更快

为了解决这个新旧流程得断点(break point)问题,暂时摸索出来的办法如下:

步骤1新建一个netlify开发网站。根据starter-academic的官方说明,利用github账号接入netlify的api,创建一个基于starter-academic模板的github repo(称之为repo-B)。netlify预览网站(称之为website-B)应该是正常的。——这里纯粹是为了获得新流程的CMS平台。后面的话,这个repo可以直接不用。

步骤2准备好本地开发成功的网站项目。首先,准备工作。把本地开发预览正常的网站项目(称之为repo-A),推送到github远端(remote)。当然,关联给netlify云端的开发预览网站(称之为website-A)是不会成功更新的。

步骤3修改新旧网站的链接关系。此时,我们有两个netlify网站(website-B和website-A)以及对应的两个github repo(repo-B和repo-A)。新流程下的website-B网站是完全可以云端正常自动开发的,但是旧流程下的website-A网站则是无法自动激发云端开发的。关键步骤就是“嫁接替换法”:

  1. 将website-B关联的github仓库,修改为repo-A。netlify操作过程:https://app.netlify.com/sites/huhuaping-kick/settings/deploys#continuous-deployment –》build settings –》 edit setting –》reopsitory –》Link a different repository

  2. 将website-B的域名修改为原来website-A的域名。netlify操作过程:https://app.netlify.com/sites/huhuaping/settings/general –》sitede tails –》site information –》 change site name

  3. “嫁接替换”完成后,netlify就在CMS新流程下,开发了原来的repo-A的网站代码内容。

至此,我们只需要继续维护本地更新升级后的repo-A,并持续推送给github,那么每次commit提交就会自动激活netlify使用新流程开发渲染网站内容啦!

11.2.4 双服务站点需求

我的特殊“双服务器”建站需求。简单说,首先我会使用blogdown+hugo+github+netlify工作流建立一个服务器在netlify的网站(A服务器网站),同时会得到该网站的一个public文件夹。因为netlify服务器在国外,国际友人访问自然没有问题,但是国内访问速度明显就不行了。因此,我必须为国内访问用户搭建一个服务器在国内的同步“复刻网站”(B服务器网站),这时我只需要将前面的public/文件夹同步上去即可。因此,“双服务器”建站需求,并不是我个人有什么极客级别的建站癖好,纯粹只是出于“简单性”和“可用性”原则。以上双服务器网站的差异性和联系:

  • 二者是几乎是“完全复制”的。但就网站更新频率和时间先后而言,后者更新频率更低而且会滞后于前者。下面说原因!

  • netlify网站(A服务器网站)是基于自动化工作流的(automate workflow),只需要github提交(commit)即可后台自动更新整个网站。因此是时效性最强的。而且可以基于git分支进行测试(如feature 分支),即兴写博客post或测试新功能,简直是指哪打哪,顺服得不要不要的。

  • 国内网站(B服务器网站)采用的是goodsync的FTP自动上传同步功能。尽管也可以完全设定自动同步(例如,可以设定goodsync规则为:一旦public、有文件修改就自动上传),但是因为不具备git那样的分枝提交功能,会导致同步正在测试阶段的网站,从而导致用户访问失败或页面错乱。最要命的是goodsync的FTP上传速度很慢,对于我这个相对庞杂的网站,要完成一次完整的网站同步,大概需要4-6个小时才能完成(家里稳定网速下)。简单说,我不能让B服务器网站“实时同步”,而且只能在网站版本稳定后才“手动”进行goodsync同步(一般在晚上进行这个同步动作)。

11.2.5 获得本地public

如果通过netlify提供的服务来发布个人站点,那么在Rstudio下使用bookdown::serve_site()或者在Windows PowerShell下使用hugo serve,渲染完整个站点并确保无误后,直接git提交就足够了。

然后,有时候我们希望在其他服务平台发布个人站点,此时我们就需要获得blogdown渲染的(或者hugo渲染的)本地public/文件夹,并将该文件夹上传至希望的服务平台上(ftp上传等之类工具)。

更新blogdown包。照常使用Rstudio addins serv site。需要注意的是新的hugo流程默认是在缓存里渲染网站,也就意味着public/文件夹默认不会随着渲染而自动更新。

  • 解决方案1(已测试):那么需要blogdown执行的是本地渲染(参看 网络问答)。(尽管如此,blogdown本地预览会优先使用public/文件夹,所以好像必须要删除public/才能获得实时修改更新,这似乎是一个bug啊!目前对这个问题也是纠结中。因为它带来了goodsync的一个同步问题,每次获得blogdown方法下的public/文件夹里的每一个文件都是全新的、不同于原同步文件(生成时间或内容改变了),这将意味着goodsync每次同步都必须上传public/的所有文件!——而实际上我只是小步调更改了网站的部分内容!)
```{r}
blogdown::build_site(local=TRUE)
```
  • 解决方案2(已测试):直接在windows命令视窗里指定hugo生成public/文件夹。具体参看blogdown社区讨论
hugo -d ./public

11.2.6 选择hugo extend

注意hugo extend与hugo存在一定差异性,简单说hugo extend似乎功能性要更多一点(参看社区问答)。建议windows用户下载使用hugo extend。原因很简单:因为hugo extend能够让我顺利生成网站的本地public/文件夹,而hugo则会报错无法生成public/文件夹!更新hugo extend基本上有两种方法:

方法一(已测试):手动下载hugo extend版本进行升级更新。具体做法进入官方版本下载界面,下载后解压缩并放到指定本地电脑路径下!还是要注意blogdown和scoop两种渲染网站的hugo路径是不同的。(见前面说过的这两种渲染方式。)

# hugo extended
hugo_extended_0.80.0_Windows-64bit.zip

# target directory path for blogdown
C:\Users\huhua\AppData\Roaming\Hugo\0.80.0\hugo.exe

# target directory path for scoop
C:\Users\huhua\scoop\shims\hugo.exe

方法二(未测试过):在windows 命令行里进行hugo升级(见官方说明)。

# Update to get the very latest developments:
hugo mod get -u ./...

# Alternatively, update to the latest official release:
hugo mod get -u

11.2.7 使用RStudio插件

一般而言,提交新的博客文章(post)最好使用Rstudio里Addin菜单中的New Post来写Rmd格式的博文。

使用RStudio插件(addins)中blogdown下的New post暂时还会带来困扰(参看“stackoverflow队长”问答)。

按照hugo的新流程,每个blog都会建立自己的对应的文件夹(参看网页)。当然,其背后的原因是hugo新版本(Hugo version 0.32以上)引入了“页面束捆”(Page Bundles)特性。具体文件夹结构如下:

library(data.tree)

post <- Node$new("post")
  data <- post$AddChild("data")
    rda<- data$AddChild("fultonfish.rda")
    excel<- data$AddChild("fultonfish.xlsx")
    other<- data$AddChild("other-data-file.dat")
  pic <- post$AddChild("pic")
    png<- pic$AddChild("check-box-invisible.png")
    other<- pic$AddChild("other-image-file.jpeg")
  rmd <- post$AddChild("2020-12-05-web-scraping-tech-webelem.en.Rmd")
  html <- post$AddChild("2020-12-05-web-scraping-tech-webelem.en.html")
  dir <- post$AddChild("2021-01-31-new-post")
    files <- dir$AddChild("index.en_file")
    rmd <- dir$AddChild("index.en.Rmd")
    html <- dir$AddChild("index.en.html")
  
print(post)
                                          levelName
1  post                                            
2   ¦--data                                        
3   ¦   ¦--fultonfish.rda                          
4   ¦   ¦--fultonfish.xlsx                         
5   ¦   °--other-data-file.dat                     
6   ¦--pic                                         
7   ¦   ¦--check-box-invisible.png                 
8   ¦   °--other-image-file.jpeg                   
9   ¦--2020-12-05-web-scraping-tech-webelem.en.Rmd 
10  ¦--2020-12-05-web-scraping-tech-webelem.en.html
11  °--2021-01-31-new-post                         
12      ¦--index.en_file                           
13      ¦--index.en.Rmd                            
14      °--index.en.html                           

blogdown这样的new-post文件夹系统变化带来的一个影响效应是:

  • 新旧文件夹体系的不一致性(见上面树形结构图)。虽然只是视觉上的差异,但是还是不够“一致”!当然,也可以关闭这样的新文件夹体系(“页面束捆”),具体需要在.Rprofile文件设定 options(blogdown.new_bundle = FALSE)

  • 致命的影响是Rmarkdown引以为傲的直接R代码块功能。新体系下,可以丝滑般得到正常的R代码块运行效果(已测试。这得益于新文件系统下的self-contained理念);旧体系下则无法运行。而且经测试,新文件夹体系下,不会影响图片/pic/.png和数据文件/data/.rds的调用。

11.2.8 blogdown升级助手

由于hugo在建站流程上做了很大的改变,blogdown包也在2021年1月迭代到了v1.0版本,显然这次是大版本升级以支持hugo,具体可以参看包作者的声明“Announcing blogdown v1.0”

其中一些包函数值得关注:

  • blogdown::built_site()函数,涉及到是否产生/public文件夹和如何渲染.Rmd文件。

  • blogdown::check_.()类函数,主要用于核查和比对blogdownhugo在配置等方面是否一致。

  • blogdown::find_hugo('all')函数,方便掌握自己正在(或曾经)使用的hugo版本。为稳定hugo版本,可以设定.Rprofile文件,具体可设定为例如options(blogdown.hugo.version = "0.79.0")

  • blogdown::remove_hugo()函数,可以删除未使用的hugo版本。

11.2.9 netlify开发修复

有时候本地预览显示正常,但netlify网站开发仍可能出错(Deploy failed),导致无法更新云端网站,此时则建议:

  • 登陆netlify账户,立即查看开发日志(Deploy log)

  • Rstudio里利用blogdown::check_site()函数进行网站检查,并根据提示做出修改调整。具体参看blogdown社区问答

下面是我的Rstudio里的一个网站检查结果和建议清单:

blogdown::check_site()

# here my check result
― Running a series of automated checks for your blogdown website project...
----------------------------------------------------------------------------------------------
○ A successful check looks like this.
● [TODO] A check that needs your attention looks like this.
| Let's check out your blogdown site!
----------------------------------------------------------------------------------------------
― Checking config.toml
| Checking "baseURL" setting for Hugo...
○ Found baseURL = "https://huhuaping.netlify.app/"; nothing to do here!
| Checking "ignoreFiles" setting for Hugo...
● [TODO] Add these items to the "ignoreFiles" setting: "\\.knit\\.md$", "\\.utf8\\.md$"
| Checking setting for Hugo's Markdown renderer...
○ All set! Found the "unsafe" setting for goldmark.
― Check complete: config.toml

― Checking .gitignore
| Checking for items to remove...
○ Nothing to see here - found no items to remove.
| Checking for items to change...
● [TODO] Change items in .gitignore: blogdown -> /blogdown, public/ -> /public/
| Checking for items you can safely ignore...
● [TODO] You can safely add to .gitignore: .DS_Store, Thumbs.db
| Checking for items to ignore if you build the site on Netlify...
● [TODO] When Netlify builds your site, you can safely add to .gitignore: /public/, /resources/
| Checking for files required by blogdown but not committed...
● [TODO] Found 1 file that should be committed in GIT:

  layouts/shortcodes/blogdown/postref.html
― Check complete: .gitignore

― Checking Hugo
| Checking Hugo version...
○ Found 4 versions of Hugo. You are using Hugo 0.80.0.
| Checking .Rprofile for Hugo version used by blogdown...
| Hugo version not set in .Rprofile.
● [TODO] Set options(blogdown.hugo.version = "0.80.0") in .Rprofile and restart R.
― Check complete: Hugo

― Checking netlify.toml...
○ Found HUGO_VERSION = 0.80.0 in [build] context of netlify.toml.
| Checking that Netlify & local Hugo versions match...
○ It's a match! Blogdown and Netlify are using the same Hugo version (0.80.0).
| Checking that Netlify & local Hugo publish directories match...
○ Good to go - blogdown and Netlify are using the same publish directory: public
― Check complete: netlify.toml

― Checking content files
| Checking for validity of YAML metadata in posts...
○ All YAML metadata appears to be syntactically valid.
| Checking for previewed content that will not be published...
○ Found 0 files with future publish dates.
● [TODO] Found 2 files marked as drafts. To un-draft, run the command:

  blogdown::edit_draft(c(
  "content/privacy.md",
  "content/terms.md"
  ))

  and change a file's YAML from 'draft: true' to 'draft: false' or delete it
| Checking your R Markdown content...
○ All R Markdown files have been knitted.
● [TODO] Found 2 R Markdown files to update by re-rendering:

  content/post/2019-04-10-note-for-git-version-control-skills.Rmd
  content/post/2020-10-05-hugo-big-update/index.en.Rmd

  To update a file, re-knit or use blogdown::build_site(build_rmd = 'timestamp')
| Checking for .html/.md files to clean up...
○ Found 0 duplicate .html output files.
○ Found 0 incompatible .html files to clean up.
| Checking for the unnecessary 'content/' directory in theme...
○ Great! Your theme does not contain the content/ directory.
― Check complete: Content

具体调整如下:

调整1:根据建议,config.toml需要调整ignoreFiles参数

Checking "ignoreFiles" setting for Hugo...
[TODO] Add these items to the "ignoreFiles" setting: "\\.knit\\.md$", "\\.utf8\\.md$"

调整2:根据建议,.gitignore需要调整参数

[TODO] Change items in .gitignore: blogdown -> /blogdown, public/ -> /public/

调整3:根据建议,.Rprofilee需要调整hugo版本参数

Hugo version not set in .Rprofile.
[TODO] Set options(blogdown.hugo.version = "0.80.0") in .Rprofile and restart R.

调整4:根据建议,部分Rmd文件需要重新渲染更新,可采用函数blogdown::build_site(build_rmd = 'timestamp')

[TODO] Found 2 R Markdown files to update by re-rendering:

  content/post/2019-04-10-note-for-git-version-control-skills.Rmd
  content/post/2020-10-05-hugo-big-update/index.en.Rmd

  To update a file, re-knit or use blogdown::build_site(build_rmd = 'timestamp')

11.2.10 output format报错

有时候过几天再开始站点内容维护更新,你会发现无论hugo渲染网站(powershell命令下hugo server),还是blogdown渲染网站(server_site)分别出现如下报错。

1.报错信息1:

Error: from config: failed to resolve output format "WebAppManifest" from site config

2.报错信息2:

Error: from config: failed to resolve output format "headers" from site config

两次报错,如同官网提到的“Error: failed to resolve output format”

最后按照官方处理办法:

  1. config.toml[outputs]清单中暂时先删除"RSS", "JSON", "WebAppManifest"

  2. 然后在powershell命令窗口中依次输入如下命令:

hugo mod clean
hugo mod get -u ./...
hugo mod tidy
  1. 完成上述操作后,再把"RSS", "JSON", "WebAppManifest"补回原处即可。

11.2.11 hugo模板微调

劝君更进一杯酒,模板莫要闲折腾。——somebody

如果使用blogdown开发课程内容,网上有人提醒慎重升级(可参看blogdown advice)。

  • 不要升级hugo(包括hugo extend

  • 不要更新hugo theme

  • 课程开发期间,不要升级任何东西!

考虑到hugo-academic模板会不断升级,如果用户想要对模板进行个性化调整,因此hugo开发了一套模板查找顺位机制template lookup)。用户可以在不直接修改hugo-academic官方模板文件下,独立地进行个性化修改和微调。当然用户的任何个性化修改,都需要在约定文件路径下,且使用go语法

  • 优点:官方模板文件和用户个性化修改文件互相分离和独立,保证了二者并行不悖。

  • 缺点:用户需要熟悉go语法。此外,因为官方模板会不断迭代变化,用户个性化修改可能会失效,因而需要不断去配合官方的迭代步骤!

11.2.12 一些学习资源

11.3 netlify服务

11.3.1 netlify文件夹系统概览

使用blogdown进行netlify建站,下述三个文件夹及作用需要区分清楚:

  • content文件夹,主要进行写作或者接口配置(链接等),对接包括:post、publication、course、project等

  • static文件夹,主要存放来自其他仓库的静态资料。例如,xaringan slide课件制作往往在我的另一个课程仓库(如course-econometrics),相关文件资料(html)需要提前拷贝到这个文件夹对应位置。

  • public文件夹,是建站结果的最终目录树文件系统。netlify服务器网站可以方便国际用户访问,但国内访问速度较慢。public/后期还要更新到本地另外一个域名服务器网站(方便国内访问)。(见之前的博客文章

11.3.2 netlify content文件系统

library(data.tree)

netlify <- Node$new("netlify")
  static <- netlify$AddChild("static")
  content <- netlify$AddChild("content")
    statistics <- content$AddChild("course-advanced-statistics")
    econometrics <- content$AddChild("course-econometrics")
      data <- econometrics$AddChild("data")
      rmd <- econometrics$AddChild("schedule-theory.Rmd")
      html <- econometrics$AddChild("schedule-theory.html")
    post <- content$AddChild("post")
    project <- content$AddChild("project")
    publication <- content$AddChild("publication")
  public <- netlify$AddChild("public")
  config <- netlify$AddChild("config")
  proj <- netlify$AddChild("netlify.Rproj")
  
print(netlify)
                            levelName
1  netlify                           
2   ¦--static                        
3   ¦--content                       
4   ¦   ¦--course-advanced-statistics
5   ¦   ¦--course-econometrics       
6   ¦   ¦   ¦--data                  
7   ¦   ¦   ¦--schedule-theory.Rmd   
8   ¦   ¦   °--schedule-theory.html  
9   ¦   ¦--post                      
10  ¦   ¦--project                   
11  ¦   °--publication               
12  ¦--public                        
13  ¦--config                        
14  °--netlify.Rproj                 
```{r}
library(listviewer)
library(widgetframe)

l <- ToListSimple(netlify)
tree <- jsonedit(l)

htmlwidgets::saveWidget(tree, file = "tree.html", selfcontained = TRUE)
```

11.3.3 netlify static文件夹系统

library(data.tree)

netlify <- Node$new("netlify")
  static <- netlify$AddChild("static")
    statistics <- static$AddChild("course-advanced-statistics")
    econometrics <- static$AddChild("course-econometrics")
      data <- econometrics$AddChild("data")
      pic <- econometrics$AddChild("pic")
        pic1 <- pic$AddChild("chpt1-log.png")
        pic2 <- pic$AddChild("chpt2-reg.png")
      reading <- econometrics$AddChild("reading")
        files <- reading$AddChild("cht01-history.files")
        html <- reading$AddChild("cht01-history.html")
      intro <- econometrics$AddChild("01-introduction-slide.html")
      reg <- econometrics$AddChild("02-simple-reg-basic-slide.html")
  content <- netlify$AddChild("content")
  public <- netlify$AddChild("public")
  config <- netlify$AddChild("config")
  proj <- netlify$AddChild("netlify.Rproj")

print(netlify)
                                    levelName
1  netlify                                   
2   ¦--static                                
3   ¦   ¦--course-advanced-statistics        
4   ¦   °--course-econometrics               
5   ¦       ¦--data                          
6   ¦       ¦--pic                           
7   ¦       ¦   ¦--chpt1-log.png             
8   ¦       ¦   °--chpt2-reg.png             
9   ¦       ¦--reading                       
10  ¦       ¦   ¦--cht01-history.files       
11  ¦       ¦   °--cht01-history.html        
12  ¦       ¦--01-introduction-slide.html    
13  ¦       °--02-simple-reg-basic-slide.html
14  ¦--content                               
15  ¦--public                                
16  ¦--config                                
17  °--netlify.Rproj                         

11.3.4 netlify public文件系统

netlify <- Node$new("netlify")
  static <- netlify$AddChild("static")
  content <- netlify$AddChild("content")
  public <- netlify$AddChild("public")
    statistics <- public$AddChild("course-advanced-statistics")
    econometrics <- public$AddChild("course-econometrics")
      schedule <- econometrics$AddChild("schedule-theory")  
      data <- econometrics$AddChild("data")
      pic <- econometrics$AddChild("pic")
        pic1 <- pic$AddChild("chpt1-log.png")
        pic2 <- pic$AddChild("chpt2-reg.png")
      intro <- econometrics$AddChild("01-introduction-slide.html")
      reg <- econometrics$AddChild("02-simple-reg-basic-slide.html")
  config <- netlify$AddChild("config")
  proj <- netlify$AddChild("netlify.Rproj")
 
print(netlify)
                                    levelName
1  netlify                                   
2   ¦--static                                
3   ¦--content                               
4   ¦--public                                
5   ¦   ¦--course-advanced-statistics        
6   ¦   °--course-econometrics               
7   ¦       ¦--schedule-theory               
8   ¦       ¦--data                          
9   ¦       ¦--pic                           
10  ¦       ¦   ¦--chpt1-log.png             
11  ¦       ¦   °--chpt2-reg.png             
12  ¦       ¦--01-introduction-slide.html    
13  ¦       °--02-simple-reg-basic-slide.html
14  ¦--config                                
15  °--netlify.Rproj                         

11.3.5 netlify关联Github仓库

目前而言,neglify主站相关联的主要github仓库有:

  • 主站仓库:netlify仓库,基于hugo-academic网站模板,集合了众多的其他仓库的展示材料

  • 教学课程仓库:主要包括本科课程《计量经济学》course-econometrics;本科课程《统计学原理》course-statistics;研究生课程《中级计量经济学》master-SEM等。主要关联材料包括:课件Xaringan slide;补充材料html;pdf等。

  • 研究项目仓库:包括各类开源的研究类仓库,如agri-trade-openteam-students等。

  • 其他仓库

11.3.6 netlify内部相互链接

如果要直接在/content/course-econometrics下用Rmd文件生成.html,然后再别处来调用这个内部的html文件?

1.基于主站的相对链接引用(已测试)。参看”队长”问答Linking to another post in blogdown

  • 设置基准地址,修改config.toml参数:
# config.toml
baseurl = "https://yourdomain.netlify.app/"
  • 设定内部引用规则,修改config.toml参数:
# config.toml
[permalinks]
  # If only creating events which are talks, we can optimize event URLs for talks.
  event = "/talk/:slug/"
  # Workaround Hugo publishing taxonomy URLs as plurals - consistently use singular across Academic.
  tags = "/tag/:slug/"
  categories = "/category/:slug/"
  publication_types = "/publication-type/:slug/"
  post = "/:year/:month/:day/:slug/"
  • Rmd内部链接1:对于hugo标准模块内容的链接。所谓hugo标准模块内容,一般包括hugo版本内置好的postpublicationtags等,可以通过上述config.toml的参数块[permalinks]看到其影子。因此,对于hugo标准模块内容的链接引用,都需要在这里指定链接的参数形式。值得说明的是:a.务必要按前述各个模块的链接地址规则进行url设定。b.不要画蛇添足/2020/10/05/hugo-big-update/index.en.html(哪怕文件夹下有这个html文件);c.只要设定了refer ID(如{#sec-your-section-refer}),就可以进行更进一步的链接。

例如,以链接其他博文post为例,见之前的一篇博客文章谈到的“双服务器建站需求”

其背后具体代码如下:

# note your can refer the section ID as your have set

例如,以链接其他博文post为例,见之前的一篇博客文章谈到的["双服务器建站需求"](/2020/10/05/hugo-big-update/#double-site)。

当然,博文内部章节或任意内容的自我链接(需要事先设定为{#sec-a-ref-id})也是一种常见的引用方式。此时对同一篇博文post内部ref ID的引用,则可以直接采用[link-text](#a-ref-id)的形式。

例如,请查看前面的content文件系统树形图

其背后代码为:

# 需要事先设定好引用指针
# you should specify the reference ID before link it

例如,请查看前面的content文件系统[树形图](#dir-content)。
  • Rmd内部链接2:对于非hugo标准模块内容的链接。与上述对应,非hugo标准模块内容一般指用户根据hugo空白模板widget = "blank"设定的页面。主站的课程系列(包括course-econometrics、course-advanced-statistics等)的内容(各种课程的文件结构可以参看前面提及的content文件系统树形图)就是基于hugo空白模板widget = "blank"生成的。具体模板参数可以逐层追溯看到,例如先查看netlify/content/course-econometrics/_index.md的yaml参数区域,然后进一步追溯其继承至netlify/config/_default/menus.toml的toml参数区域,最后发现它的模板是netlify/content/home/teaching.mdyaml参数区域指定的widget = "blank"

此时,如果要链接在外部拷贝的一个特定静态网页文件,其存放路径netlify/static/course-econometrics/reading/cht01-history.html(具体可参看前面提到的netlify static文件夹系统树形图)。此时路径指向应精准和明确。

例如,请点击查看《计量经济学》课程扩展阅读资料计量经济学的前世今生

其背后的具体代码为:

# 路径指向应精准和明确,包含了后缀.html
# specify the file with full path

例如,请点击查看《计量经济学》课程扩展阅读资料[计量经济学的前世今生](/course-econometrics/reading/cht01-history.html)。

2.利用hugo的shortcode特性(未测试)。参看yihui提到的Shortcode方法。正文行内引用方式:

[第一章](r blogdown::shortcodex("relref", "chapter01-introduction.html"))

需要注意的是不能直接引用chapter01-introduction.Rmd;但是可以直接引用chapter01-introduction.md或者chapter01-introduction.html。此外还要注意引用路径是相对的。例如:

[博客文章提到](r blogdown::shortcodex("relref", "../post/2019-02-24-xaringan-for-course-slide.html"))

11.3.7 netlify指定Rmd输出格式

用.Rmd生成的文档能不能有其他的输出格式呢?还能做些别的么?

# add this in yaml area
output:
  blogdown::html_page:
    toc: true
    number_sections: true

11.4 上线Xaringan slide

本节将介绍如何把Xaringan 演示Slides以静态网页(static webpage)形式载入个人网站。一些实现办法的参考资料可以参看:

以上都有比较系统的说明,可以直接参照。

11.4.1 xaringan slide的典型文件结构

使用Xaringan制作教学slide。需要注意到Xaringan制作的slide是不能独立封装的(self-contained),也即它需要依赖各种必要组件或资源。(注意:目前这个问题已经得到了部分解决,具体见yihui)

课程仓库下xaringan slide制作的典型文件结构:

econometrics <- Node$new("course-econometrics")
  data <- econometrics$AddChild("data")
  pic <- econometrics$AddChild("pic")
      pic1 <- pic$AddChild("chpt1-log.png")
      pic2 <- pic$AddChild("chpt1-curve-points.png")
  cat1 <- econometrics$AddChild("01-class-instruction")
  cat2 <- econometrics$AddChild("02-lab-exercise")
  slide <- econometrics$AddChild("03-slide-class")
    libs <- slide$AddChild("libs")
      crosstalk <- libs$AddChild("crosstalk-1.1.1")
      htmlwidgets <- libs$AddChild("htmlwidgets-1.5.3")
        js <- htmlwidgets$AddChild("htmlwidgets.js")
      remark <- libs$AddChild("remark-latest.min.js")
    files <- slide$AddChild("01-introduction-slide_files")
      figure <- files$AddChild("figure-html")
        fig1 <- figure$AddChild("unnamed-chunk-14-1.png")
        fig2 <- figure$AddChild("unnamed-chunk-15-1.png")
    intro <- slide$AddChild("01-introduction-slide.html")
    rmd <- slide$AddChild("01-introduction-slide.Rmd")
  template <- econometrics$AddChild("template")  
  eViews <- econometrics$AddChild("EViews")    
  mycss <- econometrics$AddChild("mycss")    
  public <- econometrics$AddChild("public")
  proj <- econometrics$AddChild("course-econometrics.Rproj")
print(econometrics)
                                levelName
1  course-econometrics                   
2   ¦--data                              
3   ¦--pic                               
4   ¦   ¦--chpt1-log.png                 
5   ¦   °--chpt1-curve-points.png        
6   ¦--01-class-instruction              
7   ¦--02-lab-exercise                   
8   ¦--03-slide-class                    
9   ¦   ¦--libs                          
10  ¦   ¦   ¦--crosstalk-1.1.1           
11  ¦   ¦   ¦--htmlwidgets-1.5.3         
12  ¦   ¦   ¦   °--htmlwidgets.js        
13  ¦   ¦   °--remark-latest.min.js      
14  ¦   ¦--01-introduction-slide_files   
15  ¦   ¦   °--figure-html               
16  ¦   ¦       ¦--unnamed-chunk-14-1.png
17  ¦   ¦       °--unnamed-chunk-15-1.png
18  ¦   ¦--01-introduction-slide.html    
19  ¦   °--01-introduction-slide.Rmd     
20  ¦--template                          
21  ¦--EViews                            
22  ¦--mycss                             
23  ¦--public                            
24  °--course-econometrics.Rproj         

11.4.2 Xaringan Rmd调用本地图片文件

值得注意的是Xaringan Rmd使用本地图片文件(存放在pic/文件夹下),应该使用相对路径,而不是绝对路径(参看the full path approach doesn’t work)。也即意味着:

  • 不能使用绝对路径D:/github/course-econometrics/pic/chpt1-log.png

  • 同时也不能使用here::here("pic","chpt1-log.png"),因为它也会给出绝对路径"D:/github/course-econometrics/pic/chpt1-log.png"

那么Rmd文件01-introduction-slide.Rmd对上述本地图片的函数化引用应该设为相对路径形式,如:

  • markdown语法:![]("../pic/chpt1-log.png")

  • 或者knitr语法:include_graphics("../pic/chpt1-log.png")

总结起来,图片绝对路径和相对路径的差异比较如下:

  • 绝对路径直接无法渲染出图形

  • 相对路径可以渲染出图形,而且可以使用chrome浏览器开发工具看到html元素

# html develop tool view
<div class="figure" style="text-align: center">
  <img src="../pic/chpt1-log.png" alt="一份样本数据" width="693">
  <p class="caption">一份样本数据</p>
</div>

11.4.3 Xaringan Rmd代码块生成的graph

Rmd使用代码块生成的图片这会自动存放在同名文件夹下的一个文件夹内。如01-introduction-slide.Rmd,则代码块chunk-14制图将存放在自动生成的文件夹01-introduction-slide_files/figure-html/unnamed-chunk-14-1.png

例如,R代码块14如下:

```{r}
# r code chunk 14

ggplot(data.frame(x = seq(from = -5, to=5,length =500 )), aes(x = x)) +
  stat_function(fun = dnorm, args = list(0,1),
                aes(color = "mean=0, sd=1")) +
  stat_function(fun = dnorm, args = list(0.5,2),
                aes(color = "mean=0.5, sd=2")) +
  scale_colour_manual("期望和标准差", values = c("red", "blue"))+
  scale_y_continuous(name = "概率值(probability)") +
  theme(legend.text = element_text(size=16), legend.title = element_text(size=16))
```

可以使用chrome浏览器开发工具看到其对应的html元素:

<!--- html develop tool view--->
<div class="figure" style="text-align: center">
<img src="01-introduction-slide_files/figure-html/unnamed-chunk-14-1.png" alt="正态分布(n=500)">
<p class="caption">正态分布(n=500)</p>
</div>

11.4.4 静态上传Xaringan slide

静态网页,正如其名,只能相对“固定”地加载封装好的.html文件或.pdf文件之类。显然,交互性和动态性就暂时难以获得。因为我们写作Slide是在另一个repo文件夹下(repo如course-econometrics文件夹下,并使用Xaringan包),然后确认没问题后才能把渲染好的.html形式的Slide文件(一般是如02-simple-reg-basic-slide.html)拷贝到网站repo文件夹指定目录下(repo如netlify,特定目录为\netlify\static\slides-course)。同时,如果html文件不是完全自容的(self-container),那你还必须把使用到的图片数据等文件夹也一同拷贝过去(这就非常糟糕了!因为你的slide制作可能会经常变动或更新)。那么要如何自动关联Xaringan(实现slide)和blogdown(实现website)呢?

当然动态性和关联性也是有成本的。如果Xaring里的文件需要渲染很久(比如ggplot作图很多,数据分析很多),那么blogsite会负担比较大。

11.4.5 知识版权保护问题

Xaringan slide上传到website后,文字和图片等都可以任意拷贝下载。对于开源项目,这完全没毛病。但是,对于一些重视原创知识保护的人,这就是很大的“命门”了。

11.5 动态网站

11.5.1 评估工具

11.6 Flarum

Flarum 官网

Flarum github repo flarum/flarum

11.7 建站技巧清单

11.7.1 本地数据和图片文件

既然blogdown下就能直接编写.Rmd文件,那就可以直接在里面进行数据分析、可视化等操作。问题的关键是如何引用本地数据(“data/”目录下)和图片文件(“pic/”目录下)。blogdown 的文件位置和关系,具体可以参看Adding and reading local data files in R Markdown posts

有两种插入图片的办法。不论如何,图片源文件默认都是放在static/文件夹下(可参看knitr::include_graphics in blogdown)。

  • 采用markdown语言:
# 源文件默认在`static/`文件夹下
![](/img/facet-multiple-geom.png)

markdown语言方法插入图片
  • 或者采用函数 knitr::include_graphics()
# 源文件默认在`static/`文件夹下
knitr::include_graphics("/pic/facet-multiple-geom.png", error = FALSE)
knitr::include_graphics("pic/facet-multiple-geom.png", error = FALSE)
图 11.1: include_graphics函数方法插入图片

11.7.2 include_graphics正确调用图片

有时候使用knitr::include_graphics函数调用图片,需要额外设定参数error = FALSE。(参看网络问答

knitr::include_graphics("pic/check-box-invisible.png", error = FALSE)
图 11.2: 额外设定参数error = FALSE

11.7.3 include_url服务器拒绝访问

报错内容:使用knitr::include_url()函数后,hugo或blogdown渲染网站,都无法显示对本地静态("static/course/slide.html")文件Xaringan slide

具体报错内容如下

  • 本地预览下,include_url('/path/to/file.html')则显示报错localhost 拒绝了我们的连接请求。。markdown语法[](/path/to/file.html)则只显示为纯文本。

  • netlify built在线建站后,include_url('/path/to/file.html')则显示服务器.netlify.app 拒绝了我们的连接请求。。但是markdown语法[](/path/to/file.html)则显示正常。

  • chrome开发工具显示:<div id="main-frame-error class="interstitial-wrapper" </div>

1.use markdown syntax(link Ok)。课件(Xaringan slide)第01章-绪论.html

[第01章-绪论.html](/course-statistics/03-slide-class/03-visualization.html)

2.try local graphic(graph OK)。

knitr::include_graphics("pic/facet-multiple-geom.png", error = FALSE)

3.test the include_url() (error):

```{r}
#slide01, echo=T, eval=F, out.extra='style="border: none;"', out.width='80%'
# out.extra='style="border: none;"', out.width='80%'
knitr::include_url("/course-statistics/03-slide-class/03-visualization.html")
```

4.try iframe html tag(error):

<iframe seamless src="/course-statistics/03-slide-class/03-visualization.html" width="100%" height="500"></iframe>

5.test xaringanExtra::embed_xaringan()(error):

```{r}
xaringanExtra::embed_xaringan(
  url = "/course-statistics/03-slide-class/03-visualization.html",
  ratio = "16:9"
)
```

排除可能的问题:

  • win10 系统防火墙设置

  • chrome隐私设置

  • knitr::include_url()函数是否正确,包括chunk option设定。

  • url地址是否正确。外部绝对url地址显示正确。本地相对地址则报错。但使用knitr::include_graphic()加载本地图片则能正确显示图片。排除url地址误写。

  • 使用iframe tag方法,仍旧报错。

  • 使用xaringanExtra::embed_xaringan(),仍旧报错。

怀疑的可能原因:

  • 根据hugo团队的观点,它们会原生支持rveal.js,但不是remark.js。(见队长讨论

  • win10操作系统静默升级,改变了hugo的链接访问权限??

  • Netlify的建站规则发生了变化?

  • knitr包的bug(iframe id失效)?

11.7.4 目录、节编号和脚注

目录(toc)和节编号(number_sections)可以在rmardown文件yaml头里设置1

output:
  blogdown::html_page:
    toc: true
    number_sections: true

脚注的写法。正文里直接在脚注位置添加[^1],并在rmarkdown最后进行对应的注释说明:

[^1]: [Headings with automatic numbering](https://github.com/rstudio/blogdown/issues/140)

11.7.5 网址引用格式

如果需要设置特定的网址应用格式,则需要进一步配置config.toml。

  • baseurl存在的斜杠问题,可能会影响netlify建站更新。

  • 网页链接显示最好是稳定的。可以通过日期+slug的形式来固定,具体设置为:

[permalinks]
  post = "/:year/:month/:day/:slug/"

11.7.6 添加浮动导航目录

有时候我们希望给post博文添加浮动导航目录(floating toc)。官方模板下,默认的post是基于widget: pages样式。其视觉效果特点是:

  • 在pc端,chrome浏览器下post博文正文宽度为720px,如果显示器分辨率为1920*1080,那么正文的宽度占比为37.5%。按照时兴的说法就是“屏占比”太低!

  • 在移动手机端,post博文正文宽度显示不错。

  • 默认情况下,是没有目录导航的(toc)。当然也可以通过添加yaml参数toc: true来添加目录导航,但是toc显示在正文之前(而且没有“目录”字样)。对于分节较多的长篇博文,非常不利于读者阅读定位和前后对照。

因此,对于分节较多的长篇博文,自然就有了”侧边浮动目录”(sidebar floating toc)的个性化页面样式修订念头。yihui建议可以通过JavaScript或CSS进行设定。Xiaoou WANG也提供了一个hugo语法下的实现方法,但是有不少bug。又比如CharlieLeee的方法,只是对于.md格式的写作有效,而对.Rmd格式的写作无效。因此,最后的忠告就是专注于内容写作:劝君更进一杯酒,模板莫要闲折腾。

11.7.7 定制box text样式

hugo academic模板下,可以使用CSS样式定制个性化文本窗框(box text)的集中常用div类型(例如puzzle、fyi、demo、note)。主要步骤如下:

  • 步骤1:在工作目录下建立一个文件"netlify/assets/scss/custom.scss"

  • 步骤2:在custom.scss正常编写个性化css代码,设定文本窗框的各类属性。

/* -----------div tips------------- */
div.puzzle, div.fyi, div.demo, div.note {
  padding: 1em;
  margin: 1em 0;
  padding-left: 100px;
  background-size: 70px;
  background-repeat: no-repeat;
  background-position: 15px center;
  min-height: 120px;
  color: #1f5386;
  background-color: #bed3ec;
  border: solid 5px #dfedff;
}

/* -----------image icon------------- */

div.puzzle {
  background-image: url("/img/puzzle2-piece.png");
}

div.fyi {
 background-image: url("/img/fyi2-comments.png");
}

div.demo {
  background-image: url("/img/demo2-laptop-code.png");
}

div.note {
  background-image: url("/img/note2-light-bulb-ff5500.png");
}
  • 步骤3:在R markdown文件中设定yaml区域的参数,正确关联到custom.scss路径。
output:
  blogdown::html_page:
    css: ../../../assets/scss/custom.scss
    toc: true
    number_sections: true
  • 步骤4:在R markdown文件中进行文本写作,使用div方法或:::方法引用特定窗框类型。
<div class="puzzle">
My content goes in here!
</div>
:::puzzle
My content goes in here!
:::

下面给大家展示上述定义的4种窗框类型:

(这是一个puzzle窗框类型)

步骤1中:

  1. .scss格式只是.css格式的一个扩展,前者运用了Sass语言。二者的差异请参看:Animating Your Hugo Academic Site

  2. 最好保持custom.scss的文件命名”custom”。经过测试,如果改动文件名,哪怕yaml里正确引用了路径,也会出现无法引用css风格的情况。猜测是hugo其他参数里默认了这个custom.scss的命名。

  3. 如果渲染建站成功,会发现hugo自动生成了一个相匹配的scss/custom.css的文件,或许这就是.scss的一个效果之一。

(这是一个note窗框类型)

步骤2中:需要注意图片存放及路径的正确引用。对于hugo academic模板而言,background-image:url('/path/of/image.png')是相对路径,相对于static/文件夹而言的。因此,你可以把图片文件存放在static/img/note.png或者static/pic/note.png,然后正确引用这一文件路径!

(这是一个fyi(for your information)窗框类型)

步骤3中:

  1. 注意css文件的相对引用路径。如果工作根目录为netlify/,那么R markdown写作文件以netlify/content/post/your-post/index.Rmd为例。因为此时的css文件存放在netlify/assets/scss/custom.scss,那么css文件的相对引用路径应该设定为css: ../../../assets/scss/custom.scss

  2. 此时要求R markdown写作文件指定输出风格output:。请参看:making pretty note boxes。这也意味着,每一次R markdown写作文件都要做这样的设定,可能略显麻烦了一点。

(这是一个demo窗框类型)

步骤4中:R markdown写作文件,要求是.Rmd格式,不是.md格式,或者.Rmarkdown格式。三者的差异请参看:R Markdown vs. Markdown

11.7.8 添加站内搜索功能

问题:原来hugo academic自带的站点搜索框,在升级后突然消失了。使得查找和检索站内博文或内容异常困难。

blogdown社区的一些讨论如下:

下面是放狗找到的关于hugo添加站内搜索功能的文档或帮助。总体而言,这些方法能够实现全站点所有内容的搜索功能,但是需要学习到一些额外的新知识。

Fuse.js号称是不依赖其他工具链(zero dependencies)。但是有不少用户反映,Fuse.js的搜索结果不是很理想(见这篇博文)。

  • Algolia(商业)。Static site search with Hugo + Algolia。介绍了使用商业服务类型的Algolia来搭建站内搜索功能,过程步骤也比较详尽。但是博文的更新时间为March 2, 2018,而且感觉依赖的工具较多,链条比较复杂,可能实现难度比较大。

以下是几个中文世界的应用:


  1. Headings with automatic numbering↩︎