个人博客搭建

烈酒焚心 提交于 2020-12-13 12:48:55

个人博客搭建

经过 1 个月的咕咕咕,总算把博客初步搭建好了,按照惯例先丢个链接大家有兴趣可以来逛逛: blog.mytyiluo.cn

博客首页

主要特点如下:

  1. Modernized - 基于 Gatsbyjs,React,Typescript 构建;
  2. Opinionated - 以约束优先,减少博客中的必需参数;
  3. Git-based - 基于 GitHub 以及 Netlify 的自动化构建/部署;

为什么不使用现有的博客框架?

之前其实一直用的是 GitBook,但说实话,GitBook 还是更加适合书籍/教程等的写作,对随笔来说还是感觉少了点功能。而且官方也表示之后不再维护命令行程序了,因此决定慢慢的从这平台迁移出来。

在这过程中,有考虑过使用 Hexo 作为博客引擎,想自己写一套主题,但无奈不习惯它的模板语法,最终还是选择了放弃。

然后,考虑到开发的难度和灵活性,决定使用基于 React 的静态网页引擎,也就是 GatsbyJS

Gatsby工作流

其中,最吸引我的便是它对多数据源的内建支持,并以 GrapQL 的形式统一提供数据,非常符合我对后期拓展的预期。此外,丰富的社区插件倒算是额外的惊喜,特别是支持 TypeScript 和 Less 对开发带来了极大的方便,支持响应式图片和 PWA 也省去了我大量的精力。

我希望的博客体验

  1. 在本地用 VS Code 来编写 Markdown,并将博客内容托管在 GitHub 上;

  2. 博客应该支持丰富的元信息,但大多数参数应该根据约定生成默认值;

    例如,博客标题一般会和文件名相同;
    同一文件夹下的博客应该为同一个主题;

  3. 博客对各类代码应该有良好的展示,且支持插入多媒体内容,例如:Gist,B 站视频等;

搭建过程

废话了那么多,还是得讲些技术性的话题。

参考资料

关于 Gatsby 的参考资料其实并不需要太大,官方文档再加官方教程已经足够了。若涉及到插件的话,相应的文档和源码也都可以在 GitHub 找到。

GraphQL 数据的 TypeScript 支持

默认 GraphQL 执行的结果类型,不出意外都是 any 。但既然在使用 Ts 了,我还是希望它是强类型的变量。可问题是根据不同的查询语句返回的类型也都不一样,所以单独为每一个查询定义类型工程量太大(懒)。

解决方案就是采用现成的代码生成器:

yarn add --dev @graphql-codegen/cli @graphql-codegen/typescript

在根目录下添加配置文件 codegen.yml :

# 这里端口为 gatsby develop 的端口
schema: http://localhost:8000/___graphql
generates:
  src/types.ts:
    plugins:
      - typescript
    # 默认类型为 T | null | undefined
    # 这里为了偷懒就直接改为了 T
    config:
      avoidOptionals: true
      maybeValue: T

然后运行:

graphql-codegen --config codegen.yml

即可,我们可以在 src 文件夹中找到定义文件 types.ts 。

CSS Module 的 TypeScript 支持

CSS Module 是 Gatsby 所支持的一种样式表导入方式,其优点是在独立的 css/less 文件中编写,可以获得完美的 IDE 支持,且不同模块之间的样式不会相互干扰。

但当其导入 ts 文件中后,会被提示无法解析类型

我采用的解决方案相对暴力,在 src 文件夹下建立一个 global.d.ts 文件:

// 若你使用的不是less的话,改成相应的后缀就行
declare module "*.less" {
  const content: { [className: string]: string };
  export = content;
}

如果 IDE 还不能识别其类型,可以在根目录的 tsconfig.json 中添加:

{
  // ...
  "typeAcquisition": {
    "include": ["./src/global.d.ts"]
  }
}

若还不行可以尝试重启下 IDE。

CSS Module 生成的样式名过长

默认的情况下,CSS Module 生成的样式名的格式为:

[path][name]__[local]--[hash:base64:5]

显然在多级目录镶嵌之后,其长度会是非常吓人的,而且会影响传输效率(即使用 Gzip)。

总之,我希望这样式名在生产环境下短一些,解决方案如下:

在 gatsby-config.js 中配置 gatsby-plugin-less 选项:

{
  resolve: `gatsby-plugin-less`,
  options: {
    cssLoaderOptions: {
      minifyClassNames: true,
      localIdentName:
        process.env.NODE_ENV === "development"
          ? "[path][name]__[local]--[hash:base64:5]"
          : "[hash:base64:5]",
    },
  },
}

依赖项 sharp 安装太慢

sharp 是 gatsby-plugin-sharp 的依赖项,提供图像编辑处理的能力。
通过分析其安装过程和文档,我们可以将问题定位到 libvips 上,sharp 在 build 阶段会从 github 下载 libvips 的预编译版本(大概 14MB),但由于国内网速的原因导致其加载时间过长甚至加载失败。

那解决方案就是通过任意手段下载对应版本的 libvips 将其复制到对应的缓存目录下,一般为:

C:\Users\[你的用户名]\AppData\Roaming\npm-cache\_libvips

添加百度统计

考虑到 GA 在国内可能有些问题,这里还是选用了百度统计作为统计分析平台。同时,百度统计平台还可以主动将站点信息提交到百度搜索引擎,提高站点收录的效率。

在 Gatsby 的社区插件中已经有实现了添加百度统计的插件,但我看了下它的源码感觉挺简单的,且为了方便之后再更换其他统计平台,这里打算是自己实现一下。

首先,我们需要注册一个百度统计的账号,然后在管理界面中获取对应的代码:

获取百度统计代码

根据文档说明,我们需要将这段代码添加到每个页面的<head>中。

这里,就需要用到 gatsby-ssr.js 中的接口 onRenderBody ,具体信息可以查看文档
代码如下:

// 因为在下方用到了JSX,所以需要导入React
var React = require("react");

exports.onRenderBody = ({ setHeadComponents }) => {
  if (process.env.NODE_ENV === `production`) {
    setHeadComponents([
      // 这里的形式是为了异步加载
      <script
        key="baidu-analytics-script"
        dangerouslySetInnerHTML={{
          __html: `
            var _hmt = _hmt || [];
            (function() {
              var hm = document.createElement("script");
              hm.src = "https://hm.baidu.com/hm.js?ff306397dbce7dab4357bd037fa286ca";
              var s = document.getElementsByTagName("script")[0];
              s.parentNode.insertBefore(hm, s);
            })();
          `
        }}
      />
    ]);
  }
};

统计博客标签

在我设置的字段中,每篇博客都会包含一个 tags,类型为 string[]。在首页中,我希望根据标签计数的降序显示计数大于 2 的标签。

其实,这个问题就相当于处理镶嵌数组,处理难度并不高,但此前往往需要写不少的代码,这里基于 lodash 的 chain 实现了一个相对“简洁”的版本:

// data 就是根据 GraphQL 得到的数据
// const data = this.props.data.statistics.nodes;
//
// Scheme 定义如下:
// statistics: allMarkdownRemark(
//   filter: { fields: { name: { ne: "README" }, posted: { ne: false } } }
// ) {
//   nodes {
//     fields {
//       topic
//       tags
//     }
//   }
// }
const tags = _
  // 包装为 chain 对象
  .chain(data)
  // 只需要 tags 字段
  .map(e => e.fields.tags)
  // 展开二维数组
  .flatten()
  // 对标签计数
  // 这里返回的为对象 { tag: count }
  .countBy()
  // 筛选计数大于 2 的标签
  .pickBy((value, key) => value > 2)
  // 将对象转为数组
  .map((value, key) => ({ tag: key, count: value }))
  // 根据计数降序排列
  .orderBy("count", "desc")
  // 只需要 tag 字段
  .map(e => e.tag)
  // 解除 chain 包装
  .value();

小结

至此,主要记录的是在开发阶段遇到的一些坑和小技巧。下一篇,我想简要叙述一下设计这个博客的思路,如果我还记得这个坑的话。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!