npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vuepress-plugin-cpt

v1.0.2

Published

Custom Post Type & Taxonomy & Pagination Plugin for Vuepress

Downloads

7

Readme

Demos and examples

Basic(Default Options) - Demo Netlify Status | Code

i18n - Demo Netlify Status | Code

Getting started

Install

# via npm
$ npm install -D vuepress-plugin-cpt

# via yarn
$ yarn add -D vuepress-plugin-cpt

Usage

// .vuepress/config.js or .vuepress/theme/index.js

const registerPostTypes = {
  news: {
    label: 'News',
    postsDir: '_posts-news',
    taxonomys: {
      category: true,
      tag: true,
      author: true
    }
  }
};

module.exports = {
  plugins: [
    [
      'vuepress-plugin-cpt', registerPostTypes
    ],
    ...
  ]
}

2. File structure

demo
├─ index.md
└─ _posts-news
   └─ 1.md
   └─ hello-world.md

3. Article Front Matter

---
title: Hello World

// By default it is used for posts sorting.
date: 2018-01-01

// Add taxonomy terms you set up
category: Plugins
author: tmiame
tag:
  - Vue
  - VuePress

// Single
category: Plugins

// Multiple
category:
- Plugins
- Projects
---

4. Create Theme layout file required by default

demo
├─ index.md
└─ .vuepress
   └─ theme
      └─ layouts
         ├─ Posts.vue
         ├─ Post.vue
         ├─ Taxonomy.vue
         └─ Term.vue

5. Write layout template

Posts.vue
<div v-if="$cpt.posts">
  <router-link
    v-for="item of $cpt.posts"
    v-if="item && item.path"
    v-bind:to="item.path">
    <div
      v-if="item.taxonomys"
      v-for="taxonomy of item.taxonomys"
      v-on:click:stop.prevent>
      <span>{{ taxonomy.indexPage.label }}:</span>
      <router-link
        v-for="category of taxonomy.list"
        v-bind:to="category.path"
        v-text="category.name" />
    </div>
    <h2 v-if="item.title" v-text="item.title" />
    <div v-if="item.frontmatter.date" v-text="item.frontmatter.date" />
    <div v-if="item.frontmatter.description" v-text="item.frontmatter.description" />
  </router-link>
</div>

<div class="pagination" v-if="$cpt.postsPagination && $cpt.postsPagination.length">
  <router-link v-if="$cpt.postsPagination.hasPrev" v-bind:to="$cpt.postsPagination.prevLink">
    Prev
  </router-link>
  <router-link
    v-for="(item, index) of $cpt.postsPagination._paginationPages"
    v-bind:to="item.permalink"
    v-bind:disable="$cpt.postsPagination._currentPage.permalink === item.permalink">
    {{ index + 1 }}
  </router-link>
  <router-link v-if="$cpt.postsPagination.hasNext" v-bind:to="$cpt.postsPagination.nextLink">
    Next
  </router-link>
</div>
Post.vue
<template>
  <div class="breadcrumb">
    <router-link v-if="$cpt.indexPage" v-bind:to="$cpt.indexPage.path">
      < {{ $cpt.indexPage.label }}
    </router-link>
  </div>

  <h1>{{ $page.title }}</h1>

  <header v-if="$cpt.post">
    <div v-for="taxonomy of $cpt.post">
      <span>
        {{ taxonomy.indexPage.label }}:
      </span>
      <router-link
        v-for="category of taxonomy.list"
        v-bind:to="category.path"
        v-text="category.name"
      />
    </div>
    <h1 v-text="$page.title" />
  </header>

  <div class="content">
    <slot name="top" />
    <Content v-bind:custom="false" />
    <slot name="bottom" />
  </div>
</template>
Taxonomy.vue
<template>
  <div class="breadcrumb">
    <router-link v-if="$cpt.indexPage" v-bind:to="$cpt.indexPage.path">
      < {{ $cpt.indexPage.label }}
    </router-link>
  </div>

  <h1>{{ $page.title }}</h1>

  <div v-if="$cpt.terms">
    <router-link v-for="item of $cpt.terms.list" v-bind:to="item.path">
      <div v-text="item.name" />
      <div v-text="item.posts.length" />
    </router-link>
  </div>
</template>
Term.vue
<template>
  <div class="breadcrumb">
    <router-link v-if="$cpt.indexPage" v-bind:to="$cpt.indexPage.path">
      < {{ $cpt.indexPage.label }}
    </router-link>
    <router-link v-if="$cpt.terms.indexPage" v-bind:to="$cpt.terms.indexPage.path">
      < {{ $cpt.terms.indexPage.label }}
    </router-link>
  </div>

  <h1>{{ $page.title }}</h1>

  <ul v-if="$cpt.termPosts">
    <li v-for="item of $cpt.termPosts">
      <div v-if="item.taxonomys" v-for="taxonomy of item.taxonomys">
        <span>{{ taxonomy.indexPage.label }}:</span>
        <router-link v-for="category of taxonomy.list" v-bind:to="category.path" v-text="category.name" />
      </div>
      <router-link v-bind:to="item.path" v-text="item.title" />
      <router-link v-bind:to="item.path">
        <div v-if="item.frontmatter.date" v-text="item.frontmatter.date" />
        <div v-if="item.frontmatter.description" v-text="item.frontmatter.description" />
      </router-link>
    </li>
  </ul>
</template>

Documentation

Options

// .vuepress/config.js or .vuepress/theme/index.js

const registerPostTypes = {
  // (string) (required) Post type ID. (If `slug` and `label` is not set, this is used for URL and Title)
  news: {
    // (string) (required) directory path for posts
    postsDir: '_posts-news',

    // (string) (optional) It is used for URL. (If `label` is not set, this is used for Post type name)
    slug: 'my-news',

    // (string) (optional) Post type name except for URL.
    label: 'News',

    // (string) (optional)
    baseUrl: '/',

    posts: {
      // (string) (optional) layout of article list
      layout: 'Posts',

      // (string) (optional) title of article list
      // - Title variables
      // - %cpt = post type name
      title: '%cpt',

      // (function) (optional)
      postsSorter: ((prev, next) => {
        const prevTime = new Date(prev.frontmatter.date).getTime()
        const nextTime = new Date(next.frontmatter.date).getTime()
        return prevTime - nextTime < 0 ? -1 : 1
      }),

      // (object|boolean) (optional) pagination of article list
      // pagination: false, (Disable pagination)
      pagination: {
        // (number) (optional)
        perPagePosts: 10,
        // (string) (optional)
        paginationUrl: '/page/',
        // (string) (optional)
        // - Title variables
        // - %cpt = post type name
        // - %paginationIndex = page index number
        title: '%cpt - Page %paginationIndex'
      }
    },

    post: {
      // (string) (optional) layout of article
      layout: 'Post',
      // (string) (optional) before title of article
      // - Title variables
      // - %cpt = post type name
      titleBefore: '',
      // (string) (optional) after title of article
      // - Title variables
      // - %cpt = post type name
      titleAfter: ' | %cpt',
      // (string) (optional)
      // https://vuepress.vuejs.org/guide/permalinks.html
      permalink: '/:year/:month/:day/:slug'
    },

    // (object|boolean) (optional) - Default is {}
    taxonomys: {
      // (string) (required) Taxonomy ID. (If `slug` and `label` is not set, this is used for URL and Title)
      category: {
        // (string) (optional) It is used for URL. (If `label` is not set, this is used for Taxonomy name)
        slug: 'my-category',
        // (string) (optional) Taxonomy name
        label: 'Category',
        // (object|boolean) (optional)
        // terms: false, (Disable terms page)
        terms: {
          // (string) (optional) Terms page layout
          layout: 'Taxonomy',
          // (string) (optional) Terms page title
          // - Title variables
          // - %cpt = post type name
          // - %taxonomy = taxonomy name
          title: `%taxonomy | %cpt`
        },
        term: {
          // (string) (optional) Term page layout
          layout: 'Term',
          // (string) (optional) Term page title
          // - Title variables
          // - %cpt = post type name
          // - %taxonomy = taxonomy name
          // - %term = term name
          title: `%term - %taxonomy | %cpt`,
          // (function) (optional)
          postsSorter: ((prev, next) => {
            const prevTime = new Date(prev.frontmatter.date).getTime()
            const nextTime = new Date(next.frontmatter.date).getTime()
            return prevTime - nextTime < 0 ? -1 : 1
          }),
          // (object|boolean) (optional) pagination of Term page
          // pagination: false, (Disable pagination)
          pagination: {
            // (number) (optional)
            perPagePosts: 10,
            // (string) (optional)
            paginationUrl: '/page/',
            // (string) (optional)
            // - Title variables
            // - %cpt = post type name
            // - %taxonomy = taxonomy name
            // - %term = term name
            // - %paginationIndex = page index number
            title: '%term - %taxonomy - Page %paginationIndex | %cpt'
          }
        }
      },
      ...
    }
  }
  ...
}

module.exports = {
  plugins: [
    [
      'vuepress-plugin-cpt', registerPostTypes
    ],
  ]
}

Get data

this.$cpts - All pages

this.$cpts: Object
  news:
    indexPage: // Root page info data
    pagination: // Posts pagination data
    posts: // Posts data
    taxonomys: // Taxonomys data
  blog:
    indexPage:
    pagination:
    posts:
    taxonomys:

this.$cpt - Page under the custom post type

Posts page            -  /news/
Posts Pagination page -  /news/page/2/
Post page             -  /news/hello-world/
Taxonomy Page         -  /news/tag/
Term Page             -  /news/tag/VuePress/
Term Pagination Page  -  /news/tag/VuePress/page/2/
this.$cpt: Object|Boolean
  indexPage: // Root page info data
  post:// Post data (only Post page)
  posts: // Posts data
  postsPagination: // Posts pagination data
  taxonomys: // Taxonomys data
  terms: // All terms data (only Taxonomy, Term and Term Pagination page)
  term: // Term data (only Term and Term Pagination page)
  termPosts: // Term posts data (only Term and Term Pagination page)
  termPostsPagination: // Term posts pagination data (only Term and Term Pagination page)

Internationalization(i18n)

1. Set Site Level i18n Config

https://vuepress.vuejs.org/guide/i18n.html
Then, specify the locales option in .vuepress/config.js:

// .vuepress/config.js
module.exports = {
  locales: {
    // The key is the path for the locale to be nested under.
    // As a special case, the default locale can use '/' as its path.
    '/': {
      lang: 'en-US', // this will be set as the lang attribute on <html>
      title: 'VuePress',
      description: 'Vue-powered Static Site Generator'
    },
    '/zh/': {
      lang: 'zh-CN',
      title: 'VuePress',
      description: 'Vue 驱动的静态网站生成器'
    },
    '/jp/': {
      lang: 'ja-JP',
      title: 'VuePress',
      description: 'Vue-powered Static Site Generator'
    }
  }
}

2. Set Plugin i18n Config

// .vuepress/config.js or .vuepress/theme/index.js
const registerPostTypes = {
  locales: {
    '/': {
      news: {
        label: 'News',
        postsDir: '_posts-news',
        posts: {
          layout: 'Posts',
          title: `%cpt`,
          pagination: {
            title: `%cpt - Page %paginationIndex`
          }
        },
        post: {
          layout: 'Post'
        },
        taxonomys: {
          category: {
            label: 'Category',
            terms: {
              layout: 'Taxonomy',
              title: `%taxonomy - %cpt`
            },
            term: {
              layout: 'Term',
              title: `%term - %taxonomy - %cpt`,
              pagination: {
                title: `%termの投稿 - Page %paginationIndex`
              }
            }
          }
        }
      }
    },
    '/jp/': {
      news: {
        label: 'ニュース',
        postsDir: '/jp/_posts-news',
        posts: {
          layout: 'Posts',
          title: `%cpt`,
          pagination: {
            title: `%cpt - %paginationIndexページ目`
          }
        },
        post: {
          layout: 'Post'
        },
        taxonomys: {
          category: {
            label: 'カテゴリー',
            terms: {
              layout: 'Taxonomy',
              title: `%taxonomy一覧 - %cpt`
            },
            term: {
              layout: 'Term',
              title: `%taxonomy - %termの投稿 - %cpt`,
              pagination: {
                title: `%taxonomy - %termの投稿 - %paginationIndexページ目`
              }
            }
          }
        }
      }
    }
  }
}

module.exports = {
  plugins: [
    [ 'vuepress-plugin-cpt', registerPostTypes ],
    ...
  ]
}

Tips

  1. Set URL as root page
  2. Resource prefetch settings for many pages
  3. Build performance for many pages

1. Set URL as root page

const registerPostTypes = {
  news: {
    baseUrl: '/',
    ...
  }
}

> https://examples.com/news/

const registerPostTypes = {
  news: {
    baseUrl: '../',
    ...
  }
}

> https://examples.com/

2. Resource prefetch settings for many pages

https://vuepress.vuejs.org/config/#shouldprefetch

https://ssr.vuejs.org/api/#shouldprefetch

https://ssr.vuejs.org/api/#shouldpreload

module.exports = {
  ...
  shouldPrefetch: (file, type) => {
    if (type !== 'script') {
      return true
    }
  }
}

3. Build performance for many pages

https://github.com/vuejs/vuepress/issues/19

I see where you're coming from, but performance isn't a core focus of VuePress. The design is largely limited by webpack (having 1000 pages means 1000 Vue components being compiled via webpack and minified) and looks like it runs out of memory at some point with 1000 pages.
Tested that we can handle ~300 pages in around 40s, and there are probably quite a few low hanging fruits to improve perf as the current implementation essentially didn't take build perf into consideration at all.