从零开始搭建个人博客网站(hexo-fluid+netlify+cloudflare)

🍀 前言

笔者从小就有一个梦想,那就是能拥有一个属于自己的网站,那真的是泰裤辣!!但是对于当时一个连html/css/js是啥都不知道的小白来说有辣么亿点点难..后来我遇见了hexo框架,开箱即用,大大降低了难度,感觉可以尝试一下[捂脸],所以笔者就从零开始,慢慢摸索。而写这篇文章,就是为了记录总结笔者建站(掉坑)的全过程...

✨简介

Hexo是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他标记语言)解析文章,在几秒内,即可利用靓丽的主题生成静态网页

Fluid是基于 Hexo 的一款 Material Design 风格的主题,由 Fluid-dev 负责开发与维护。

netlify是一个提供托管服务的平台,免费额度充足,速度较快且易于上手

Cloudflare是国外著名的CDN供应商,可以提供免费的DNS服务和SSL证书,用来加速和保护网站

📌准备工作

🔧安装博客

安装Hexo

Hexo官方文档

使用 npm 安装 Hexo:

npm install -g hexo-cli

{% note success %}
使用淘宝镜像加速 npm config set registry http://registry.npmmirror.com

安装完成后新建博客项目:

hexo init <folder>
cd <folder>
npm install

安装Fluid主题

Fluid用户手册

下载 最新release 版本解压到 themes 目录,并将解压出的文件夹重命名为 fluid

然后在博客目录下创建 _config.fluid.yml,将主题的 _config.yml内容复制过去

如下修改 Hexo 博客目录中的 _config.yml:

theme: fluid  # 指定主题

language: zh-CN  # 指定语言,会影响主题显示的语言,按需修改

首次使用主题的「关于页」需要手动创建:

hexo new page about

创建成功后修改/source/about/index.md,添加layout 属性:

---
title: 标题
layout: about
---

这里写关于页的正文,支持 Markdown, HTML

至此,博客已基本安装完成,以下为常用hexo命令:

# 生成静态网页
hexo g

#启动服务,默认地址为 http://localhost:4000/ 在浏览器中输入地址即可预览
hexo s

# 删除生成的静态网页
hexo clean

#创建一篇新文章或者新的页面
hexo new [layout] <title>
#文章的布局(layout), 默认为 post,可以通过修改 _config.yml 中的default_layout 参数来指定默认布局
#Hexo 有三种默认布局:post、page 和 draft。在创建这三种不同类型的文件时,它们将会被保存到不同的路径
#而您自定义的其他布局和 post 相同,都将储存到 source/_posts 文件夹

🎨博客配置

Fluid配置文档
修改博客目录下的_config.yml"站点配置" 和 _config.fluid.yml"主题配置" 以配置博客

首页Slogan(打字机) + Hitokoto(一言)

修改主题配置:

index:
  slogan:
    enable: true
    text: 这是一条 Slogan
    api:
      enable: true
      url: "https://v1.hitokoto.cn/?c=d"
      method: "GET"
      headers: {}
      keys: ["hitokoto"]

url: 一言的请求接口,参数c为句子类型见下表

参数 说明
a 动画
b 漫画
c 游戏
d 文学
e 原创
f 来自网络
g 其他
h 影视
i 诗词
j 网易云
k 哲学
l 抖机灵
其他值 作为 动画 类处理

LaTeX 数学公式

设置主题配置:

post:
  math:
    enable: true
    specific: false
    engine: katex

更换 Markdown 渲染器:

npm uninstall hexo-renderer-marked --save
npm install hexo-renderer-markdown-it --save
npm install @traptitech/markdown-it-katex --save

然后在站点配置中添加:

markdown:
  plugins:
    - "@traptitech/markdown-it-katex"

安装完成后执行 hexo clean


安装hexo-abbrlink:

npm install hexo-abbrlink --save

修改站点配置:

#permalink: :year/:month/:day/:title/
permalink: post/:abbrlink/

页脚显示网站运行时长[1]

在主题配置的 footer: content 中添加:

footer:
  content: '
    <a href="https://hexo.io" target="_blank" rel="nofollow noopener"><span>Hexo</span></a>
    <i class="iconfont icon-love"></i>
    <a href="https://github.com/fluid-dev/hexo-theme-fluid" target="_blank" rel="nofollow noopener"><span>Fluid</span></a>
    <div style="font-size: 0.85rem">
      <span id="timeDate">载入天数...</span>
      <span id="times">载入时分秒...</span>
      <script src="/js/duration.js"></script>
    </div>
  '

在博客目录下创建 source/js/duration.js,内容如下:

!(function() {
  /** 计时起始时间,自行修改 **/
  var start = new Date("2020/01/01 00:00:00");

  function update() {
    var now = new Date();
    now.setTime(now.getTime()+250);
    days = (now - start) / 1000 / 60 / 60 / 24;
    dnum = Math.floor(days);
    hours = (now - start) / 1000 / 60 / 60 - (24 * dnum);
    hnum = Math.floor(hours);
    if(String(hnum).length === 1 ){
      hnum = "0" + hnum;
    }
    minutes = (now - start) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
    mnum = Math.floor(minutes);
    if(String(mnum).length === 1 ){
      mnum = "0" + mnum;
    }
    seconds = (now - start) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
    snum = Math.round(seconds);
    if(String(snum).length === 1 ){
      snum = "0" + snum;
    }
    document.getElementById("timeDate").innerHTML = "本站安全运行&nbsp"+dnum+"&nbsp天";
    document.getElementById("times").innerHTML = hnum + "&nbsp小时&nbsp" + mnum + "&nbsp分&nbsp" + snum + "&nbsp秒";
  }

  update();
  setInterval(update, 1000);
})();

不要忘记把上面注释的时间改为自己的时间,至此这项功能就引入到

里了。


评论 (Twikoo)

先用netlify部署twikoo

在主题配置中开启并指定评论模块:

post:
  comments:
    enable: true
    type: twikoo

在下面配置参数:

twikoo:
  envId: https://xxx.netlify.app/.netlify/functions/twikoo
  # 将xxx.netlify.app换成自己部署在netlify上的域名

twikoo评论系统就此部署好啦,可以点击评论窗口的“小齿轮”图标,设置管理员密码,进入twikoo管理面板中进行进一步配置和管理


看板娘Live2D

1. 旧版本

{% note warning %}
只支持Cubism 2.1的旧版模型,不建议使用

安装依赖

npm install --save hexo-helper-live2d

安装模型

npm install live2d-widget-model-shizuku

模型列表(大部分都很抽象)

  • live2d-widget-model-chitose
  • live2d-widget-model-epsilon2_1
  • live2d-widget-model-gf
  • live2d-widget-model-haru
  • live2d-widget-model-haruto
  • live2d-widget-model-hibiki
  • live2d-widget-model-hijiki
  • live2d-widget-model-izumi
  • live2d-widget-model-koharu
  • live2d-widget-model-miku
  • live2d-widget-model-ni-j
  • live2d-widget-model-nico
  • live2d-widget-model-nietzsche
  • live2d-widget-model-nipsilon
  • live2d-widget-model-nito
  • live2d-widget-model-shizuku
  • live2d-widget-model-tororo
  • live2d-widget-model-tsumiki
  • live2d-widget-model-unitychan
  • live2d-widget-model-wanko
  • live2d-widget-model-z16

修改主题配置

live2d:
  enable: true
  model:
    use: shizuku
  display:
    position: left
    width: 150
    height: 300

2. 新版本(CDN方法)

修改自stevenjoezhang大佬的版本 [2][3][4][5]

{% note success %}
支持Cubism 3及以上的版本,可自定义,交互功能丰富

在主题配置的 footer: content 中添加:

footer:
  content: '
    <!-- PixiJS -->
    <script src="https://blog.jsdmirror.com/npm/pixi.js@7.x/dist/pixi.min.js"></script>
    <script src="https://blog.jsdmirror.com/gh/giraffishh/live2d-widget@master/autoload.js"></script>
  '

感谢 https://blog.jsdmirror.com/ 的公益 jsdelivr 国内CDN加速节点
jsdelivr官方节点(慢):gcore.jsdelivr.net testingcf.jsdelivr.net quantil.jsdelivr.net fastly.jsdelivr.net cdn.jsdelivr.net

自定义配置:

首先将项目fork到自己github的仓库中

说明一下几个文件的作用:

文件 作用
autoload.js 自动加载看板娘
waifu.css 看板娘样式
waifu-tips.js 看板娘说话的脚本
waifu-tips.json 看板娘说话的内容
live2d.min.js 加载Cubism 2.1的模型的脚本
live2dcubismcore.min.js 加载Cubism 3及以上的模型的脚本

你可以对照以上文件的查看可选的配置项目。

记得要修改在autoload.js的开头中定义的加载看板娘的路径,将其改成自己仓库的路径

const live2d_path = "https://blog.jsdmirror.com/gh/{GitHub用户名}/live2d-widget@master/";

如果要自定义模型,将模型仓库fork到自己github的仓库中就可以往里面添加自己的Live2D模型了

{% note warning %}
注意要按照原有模型的目录结构放入新模型
然后把模型的组织文件(通常是{模型名}.json)改成index.json才能被正确识别

记得要修改在autoload.js的结尾中定义的模型仓库的路径,将其改成自己模型仓库的路径

cdnPath: "https://blog.jsdmirror.com/gh/{GitHub用户名}/live2d_api@master/"

📤部署至netlify

{% note success %}
笔者喜欢使用Sourcetree管理git仓库

在github中新建一个公开仓库,克隆到本地,将博客目录内所有内容移入本地仓库,再推送至回远端
然后在netlify中部署该仓库(与部署twikoo同理)

具体可以参考:

📍设置域名

免费域名

在netlify的Domain management中可以设置一个系统分配的二级域名xxx.netlify.app

私人域名

可以在阿里云、腾讯云等域名注册商购买域名,笔者以阿里云为例

使用阿里云DNS解析域名

阿里云提供了免费版的域名解析服务,但不包括SSL证书,访问时浏览器会提示网站不安全

购买后在域名解析处,添加以下记录(笔者另加了blog.xxx.xxx的二级域名),指向Netlify分配的二级域名

0d37b84a644204bbd13b018383ed7866.jpeg

使用 Cloudflare DNS 解析域名

在阿里云域名 > 管理 > DNS修改 更改DNS服务器(名称服务器)为Cloudflare提供的名称服务器

ddc950191db2e3ab68a02c309d7c653f.jpeg

在Cloudflare中添加购买的域名

c87c74bc59a6c96cb7bcf28114ef3e76.jpeg

添加DNS记录(笔者另加了blog.xxx.xxx的二级域名),指向Netlify分配的二级域名

be9615705bfa14b60895251bdfbfcc8d.jpeg

在名称中填入@即解析主域名

{% note warning %}
如果你的网站页面在加载的时候,进度条在最后卡住,尝试关闭Cloudflare的Rocket Loader™功能
其他功能能开的就看着开吧,反正免费


最后在netlify的Domain management添加购买的域名

d800729b8092dfad88a094338a825c5b.jpeg

✏️Hexo Netlify CMS 在线编辑博客[6]

在netlify的Site configuration中开启Identity

下滑找到Git Gateway开启

修改博客目录下的_config.yml

skip_render: admin/*

在博客项目的source文件夹中,创建admin文件夹,并新建两个文件index.html和config.yml于其中

在index.html中添加以下内容:

<!doctype html>
<html>
    
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="x-UA-Compatible" content="IE=Edge">
        <meta name="apple-mobile-web-app-status-bar-style" content="white" />
        <script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
        <title>CMS-在线编辑博客</title>
    </head>
    
    <body>
        <script defer="true" src="https://cdn.jsdelivr.net/npm/netlify-cms@2/dist/netlify-cms.js"></script>
    </body>

</html>

在config.yml中添加如下内容

backend:
  # name: test-repo # 测试专用 https://www.netlifycms.org/docs/test-backend/
  name: git-gateway # https://www.netlifycms.org/docs/git-gateway-backend/
  branch: main # 要更新的分支(可选;默认为主分支)
  squash_merges: true # 合并提交

local_backend: true

# This line should *not* be indented
publish_mode: editorial_workflow

# This line should *not* be indented
media_folder: "source/images/uploads" # 媒体文件将存储在图片/上载下的Repo中。
public_folder: "/images/uploads" # 上传的媒体的src属性将以/images/uploads开头。

locale: "zh_Hans" # 语言环境 https://github.com/netlify/netlify-cms/tree/master/packages/netlify-cms-locales/src

collections:      # https://www.netlifycms.org/docs/configuration-options/#collections
  - name: "posts" # 在路由中使用,例如:/admin/collections/blog。
    label: "Post" # 在用户界面中使用
    folder: "source/_posts" # 存储文件的文件夹的路径。
    create: true # 允许用户在这个集合中创建新的文件。
    fields: # 每份文件的字段,通常是前面的内容。
      - {label: "顶部图", name: "banner_img", widget: "image", required: false} 
      - {label: "文章封面", name: "index_img", widget: "image", required: false} 
      - {label: "文章排序", name: "sticky", widget: "number", required: false, hint: "数值越大,该文章越靠前"}
      - {label: "标题", name: "title", widget: "string" }
      - {label: "发布日期", name: "date", widget: "datetime", format: "YYYY-MM-DD HH:mm:ss", dateFormat: "YYYY-MM-DD", timeFormat: "HH:mm:ss", required: false}
      - {label: "更新日期", name: "updated", widget: "datetime", format: "YYYY-MM-DD HH:mm:ss", dateFormat: "YYYY-MM-DD", timeFormat: "HH:mm:ss", required: false}
      - {label: "标签", name: "tags", widget: "list", required: false}
      - {label: "分类", name: "categories", widget: "list", required: false}
      - {label: "关键词", name: "keywords", widget: "list", required: false}
      - {label: "摘要", name: "excerpt", widget: "string", required: false}
      - {label: "永久链接", name: "permalink", widget: "string", required: false}
      - {label: "评论", name: "comments", widget: "boolean", default: true, required: false}
      - {label: "内容", name: "body", widget: "markdown", required: false}

  - name: "pages"
    label: "Pages"
    files:
      - file: "source/about/index.md"
        name: "about"
        label: "关于"
        fields:
          - {label: "标题", name: "title", widget: "string"}
          - {label: "内容", name: "body", widget: "markdown", required: false}
          - {label: "评论", name: "comments", widget: "boolean", default: true, required: false}

# 如切换主题,请删除以下选项或自行配置,默认仅配置了fluid主题
  - name: "settings"
    label: "settings"
    files:      
      - file: "source/_data/fluid_config.yml"
        name: "fluid"
        label: "fluid主题配置"
        editor:
          preview: false      # 是否开启编辑预览
        fields:
          - label: "导航栏"
            name: "navbar"
            widget: "object"
            collapsed: true   # 是否折叠显示
            fields:
              - {label: "博客名", name: "blog_title", widget: "string", required: false}
              - {label: "毛玻璃特效", name: "ground_glass", widget: "boolean", default: true, required: false}
          - label: "首页"
            name: "index"
            widget: "object"
            collapsed: true   # 是否折叠显示
            fields:
              - label: "顶部图"
                name: "banner_img"
                widget: "image"
                default: "/img/default.png"
              - label: "高度"
                name: "banner_img_height"
                widget: "number"
              - label: "副标题"
                name: "slogan"
                widget: "object"
                fields:
                  - {label: "修改副标题", name: "text", widget: "string", required: false}
          - label: "文章页"
            name: "post"
            widget: "object"
            collapsed: true
            fields:
              - label: "顶部图(默认)"
                name: "banner_img"
                widget: "image"
                default: "/img/default.png"
              - label: "高度"
                name: "banner_img_height"
                widget: "number" 
              - label: "文章封面图(默认)"
                name: "default_index_img"
                widget: "image"
          - label: "归档页"
            name: "archive"
            widget: "object"
            collapsed: true
            fields:
              - label: "顶部图"
                name: "banner_img"
                widget: "image"
                default: "/img/default.png"
              - label: "高度"
                name: "banner_img_height"
                widget: "number"
              - label: "副标题"
                name: "subtitle"
                widget: "string"
                required: false  
          - label: "分类页"
            name: "category"
            widget: "object"
            collapsed: true
            fields:
              - label: "顶部图"
                name: "banner_img"
                widget: "image"
                default: "/img/default.png"
              - label: "高度"
                name: "banner_img_height"
                widget: "number"
              - label: "副标题"
                name: "subtitle"
                widget: "string"
                required: false
          - label: "标签页"
            name: "tag"
            widget: "object"
            collapsed: true
            fields:
              - label: "顶部图"
                name: "banner_img"
                widget: "image"
                default: "/img/default.png"
              - label: "高度"
                name: "banner_img_height"
                widget: "number"
              - label: "副标题"
                name: "subtitle"
                widget: "string"
                required: false
          - label: "关于页"
            name: "about"
            widget: "object"
            collapsed: true
            fields:
              - label: "顶部图"
                name: "banner_img"
                widget: "image"
                default: "/img/default.png"
              - label: "高度"
                name: "banner_img_height"
                widget: "number"
              - label: "副标题"
                name: "subtitle"
                widget: "string"
                required: false
              - label: "作者头像"
                name: "avatar"
                widget: "image"
              - label: "博客名称"
                name: "name"
                widget: "string"
              - label: "网站描述"
                name: "intro"
                widget: "string"
          - label: "友链页面"
            name: "links"
            widget: "object"
            collapsed: true
            fields:
              - label: "顶部图"
                name: "banner_img"
                widget: "image"
                default: "/img/default.png"
              - label: "高度"
                name: "banner_img_height"
                widget: "number"
              - label: "副标题"
                name: "subtitle"
                widget: "string"
                required: false
              - label: "添加友链"
                name: "items"
                widget: "list"
                fields:
                  - {label: "网站名称", name: "title", widget: "string", required: true}
                  - {label: "网址描述", name: "intro", widget: "string", required: false}
                  - {label: "网站地址", name: "link", widget: "string", required: true}
                  - {label: "网站头像", name: "avatar", widget: "image", required: true}

创建source\_data\fluid_config.yml,修改并添加以下内容:

navbar:
  blog_title: 博客标题
  ground_glass:
    enable: true
index:
  banner_img: #背景图
  banner_img_height: 100
  slogan:
    text: 标语
post:
  banner_img: #文章页图
  banner_img_height: 85
  default_index_img: #文章封面图
archive:
  banner_img: #归档页图
  banner_img_height: 80
  subtitle: null
category:
  banner_img: #分类页图
  banner_img_height: 80
  subtitle: null
tag:
  banner_img: #标签页图
  banner_img_height: 80
  subtitle: null
about:
  banner_img: #关于页图
  banner_img_height: 80
  subtitle: null
  avatar: #头像
  name: 用户名
  intro: 介绍下自己
links:
  banner_img: /images/uploads/1616421416500-wallhaven-rddv31.jpg
  banner_img_height: 80
  subtitle: null
  items:
    - title: Fluid Repo
      intro: 主题 GitHub 仓库
      link: https://github.com/fluid-dev/hexo-theme-fluid
      avatar: /img/favicon.png

各个页面的背景图可以在里面修改,可以是图片的直链,也可以是本地图片的相对路径(以/img/开头)

{% note warning %}
本地图片应放置在博客目录中/source/img/里面,图片过大会严重拖慢页面加载

可以在主题配置中修改导航栏的菜单,添加或删去admin的按钮

navbar:
  menu:
    - { key: 'Admin', link: '/admin/', icon: "iconfont icon-pen" }

打开部署好的博客网站,进入CMS,注册一个账号

然后回到netlify的Site configuration > Identity 中将Registration preferences修改为Invite only关闭注册通道

至此Netlify CMS配置就算完成了,只要推送代码,等待片刻,通过你部署在 Netlify 上的域名,加/admin/即可访问你的博客后台。

🖼️图床

通过将图片存储在图床中,通过直链访问,而非直接放在博客中,来提高网站的加载速度,并使网站中的图片更易于管理

{% note warning %}
免费的图床稳定性未知,有删图的风险,且加载速度一般

如需要保证稳定和加载速度,可以选择使用各大平台的对象储存,成本也没多少:

{% note warning %}
七牛云\又拍云\多吉云都有免费的下行流量额度,但均需绑定ICP备案的域名

🛠️PWA - 渐进式网页应用[7]

本来笔者想直接使用插件hexo-offline或hexo-pwa或hexo-service-worker来实现PWA的,结果均年久失修,出现各种各样的问题,所以放弃了,选择比较原始的方法,但本人是js小白,在跟人工智障交流了俩天后,得出了还算可以的方案

渐进式

什么是渐进式,即将传统的web应用,应用现代的技术和方法使之在能够有桌面应用一般的体验,即为渐进式web应用。渐进式web应用可以同时运行在传统的浏览器上,像普通的网站一样进行浏览和操作;其同时也可以运行在现代功能完善的浏览器中,可以使其具备更多的效果和功能。比较常见的有可安装,即在支持的浏览器和操作系统上可以生成访问图标,通过图标可以可桌面应用一样访问应用;消息推送,即访问应用时服务器端可以通过应用的后台进程主动向客户端推送消息,类似于桌面应用的消息队列。

可离线

支持应用离线访问,即正常访问应用时,后台进程会自动缓存内容,下次访问时应用优先从缓存区读取数据,然后是进行web请求。因此可离线实质上充当了web代理服务器的职责,先是将正常请求代理到缓存区,再是将缓存区不足的文件进行正常的网络请求,通过此方法实现了离线的目标。根据可离线的规律,应用在一次访问缓存之后二次访问即可断网。

步骤

首先要实现PWA的可安装性,需要有一个清单文件manifest.json。manifest.json是一个简单的json文件,它描述了我们的图标在主屏幕上如何显示,以及图标点击进去的启动页是什么,自动生成manifest.json的工具:manifest.json生成工具(好像崩了),本站的JSON格式如下所示:

{
  "name": "卖柠檬雪糕的鱼的博客",
  "short_name": "Giraffish' blog",
  "theme_color": "#252d38",
  "background_color": "#252d38",
  "display": "standalone",
  "Scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "/img/icons/icon72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "/img/icons/icon512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
}
  • start_url 可以设置启动网址
  • icons 可以设置各个分辨率下页面的图标,适配不同的尺寸的路径
  • background_color 会设置背景颜色, Chrome 在网络应用启动后会立即使用此颜色,这一颜色将保留在屏幕上,直至网络应用首次呈现为止。
  • theme_color 会设置主题颜色
  • display 设置启动样式

然后在博客目录下新建文件夹scripts,再在里面新建一个pwa.js文件,并添加以下内容,从而通过Hexo注入器将manifest.json引入<head>中并注册serviceWorker,检查是否有新版本

hexo.extend.injector.register('head_begin', '<link rel="manifest" href="/manifest.json">', 'default');
hexo.extend.injector.register(
  'head_begin',
  `<script>
    if ('serviceWorker' in navigator) {
	  if (!location.pathname.startsWith('/admin')) {
        window.addEventListener('load', () => {
          navigator.serviceWorker.register('/serviceWorker.js').then(registration => {
            registration.addEventListener('updatefound', () => {
              const newWorker = registration.installing;
              newWorker.addEventListener('statechange', () => {
                if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
                  if (confirm('New content is available; please refresh.')) {
                    window.location.reload();
                  }
                }
              });
            });
          });
        });
	    } else {
        console.log('Service Worker is not registered on /admin path.');
      }
    }
  </script>`,
  'default'
);

在scripts 文件夹创建一个 serviceWorker.js ,这个脚本将在hexo g构建过程通过after_generate钩子来自动生成带有缓存版本号的 serviceWorker.js

hexo.extend.filter.register('after_generate', () => {
  const fs = require('fs');
  const path = require('path');
  const swTemplatePath = path.join(hexo.base_dir, 'source', 'serviceWorker-template.js');
  const swOutputDir = hexo.public_dir;
  const swOutputPath = path.join(swOutputDir, 'serviceWorker.js');

  // Generate a unique version number
  const version = new Date().getTime();

  // Read the template file content
  let swContent = fs.readFileSync(swTemplatePath, 'utf8');

  // Replace the placeholder with the actual version number
  swContent = swContent.replace('__CACHE_VERSION__', `v${version}`);

  // Ensure the public directory exists
  if (!fs.existsSync(swOutputDir)){
    fs.mkdirSync(swOutputDir, { recursive: true });
  }

  // Write the final serviceWorker.js file
  fs.writeFileSync(swOutputPath, swContent);
});

在source目录下新建serviceWorker-template.js,这是serviceWorker.js的模板文件

const cacheVersion = '__CACHE_VERSION__'; // This will be replaced by the build script
const cacheName = `blog-cache-${cacheVersion}`;

// Initial cache files
const initialCacheFiles = [
  "/",
  "/index.html",
  "/manifest.json"
];

/**
 * Install Service Worker
 */
self.addEventListener("install", event => {
  event.waitUntil(
    caches.open(cacheName).then(cache => {
      return cache.addAll(initialCacheFiles);
    })
  );
  self.skipWaiting(); // Immediately take control
});

/**
 * Activate Service Worker
 */
self.addEventListener("activate", event => {
  event.waitUntil(
    caches.keys().then(keys => {
      return Promise.all(
        keys.map(key => {
          if (key !== cacheName) {
            return caches.delete(key);
          }
        })
      );
    }).then(() => {
      self.clients.claim(); // Immediately take control
    })
  );
});

/**
 * Fetch request and cache first strategy
 */
self.addEventListener("fetch", event => {
  const url = new URL(event.request.url);

  // Ignore requests to /admin/ and /.netlify/
  if (url.pathname.startsWith('/admin/') || url.pathname.startsWith('/.netlify/')) {
    return;
  }

  event.respondWith(
    caches.match(event.request).then(cachedResponse => {
      if (cachedResponse) {
        return cachedResponse;
      }
      return fetch(event.request).then(networkResponse => {
        if (networkResponse && networkResponse.status === 200 && networkResponse.type === 'basic') {
          const responseToCache = networkResponse.clone();
          caches.open(cacheName).then(cache => {
            cache.put(event.request, responseToCache);
          });
        }
        return networkResponse;
      });
    })
  );
});

通过这种方式,使用缓存名称来确定缓存是否是最新的。如果缓存名称发生变化,则说明有新的版本, Service Worker会自动删除旧的缓存版本并激活新的缓存。这样可以确保用户每次刷新页面时都能获取到最新的内容。如果没有新版,就优先使用缓存的数据,以减少多余的网络请求

在https连接下就能支持PWA啦,可以离线访问访问过的网站啦


  1. Fluid 页脚增加网站运行时长_ ↩︎

  2. 网页添加 Live2D 看板娘 ↩︎

  3. PR: Migrate to pixi-live2d-display ↩︎

  4. live2d-widget 添加 moc3 模型支持 ↩︎

  5. pixi-live2d-display ↩︎

  6. Hexo Netlify CMS 在线编辑博客 ↩︎

  7. 给 Hexo 博客添加 PWA 支持_ ↩︎

posted @ 2025-03-30 17:46  Giraffish  阅读(148)  评论(0)    收藏  举报