VuePress TOPページに記事一覧データを取得して表示

vuePress 公開日: 2022-02-20 更新: 2022-04-25
サムネ画像

VuePress のライフサイクル "onInitialized" フックで、各記事データを取得

doc- vuePress Lifecycle Hooksopen in new window

記事一覧を取得open in new window

doc- VuePress architectureopen in new window

onInitialized フックは VuePressアプリが初期化された時点で1度だけ実行される

.vuepress/config.ts に onInitialized フックで記事データの配列を取得する処理を追加

// docs/.vuepress/config.ts
import { defineUserConfig, viteBundler } from 'vuepress'

export default defineUserConfig({
  // site config メタデータ
  // ...

  // onInitialized フック VuePressアプリが初期化された時点で呼び出される https://vuepress.github.io/reference/plugin-api.html#lifecycle-hooks
  onInitialized: (app) => {
    console.log("🚀 ~ file: config.ts ~ liclene 31 ~ onInitialized ライフサイクルフック");
    console.log("🚀 ~ file: config.ts ~ line 34 ~ app.pages", app.pages); // 各ページのオブジェクトを格納した配列











app.pages で、↓のような各記事データObjの配列が取得できることが分かる

app.pages 各記事データObjの配列
[
  {
    data: {
      key: 'v-bab32a4e',
      path: '/articles/second-post.html',
      title: '2つめの記事です',
      lang: 'ja',
      frontmatter: {
        title: '2つめの記事です',
        description: 'VuePress ブログ',
        date: '2022-1-10',
        image: 'lake.jpg'
      },
      excerpt: '',
      headers: [ [Object], [Object] ],
      git: {},
      filePathRelative: 'articles/second-post.md'
    },
    key: 'v-bab32a4e',
    path: '/articles/second-post.html',
    title: '2つめの記事です',
    lang: 'ja',
    frontmatter: {
      title: '2つめの記事です',
      description: 'VuePress ブログ',
      date: '2022-1-10',
      image: 'lake.jpg'
    },
    excerpt: '',
    headers: [
      { level: 3, title: 'これから', slug: 'これから', children: [] },
      { level: 3, title: 'さいごに', slug: 'さいごに', children: [] }
    ],
    content: '\n' +
      '![lake-cover](/images/lake.jpg)\n' +
      '\n' +
      '# 2つめの記事です\n' +
      '\n' +
      '- [TOP](../README.md)\n' +
      '- [first-post](./first-post.md)\n' +
      // ...
      '\n',
    contentRendered: '<p><img src="/images/lake.jpg" alt="lake-cover"></p>\n' +
      '<h1 id="_2つめの記事です" tabindex="-1"><a class="header-anchor" href="#_2つめの記事です" aria-hidden="true">#</a> 2つめの記事です</h1>\n' +
      '<ul>\n' +
      // ...
      '<h3 id="さいごに" tabindex="-1"><a class="header-anchor" href="#さいごに" aria-hidden="true">#</a> さいごに</h3>\n',
    date: '2022-01-10',
    deps: [],
    hoistedTags: [],
    links: [
      {
        raw: '../README.md',
        relative: 'README.md',
        absolute: '/README.md'
      },
      {
        raw: './first-post.md',
        relative: 'articles/first-post.md',
        absolute: '/articles/first-post.md'
      }
    ],
    pathInferred: '/articles/second-post.html',
    pathLocale: '/',
    permalink: null,
    routeMeta: { title: '2つめの記事です' },
    slug: 'second-post',
    filePath: '/Volumes/External_SSD/Users/mbp/Desktop/vuePress/vuepress-starter/docs/articles/second-post.md',
    filePathRelative: 'articles/second-post.md',
    componentFilePath: '/Volumes/External_SSD/Users/mbp/Desktop/vuePress/vuepress-starter/docs/.vuepress/.temp/pages/articles/second-post.html.vue',
    componentFilePathRelative: 'pages/articles/second-post.html.vue',
    componentFileChunkName: 'v-bab32a4e',
    dataFilePath: '/Volumes/External_SSD/Users/mbp/Desktop/vuePress/vuepress-starter/docs/.vuepress/.temp/pages/articles/second-post.html.js',
    dataFilePathRelative: 'pages/articles/second-post.html.js',
    dataFileChunkName: 'v-bab32a4e',
    htmlFilePath: '/Volumes/External_SSD/Users/mbp/Desktop/vuePress/vuepress-starter/docs/.vuepress/dist/articles/second-post.html',
    htmlFilePathRelative: 'articles/second-post.html'
  },
  {...},
  ...
]

articles ディレクトリ以下の記事データオブジェクトの配列を生成

// docs/.vuepress/config.ts
// https://v2.vuepress.vuejs.org/guide/configuration.html
// ...

export default defineUserConfig({
  // ...

  // onInitialized フック VuePressアプリが初期化された時点で呼び出される https://vuepress.github.io/reference/plugin-api.html#lifecycle-hooks
  onInitialized: (app) => {
    // === 型エイリアス (type alias) https://typescriptbook.jp/reference/values-types-variables/type-alias ===

    type Frontmatter = {
      title?: string;
      description?: string;
      date?: string | Date;
      lastUpdatedAt?: string | Date;
      image?: string;
      tags?: string[];
    };

    type Link = {
      raw?: string;
      relative?: string;
      absolute?: string;
    };

    // 配列Obj app.pages の各オブジェクト オリジナルの型
    type AppPageData = {
      data: {
        key: string;
        path: string;
        title: string;
        lang?: string;
        frontmatter?: Frontmatter;
        excerpt?: string;
        headers?: any[];
        git?: any;
        filePathRelative?: string;
      },
      key: string;
      path: string;
      title: string;
      lang: string;
      frontmatter?: Frontmatter;
      excerpt: string;
      headers: object[];
      content: string;
      contentRendered: string;
      date: string | Date;
      deps: any[];
      hoistedTags: any[];
      links: Link[];
      pathInferred: string;
      pathLocale: string;
      permalink: string;
      routeMeta: object;
      slug: string;
      filePath: string;
      filePathRelative: string;
      componentFilePath: string;
      componentFilePathRelative: string;
      componentFileChunkName: string;
      dataFilePath: string;
      dataFilePathRelative: string;
      dataFileChunkName: string;
      htmlFilePath: string;
      htmlFilePathRelative: string;
    };

    // 必要なプロパティだけのオブジェクトの型エイリアス
    type PageData = {
      key: string;
      path: string;
      category: string;
      frontmatter: {
        title: string;
        description?: string;
        lastUpdatedAt?: Date;
        image?: string;
      };
      links: Link[];
      slug: string;
      // content?: string;
    };

    // ================================================================================================

    // path が, '/articles/' ~ から始まる & 末尾が .html の要素(ページ オブジェクト) だけに絞り込み 404ページや、README.md(カテゴリーページ)の記事データオブジェクトを除外した配列を生成
    const articles: AppPageData[] = app.pages.filter(obj => obj.path.startsWith('/articles/') && obj.path.endsWith('.html'));
    console.log("🚀 ~ file: config.ts ~ line 54 ~ articles.length", articles.length);
  },

)};












 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
コンソール結果 (各記事のパス & 配列の要素数)
🚀 ~ file: config.ts ~ liclene 31 ~ onInitialized ライフサイクルフック
🚀 ~ file: config.ts ~ line 145 ~ article.path /articles/first-post.html
🚀 ~ file: config.ts ~ line 145 ~ article.path /articles/second-post.html
🚀 ~ file: config.ts ~ line 145 ~ article.path /articles/vuePress/setting-get-all-articles.html
🚀 ~ file: config.ts ~ line 145 ~ article.path /articles/vuePress/setting-up.html
🚀 ~ file: config.ts ~ line 145 ~ article.path /articles/vuePress/theme-customize.html
🚀 ~ file: config.ts ~ line 145 ~ articles.length 5

型エイリアス (type alias)を別ファイルで定義してインポート(リファクタリング)

// docs/.vuepress/type.ts

// ======
// 型エイリアス (type alias) https://typescriptbook.jp/reference/values-types-variables/type-alias
// ======
export type Frontmatter = {
  title?: string;
  description?: string;
  date?: string | Date;
  lastUpdatedAt?: string | Date;
  image?: string;
  tags?: string[];
};

export type Link = {
  raw?: string;
  relative?: string;
  absolute?: string;
};

// 配列Obj app.pages の各オブジェクト オリジナルの型
export type AppPageData = {
  data: {
    key: string;
    path: string;
    title: string;
    lang?: string;
    frontmatter?: Frontmatter;
    excerpt?: string;
    headers?: any[];
    git?: any;
    filePathRelative?: string;
  },
  key: string;
  path: string;
  title: string;
  lang: string;
  frontmatter?: Frontmatter;
  excerpt: string;
  headers: object[];
  content: string;
  contentRendered: string;
  date: string | Date;
  deps: any[];
  hoistedTags: any[];
  links: Link[];
  pathInferred: string;
  pathLocale: string;
  permalink: string;
  routeMeta: object;
  slug: string;
  filePath: string;
  filePathRelative: string;
  componentFilePath: string;
  componentFilePathRelative: string;
  componentFileChunkName: string;
  dataFilePath: string;
  dataFilePathRelative: string;
  dataFileChunkName: string;
  htmlFilePath: string;
  htmlFilePathRelative: string;
};

// 必要なプロパティだけのオブジェクトの型エイリアス
export type PageData = {
  key: string;
  path: string;
  // frontmatter: Frontmatter;
  frontmatter: {
    title?: string;
    description?: string;
    lastUpdatedAt?: Date;
    image?: string;
  };
  links: Link[];
  slug: string;
  content?: string;
};

config.ts で、ファイル type.ts から AppPageData, PageData の型エイリアス をインポート

// docs/.vuepress/config.ts
// ...
import { path } from '@vuepress/utils'
import { AppPageData, PageData, ArticleData } from './type' // 型エイリアス (type alias)
import WindiCSS from 'vite-plugin-windicss'

// ... 以下、変更なし



 



関数を別ファイルで定義して読み込む

必要なプロパティのみ抽出したオブジェクトを生成してリターンする関数 & 各記事データObjの frontmatter.lastUpdatedAt を 文字列(exp '2022-01-01') に変更した配列を生成してリターンする関数を別ファイルで定義

// docs/.vuepress/func/methods.ts

import { AppPageData, PageData, ArticleData } from '../type' // 型エイリアス (type alias)

// 必要なプロパティのみ抽出したオブジェクトを生成してリターンする map関数
export const createPageObj = (pageObj: AppPageData) :PageData => {
  // 最終更新日 lastUpdateDate
  let lastUpdateDate = pageObj.frontmatter.date;
  // frontmatter.date が 文字列 or null だった場合、エラーを投げて処理を終了
  if (typeof(lastUpdateDate) === 'string') {
    throw new Error(`👉👉👉 ${pageObj.path} の frontmatter.date が ${lastUpdateDate} (文字列)になっています❗ 修正してください🔺🔺🔺`)
  }
  else if (!lastUpdateDate) {
    throw new Error(`👉👉👉 ${pageObj.path} の frontmatter.date が ${lastUpdateDate} になっています❗ 修正してください🔺🔺🔺`)
  };

  // frontmatter.lastUpdatedAt が存在した場合
  if (pageObj.frontmatter.lastUpdatedAt) {
    if (typeof(pageObj.frontmatter.lastUpdatedAt) === 'string') {
      throw new Error(`👉👉👉 ${pageObj.path} の frontmatter.lastUpdatedAt が ${pageObj.frontmatter.lastUpdatedAt} (文字列)になっています❗ 修正してください🔺🔺🔺`)
    }
    else if (typeof(pageObj.frontmatter.lastUpdatedAt) !== 'string') {
      console.log("🚀 ~ file: config.ts ~ line 88 ~ ", `${pageObj.path} の lastUpdateDate を ${lastUpdateDate} -> ${pageObj.frontmatter.lastUpdatedAt} に上書き`);
      lastUpdateDate = pageObj.frontmatter.lastUpdatedAt;
    }
  };

  // 👉 カテゴリー
  let category = '';
  if (pageObj.path.split('/articles/')[1].includes('/')) {
    category = pageObj.path.split('/articles/')[1].split('/')[0] // "vuePress"
  }
  else category = null
  // ///////////////////

  return {
    'key': pageObj.key,
    'path': pageObj.path, // "/articles/vuePress/setting-up.html"
    'category': category,
    'frontmatter': {
      title: pageObj.title,
      description: pageObj.frontmatter.description,
      lastUpdatedAt: lastUpdateDate, // Dateオブジェクト
      image: pageObj.frontmatter.image,
    },
    'links': pageObj.links,
    'slug': pageObj.slug,
    // 'content': pageObj.content
  }
}; // createPageObj() END ===


// 各記事データObjの frontmatter.lastUpdatedAt を 文字列(exp '2022-01-01') に変更した配列を生成してリターン
export const createFixedExtractArticles = (extractArticles: PageData[]) : ArticleData[] => {
  const FixedExtractArticles: ArticleData[] = extractArticles.map(obj => {
    const lastUpdatedAtVal = obj.frontmatter.lastUpdatedAt
    const lastUpdatedAt: string = `${lastUpdatedAtVal.getFullYear()}-${lastUpdatedAtVal.getMonth() + 1}-${lastUpdatedAtVal.getDate()}`
    // console.log("🚀 ~ file: config.ts ~ line 168 ~ lastUpdatedAt", lastUpdatedAt);
    return {
      'key': obj.key,
      'path': obj.path, // "/articles/vuePress/setting-up.html"
      'category': obj.category,
      'frontmatter': {
        title: obj.frontmatter.title,
        description: obj.frontmatter.description,
        lastUpdatedAt: lastUpdatedAt, // 文字列
        image: obj.frontmatter.image,
      },
      'links': obj.links,
      'slug': obj.slug,
    }
  });
  return FixedExtractArticles
}

各記事データから必要なプロパティだけを抽出したオブジェクトの配列を生成(更新日順にソート)

// docs/.vuepress/config.ts
// ...
// メソッド 別ファイル func/methods で定義した関数をインポート
import { createPageObj, createFixedExtractArticles } from './func/methods'

export default defineUserConfig({
  // ...

  // onInitialized フック VuePressアプリが初期化された時点で呼び出される https://vuepress.github.io/reference/plugin-api.html#lifecycle-hooks
  onInitialized: (app) => {
    // ...

    // onInitialized フック VuePressアプリが初期化された時点で呼び出される https://vuepress.github.io/reference/plugin-api.html#lifecycle-hooks
  onInitialized: (app) => {
    console.log("🚀 ~ file: config.ts ~ liclene 31 ~ onInitialized ライフサイクルフック");
    console.log("🚀 ~ file: config.ts ~ line 52 ~ app.pages", app.pages.length);

    // path が, '/articles/' ~ から始まる & 末尾が .html の要素(ページ オブジェクト) だけに絞り込み 404ページや、README.md(カテゴリーページ)の記事データオブジェクトを除外した配列を生成
    const articles: AppPageData[] = app.pages.filter(obj => obj.path.startsWith('/articles/') && obj.path.endsWith('.html'));
    console.log("🚀 ~ file: config.ts ~ line 54 ~ articles.length", articles.length);

    // 各記事データObjから、必要なプロパティのみ抽出したオブジェクトの配列を生成
    let extractArticles: PageData[] = articles.map( pageObj => createPageObj(pageObj));

    // 日付 PageData.frontmatter.lastUpdatedAt の新しい順にソート (破壊的メソッド)
    extractArticles.sort((a, b) => {
      if( a.frontmatter.lastUpdatedAt > b.frontmatter.lastUpdatedAt ) return -1;
      if( a.frontmatter.lastUpdatedAt < b.frontmatter.lastUpdatedAt ) return 1;
      return 0;
    });

    // 各記事データObjの frontmatter.lastUpdatedAt を 文字列(exp '2022-01-01') に変更した配列を生成
    const extractArticlesData: ArticleData[] = createFixedExtractArticles(extractArticles);
    console.log("🚀 ~ file: config.ts ~ line 74 ~ extractArticlesData", extractArticlesData);
  },

});

















 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

temp フォルダに 一時的ファイル "articles.js" として書き出し

Node API の App Methods "writeTemp"メソッドを用いて docs/.vuepress/.cache/.temp フォルダに 一時的ファイル "articles.js" として書き出し

doc- Node API writetempopen in new window

// docs/.vuepress/config.ts
// ...
export default defineUserConfig({
  // ...
  onInitialized: (app) => {
    // ...

    // 各記事データObjの frontmatter.lastUpdatedAt を 文字列(exp '2022-01-01') に変更した配列を生成
    const extractArticlesData: ArticleData[] = createFixedExtractArticles(extractArticles);

    // 更新日の新しい順で記事10件の配列Objを docs/.vuepress/.cache/.temp フォルダに 一時的ファイル "articles.js" として書き出し https://v2.vuepress.vuejs.org/reference/node-api.html#writetemp
    let maxPosts = 10;
    const jsFileContent = `export const articles = ${JSON.stringify(extractArticlesData.slice(0, maxPosts))}`
    // app.writeTemp() で 一時的ファイル "articles.js" を書き出し
    app.writeTemp('articles.js', jsFileContent)
    console.log("🚀 ~ file: config.ts ~ line 76 ~ 各記事データObjを 一時的ファイル 'articles.js' として書き出し");
  },

)};















 
 
 
 

これでvuePressをリビルドした時などの初期化時に毎回、articles/ ディレクトリ配下の README.md(カテゴリーページ)を除く記事データオブジェクトの配列が docs/.vuepress/.temp/articles.js に書き出される

TOPページで記事一覧を表示

<!-- docs/README.md -->

### 記事一覧(日付の新しい順)

<!-- 各記事へのリンク付き タイトルをリストで表示 -->
<ul>
  <li v-for="pageData in articles">
    <RouterLink :to="pageData.path">{{ pageData.frontmatter.title }}</RouterLink>
    <span>{{ pageData.frontmatter.lastUpdatedAt }}</span>
  </li>
</ul>


<script setup lang="ts">
// 一時的ファイル docs/.vuepress/.temp/articles.js から記事データObjの配列をインポート
import { articles } from '@temp/articles'
</script>

My Custom Footer

Join 31,000+ other and never miss out

About

Company

Blog

Tec

Contact

example@email.com

© Brand 2020 - All rights reserved