1. 程式人生 > >個人博客搭建

個人博客搭建

預編譯 包含 hub har image ocs 復制 define markdown

個人博客搭建

經過 1 個月的咕咕咕,總算把博客初步搭建好了,按照慣例先丟個鏈接大家有興趣可以來逛逛: blog.mytyiluo.cn。

技術分享圖片

主要特點如下:

  1. Modernized - 基於 Gatsbyjs,React,Typescript 構建;
  2. Opinionated - 以約束優先,減少博客中的必需參數;
  3. Git-based - 基於 GitHub 以及 Netlify 的自動化構建/部署;

為什麽不使用現有的博客框架?

之前其實一直用的是 GitBook,但說實話,GitBook 還是更加適合書籍/教程等的寫作,對隨筆來說還是感覺少了點功能。而且官方也表示之後不再維護命令行程序了,因此決定慢慢的從這平臺遷移出來。

在這過程中,有考慮過使用 Hexo 作為博客引擎,想自己寫一套主題,但無奈不習慣它的模板語法,最終還是選擇了放棄。

然後,考慮到開發的難度和靈活性,決定使用基於 React 的靜態網頁引擎,也就是 GatsbyJS。

技術分享圖片

其中,最吸引我的便是它對多數據源的內建支持,並以 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();

小結

至此,主要記錄的是在開發階段遇到的一些坑和小技巧。下一篇,我想簡要敘述一下設計這個博客的思路,如果我還記得這個坑的話

個人博客搭建