iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0

因為我原先使用的 CMS 是 Contentful,之前寫了也有 5x 篇文章,當然是可以手動一篇一篇去搬,不過可以的話還是能找到直接 Contentful 匯出再匯入 Sanity 的方法比較好。

Contentful to Sanity

Sanity 官方有個套件叫做 contentful-to-sanity,專門就是來做從 Contentful 的轉移到 Sanity 的工作,用法也不是太複雜。

因為這工具不是常常會用的,個人覺得只要使用 npx 在需要的時候及時安裝、使用就好了

npx contentful-to-sanity@latest --help
Usage: contentful-to-sanity [options] [command]

Options:
  -V, --version               output the version number
  -h, --help                  display help for command

Commands:
  export [options] <outdir>
  schema [options] <outdir>
  dataset [options] <outdir>
  batch [options] <outdir>    Runs the export, schema and dataset commands in sequence.
  help [command]              display help for command

第一件事情要做的是將 Contentful 的資料匯出

匯出的資料路徑是在我們的 Sanity 專案

.
├── next-app
└── sanity-project // <- 資料匯出到這

執行匯出指令:

npx contentful-to-sanity -s <space-id> -t <management-token> -a <access-token> ./sanity-project

匯出後會走以下幾個檔案:contentful.json, contentful.published.json, dataset.ndjson, schema.ts,而要這次用到的只有 dataset.ndjson 這個檔案:

檔案產生後再到檔案所在目錄 ( 也是 Sanity 專案目錄 )

cd sanity-project

// 執行 Sanity import 指令,並且將 ndjson 作為導入檔案
sanity dataset import ./dataset.ndjson

✅ [100%] Fetching available datasets
? Select target dataset (Use arrow keys)
❯ Create new dataset // <- 可以開一個新的 dataset 作為測試
  ──────────────
  production // <- 也可以直接匯入到 production 環境

執行後檔案跟文章內容就都匯入了。

sanity dataset import ./dataset.ndjson
✅ [100%] Fetching available datasets
? Select target dataset Create new dataset
? Name your dataset: from-contentful
╭───────────────────────────────────────────────╮
│                                               │
│ Importing to:                                 │
│ projectId: zxxxxxxg                           │
│ dataset: production.                          │
│                                               │
╰───────────────────────────────────────────────╯

✅ [100%] Reading/validating data file (144ms)
✅ [100%] Importing documents (1.85s)
✅ [100%] Importing assets (files/images) (36.84s)
✅ [100%] Setting asset references to documents (1.06s)
✅ [100%] Strengthening references (604ms)
Done! Imported 56 documents to dataset "from-contentful"

但其實在匯入的時候是有些 ContentfulSanity 格式不符合的問題。
相對於 SanityContentful 的格式是很複雜的。像是 Contentful 的 Markdown 格式會變成像這樣:

{
  "body": [
    {
      "_key": "8c383bccf605",
      "children": [
        {
          "_type": "span",
          "marks": [],
          "text": "@WebServlet",
          "_key": "8c383bccf6050"
        }
      ],
      "markDefs": [],
      "_type": "block",
      "style": "h2"
    },
    {
      "_key": "8947703c7e75",
      "children": [
        {
          "_type": "span",
          "marks": [
            "code"
          ],
          "text": "@WebServlet",
          "_key": "8947703c7e750"
        },
        {
          "_type": "span",
          "marks": [],
          "text": " 是用來定義這個 Servlet 的屬性, 被定義的 class 通常繼承 javax.servlet.http.HttpServlet 這個 class",
          "_key": "8947703c7e751"
        }
      ],
      "markDefs": [],
      "_type": "block",
      "style": "normal"
    },
    // ...
}

可以看得出來被 Contentful 做了一些格式的處裡,這是沒辦法直接 import 到 Sanity 的,必須要有個轉譯的方法才能匯入 Sanity

我這邊的解法也不怕大家笑,我是直接到 Contentful 的後台將格式從 Markdown 改成 Multiple line,這樣再輸出一次就會是字串格式了。

https://ithelp.ithome.com.tw/upload/images/20240930/201019894XMphxhz8D.png

再來是有些欄位可能 SanityContentful 的名稱沒有對齊,像是:

  • Sanity 中,文章內容的欄位叫做 content,但是 Contentful 中叫做 body,之類的欄位名稱沒對齊
  • Contentfulslug 欄位的資料多了一層 { current: "..." } 等等的資料結構問題

這些 export, import 套件並沒有現成的方法支援解決,要解決這些問題也就必須手動更改名稱或是自己另外處理掉了。

由於匯入 Sanity 的資料格式是 ndjson 格式,不是很難,簡單說就是用分行來隔開資料的陣列格式吧,所以只要解析 ndjson 之後做些資料格式的處裡跟欄位對應,做完後再輸出回去成一個新的 ndjson 檔案就完成了。

一樣不怕大家笑我,這種一次性的操作就沒再跟他注意什麼程式碼的漂亮了。
我的作法是用 node 寫一個簡單的腳本來處裡,
因為不是什麼複雜的操作,在這邊就不特別解釋了,直接腳本把原始碼放上來。

import fs from "fs";
import ndjson from "ndjson";
import moment from "moment";

// @ts-ignore - 沒有支援的 Typescript 型別
import arrayToNdjson from "array-to-ndjson";

type SanityType = {
  _type: "blogPost";
  _id?: string;
  title: string;
  slug: string;
  subtitle: string;
  heroImage: any;
  content: string;
  publishedAt: string;
  tags: string[];
};
async function getContentfulJson() {
  const arr: SanityType[] = [];
  return new Promise((resolve, reject) => {
    const promises: any[] = [];
    fs.createReadStream("dataset.ndjson", "utf8")
      .pipe(ndjson.parse())
      .on(
        "data",
        (contentfulObj: {
          _type: string;
          _id: string;
          slug: { current: string };
          title: string;
          body: any;
          description: { children: { text: string }[] }[];
          heroImage: any;
          publishDate: string;
          tags: string[];
        }) => {
          if (contentfulObj._type === "blogPost") {
            const sanityObj: SanityType = {
              _type: "blogPost",
              title: "",
              slug: "",
              subtitle: "",
              heroImage: null,
              content: "",
              publishedAt: "",
              tags: [],
            };
            sanityObj._id = contentfulObj._id;
            sanityObj.title = contentfulObj.title;
            sanityObj.slug = contentfulObj.slug.current;
            sanityObj.subtitle = contentfulObj.description[0].children[0].text;
            sanityObj.heroImage = contentfulObj.heroImage;
            sanityObj.content = contentfulObj.body;
            sanityObj.publishedAt = moment(contentfulObj.publishDate).format(
              "YYYY-MM-DD"
            );
            sanityObj.tags = contentfulObj.tags;
            arr.push(sanityObj);
          }
        }
      )
      .on("end", async () => {
        resolve(arr);
      });
  });
}

async function main() {
  const contenfulNDJson: any = await getContentfulJson();
  arrayToNdjson(contenfulNDJson).pipe(fs.createWriteStream("newDataset.ndjson"));
}

main();

最後再把 newDataset.ndjson 匯入到 Sanity 就完成了。

https://ithelp.ithome.com.tw/upload/images/20240930/20101989ALeeNFjxL3.png

花花綠綠的文章都進來了~~


上一篇
Day 14 - Sanity Markdown Plugin
下一篇
Day 16 - Next.js 字型設定
系列文
用 Sanity 跟 Nextjs 重寫個人部落格30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言