目录

Hexo 迁移到 Hugo 记录

参考:

为什么要迁移到Hugo?

因为 Hexo 太慢啦~ Node的依赖也乱七八糟~ 顺便想要简洁主题找不到合适自己的,要么太过简洁,想要的功能都没有;要么太过臃肿,阅读界面太乱;太难了~

迁移需要注意的地方

关于 Front-matter

时间格式 date

Hexo 中时间格式与 Hugo 格式不太相同,Hugo 对时间格式的要求比较严格,Hexo 中YYYY-MM-DD HH:MM:SS格式的时间在 Hugo 中无法被正确识别。

Hugo 中使用的时间格式如下:

1
YYYY-MM-DD HH:MM:SS +08:00

最后的+08:00代表中国的时区是 GMT+8。

另外,Hexo 中 Front Matter 代表博客更新时间的updated选项,在 Hugo 中是lastmod

由于写的文章太多,我直接用正则表达式搜索替换:

1
([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8])))\s([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])

关于迁移旧链接

迁移博客过程中最重要的莫过于保证文章的 URL 不变,不然,这将会非常不利于 SEO。好不容易有一篇文章出现在 Google 相关搜索结果的前排,却因为 URL 的变化导致原链接 404 从而导致该文章从 Google 的索引中移除,这一定会是非常令人沮丧的😶。下面我们就来说说怎么解决这个问题~

Hugo 默认使用 “Pretty URLs”(漂亮URL), 而 Hexo 使用 “Ugly URLs”(丑陋URL)

1
2
3
4
5
6
7
8
├─content           # 网站源代码目录
| ├─_index.md       # URL转换为:https://example.com/
| ├─posts           # 一个名为posts的章节文件夹
| | ├─_index.md     # URL转换为:https://example.com/posts/
| | ├─first.md      # URL转换为:https://example.com/posts/first/
| | └─topic         # 章节内的子页面内容目录(包含md文件和图片)
| | | ├─second.md   # URL转换为:https://example.com/posts/topic/second/
| | | └─123.jpg     # second.md文件引用的本地图片

“_index.md” 和 “index.md” 文件在 Hugo 中具有特殊含义(用于组织页面)。在 “Pretty URLs” 渲染下,与实际路径一致。

若文件名不为 “_index.md” 或 “index.md” 时,“Pretty URLs” 将 md 文件渲染为目录

会导致后果:md 文件引用本地图片时,渲染路径比实际路径多了一个与文件名同名的目录名,导致引用图片失败。

而 “Ugly URLs” 则会直接渲染为 xxx.html,这会导致迁移后 Hexo 版本的链接会直接失效。若有搜索引擎收录了 Hexo 版本的链接,则会指向 404 页面

这是我不能接受的,于是我翻了翻 Hugo 文档,终于找到了解决方法

假设您在content/posts/my-awesome-blog-post.md中创建了一条新内容。内容是您以前的帖子的修订版,位于content/posts/my-original-url.md。您可以在新的my-awesome-blog-post.md的开头创建别名字段,在其中可以添加以前的路径。以下示例说明如何在 YAML 中创建此字段。(机翻勿吐槽🙈️……)

content/posts/my-awesome-post.md

1
2
3
aliases:
    - /posts/my-original-url/
    - /2010/01/01/even-earlier-url.html

当你输入aliases中任意一个路径时,他就会跳转到my-awesome-post.md所在的页面啦~

给 Hugo 添加新功能

永久链接

之前使用 Hexo 的时候,用的是hexo-abbrlink插件来处理永久链接。迁移到 Hugo 之后,发现没有这种插件,顿时就慌了,那可怎么办呀~

搜索了一下发现还是有大佬利用 Hugo 自建函数实现了这个功能。

永久链接生成方案

大佬的永久链接生成方案是直接对时间 + 文章名生成字符串做一下 md5 然后取任意 4-12 位。想了一下,这样的 hash 冲撞概率还是挺小的,我觉得可以!

那么接下来说说怎么把这个方案应用到 Hugo 中……

Hugo 在永久链接中支持一个参数:slug。简单来说,我们可以针对每一篇文章指定一个 slug,然后在 config.toml 中配置permalinks包含slug参数,就可以生成唯一的永久链接。我们的目的就是对每篇文章自动生成一个 slug

修改archetypes/default.md添加如下一行:

1
2
3
4
5
---
#...
slug: {{ substr (md5 (printf "%s%s" .Date (replace .TranslationBaseName "-" " " | title))) 4 8 }}
#...
---

这样在每次使用hugo new的时候就会自动填写一个永久链接了。

之后修改config.toml添加如下行:

1
2
[permalinks]
  posts = "/post/:slug"

DisqusJS

我现在用的是 LoveIt 主题,官方集成了 Disqus 评论模块,却没有集成 DisqusJS,于是决定自己动手~

以 LoveIt 主题为例:

  1. 拷贝themes/LoveIt/layouts/partials/comment.html博客根目录/layouts/partials/comment.html

  2. id="comments"的 div 块里加入以下代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    <div id="comments">
    ...
    {{- /* DisqusJS Comment System */ -}}
        {{- $disqusjs := $comment.disqusjs | default dict -}}
        {{- if $disqusjs.enable -}}
            <!-- jsDelivr -->
            {{- $source := $cdn.disqusjsCSS | default "lib/disqusjs/disqusjs.css" -}}
            <link rel="stylesheet" href="{{- $source -}}">
            {{- $source := $cdn.disqusjsJS | default "lib/disqusjs/disqus.js" -}}
            <!-- jsDelivr -->
            <script src="{{- $source -}}"></script>
            <div id="disqus_thread" class="comment"></div>
            <script>
                var dsqjs = new DisqusJS({
                    shortname: {{- $disqusjs.shortname -}},
                    siteName: {{- $disqusjs.siteName -}},
                    api: {{- $disqusjs.api -}},
                    apikey: {{- $disqusjs.apikey -}},
                    admin: {{- $disqusjs.admin -}},
                    adminLabel: {{- $disqusjs.adminLabel -}}
                });
            </script>
    {{- end -}}
    ...
    </div>
    
  3. 拷贝themes/LoveIt/assets/data/cdn/jsdelivr.yml博客根目录/assets/data/cdn/jsdelivr.yml

  4. libFiles结点里添加以下代码:

    1
    2
    3
    4
    
    libFiles:
        ...
        disqusjsCSS: [email protected]1.3/dist/disqusjs.css
        disqusjsJS: [email protected]1.3/dist/disqus.js
    
  5. 修改config.toml添加如下行:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    [params.page.comment]
      enable = true
      ...
      # DisqusJS 评论系统设置
      [params.page.comment.disqusjs]
        # Reborn 新增
        enable = true
        # Disqus 的 shortname,用来在文章中启用 Disqus 评论系统
        shortname = "Your shortname"
        siteName = 'Your siteName'
        apikey = 'Your apikey'
        api = 'https://disqus.skk.moe/disqus/'
        admin = 'Your admin'
        adminLabel = 'admin'
        ...
    

PWA/Service Worker

懒得写了,网上一堆解释,直接贴源码算了:

  1. static/目录下新建sw.js文件,然后加入以下代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    
    importScripts('https://cdn.jsdelivr.net/npm/workbox-cdn/workbox/workbox-sw.js');
       
    if (workbox) {
        console.log(`Yay! Workbox is loaded 🎉`);
       
        workbox.core.setCacheNameDetails({
            prefix: "Your Username"
        });
       
        workbox.core.skipWaiting();
        workbox.core.clientsClaim();
       
        workbox.precaching.precacheAndRoute([
            {
                "url": "/index.html",
                "revision": "MD5 of your index.html"
            },
            {
                "url": "/css/style.min.css",
                "revision": "MD5 of your style.css"
            }
        ], {});
       
        workbox.routing.registerRoute(/(?:\/)$/,
            new workbox.strategies.StaleWhileRevalidate({
                cacheName: "html",
                plugins: [
                    new workbox.expiration.ExpirationPlugin({
                        maxAgeSeconds: 60 * 60 * 24 * 7,
                        // purgeOnQuotaError: !0
                    })
                ]
            }), "GET");
       
        workbox.routing.registerRoute(
            /\.(?:js|css)$/,
            new workbox.strategies.StaleWhileRevalidate({
                cacheName: 'static-resources'
            })
        )
       
        workbox.routing.registerRoute(
            /\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico)$/,
            new workbox.strategies.CacheFirst({
                cacheName: "images",
                plugins: [new workbox.expiration.ExpirationPlugin({
                    maxEntries: 100,
                    maxAgeSeconds: 7 * 24 * 60 * 60,
                    // purgeOnQuotaError: !0
                })]
            }), "GET");
       
        // Fonts
        workbox.routing.registerRoute(
            /\.(?:eot|ttf|woff|woff2)$/,
            new workbox.strategies.CacheFirst({
                cacheName: "fonts",
                plugins: [
                    new workbox.expiration.ExpirationPlugin({
                        maxEntries: 1000,
                        maxAgeSeconds: 60 * 60 * 24 * 30
                    }),
                    new workbox.cacheableResponse.CacheableResponsePlugin({
                        statuses: [0, 200]
                    })
                ]
            })
        );
       
        workbox.routing.registerRoute(
            /^https:\/\/fonts\.googleapis\.com/,
            new workbox.strategies.StaleWhileRevalidate({
                cacheName: 'google-fonts-stylesheets'
            })
        );
       
        workbox.routing.registerRoute(
            /^https:\/\/fonts\.gstatic\.com/,
            new workbox.strategies.CacheFirst({
                cacheName: 'google-fonts-webfonts',
                plugins: [
                    new workbox.cacheableResponse.CacheableResponsePlugin({
                        statuses: [0, 200]
                    }),
                    new workbox.expiration.ExpirationPlugin({
                        maxAgeSeconds: 60 * 60 * 24 * 365,
                        maxEntries: 30
                    })
                ]
            })
        );
       
        // external resources
        workbox.routing.registerRoute(
            /(^https:\/\/cdn\.jsdelivr\.net.*?(\.js|\.css))|(^https:\/\/cdnjs\.cloudflare\.com)/,
            new workbox.strategies.CacheFirst({
                cacheName: "external-resources",
                plugins: [
                    new workbox.expiration.ExpirationPlugin({
                        maxEntries: 1000,
                        maxAgeSeconds: 60 * 60 * 24 * 30
                    }),
                    new workbox.cacheableResponse.CacheableResponsePlugin({
                        statuses: [0, 200]
                    })
                ]
            })
        );
       
        workbox.googleAnalytics.initialize({});
    } else {
        console.log(`Boo! Workbox didn't load 😬`)
    }
    
  2. 拷贝themes/LoveIt/layouts/_default/baseof.html博客根目录/layouts/_default/baseof.html

  3. </body>后加入以下代码:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    <script>
    // 可以这么注册 Service Worker
    if ('serviceWorker' in navigator) {
        // 为了保证首屏渲染性能,可以在页面 load 完之后注册 Service Worker
        window.onload = function () {
            navigator.serviceWorker
                .register('/sw.js', { scope: '/' })
                .then(function(registration) {
                    console.log('Service Worker Registered');
                });
    
            navigator.serviceWorker
                .ready
                .then(function(registration) {
                    console.log('Service Worker Ready');
                });
        };
    }
    </script>
    

添加清理旧版本策略

修改sw.js文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
...
workbox.core.setCacheNameDetails({
        prefix: "Your name",
        suffix: 'v2', // 修改这里就可以更新了
        precache: 'precache',
        runtime: 'runtime'
});

// 跳过等待期
workbox.core.skipWaiting();
// 一旦激活就开始控制任何现有客户机(通常是与skipWaiting配合使用)
workbox.core.clientsClaim();
// 删除过期缓存
workbox.precaching.cleanupOutdatedCaches();
...
...
// 安装阶段跳过等待,直接进入 active
self.addEventListener('install', function (event) {
    event.waitUntil(self.skipWaiting());
});

// Call Activate Event to remove old cache
self.addEventListener('activate', function (event) {
    event.waitUntil(
        Promise.all([
            // 更新客户端
            self.clients.claim(),

            // 清理旧版本
            caches.keys().then(function (cacheList) {
                return Promise.all(
                    cacheList.map(function (cacheName) {
                        if (/(v\d+)/.test(cacheName) === false || workbox.core.cacheNames.suffix !== RegExp.$1) {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
        ])
    );
});

然后确保每个new workbox.strategies.xxxcacheName都是如下代码:

1
2
3
new workbox.strategies.xxx({
    cacheName: "your-cache-name-" + workbox.core.cacheNames.suffix,
})

Github Action 自动部署

Github Action 是个好东西,一定要好好利用,我已经写好一个 Hugo 部署到 github page 的 github action 了,直接用就好了~

  1. 在博客源码的 Github 仓库项目中Settings—Secrets设置好GH_TOKENGIT_NAMEGIT_EMAIL

  2. 新建.github/workflows/deploy.yml,示例:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    
    name: 自动部署 Hugo
       
    on:
      push:
        branches:
          - master
       
    jobs:
      build:
        runs-on: ubuntu-latest
        strategy:
          matrix:
            node-version: [10.x]
       
        steps:
          - name: 开始运行
            uses: actions/[email protected]
       
          - name: 缓存
            uses: actions/[email protected]
            id: cache-dependencies
            with:
              path: node_modules
              key: ${{runner.OS}}-${{hashFiles('**/package-lock.json')}}
       
          - name: 部署准备
            run: |
              git clone https://${{secrets.GH_TOKEN}}@github.com/username/username.github.io.git .deploy_git
          - name: 部署博客
            uses: RebornQ/[email protected].1.2
            with:
              github_token: ${{secrets.GH_TOKEN}}
              username: ${{secrets.GIT_NAME}}
              email: ${{secrets.GIT_EMAIL}}
              repo_url: https://${{secrets.GH_TOKEN}}@github.com/username/username.github.io.git
    

音乐 shortcode 支持本地歌词文件

太简单了,看 APlayer 文档就能找出来,直接贴代码:

  1. 拷贝themes/LoveIt/layouts/shortcodes/music.html博客根目录/layouts/shortcodes/music.html

  2. 修改如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    ...
    {{- if .Get "url" -}}
    ...
    {{- $lrc := .Get "lrc" -}}
    {{- with dict "Path" $lrc "Resources" .Page.Resources | partial "function/resource.html" -}}
        {{- $lrc = .RelPermalink -}}
    {{- end -}}
    <meting-js ... lrc="{{ $lrc }}" ... ></meting-js>
    ...