# Develop VuePress plugins

VuePressを改造するにあたり幾つかの選択肢が存在するの整理していく.

# VueComponent

Using Vue in Markdown (opens new window)

VueComponent (Vue.js勉強し始めたところなのでよく分かってない)で部品を作ってMarkdown内で利用できる. DOMを追加したり単純にjs実行したりできるので自由度は高い.実行はブラウザ上で行われる.

明示的にMarkdown内で呼び出す必要があり,更にサイト全体に関係する操作(例: sidebar挙動変更)はできないためピンポイントで部品を導入する用途.

TIP

Markdown内で呼び出す以外にも,レイアウトを作って(docs/.vuepress/theme/layouts/*.vue)その中で呼び出すことも可能らしい. ただしtheme/が存在する場合はデフォルトテーマ自体が読み込まれなくなるため結構大変.

vuepress eject docsでデフォルトテーマ抽出はできるので,根気がある人はそちらも視野に入れるとよさそう. ただ抽出したデフォルトテーマはvuepressのバージョンアップに伴い自動追従されないので,バージョンアップに慎重にならざるを得なくなる.

# Plugin

サイト全体に影響する操作はこちらで実装する.ちょっと分かりにくいのでサンプルを動かしてみる.

以下のように記述すると./docs/.vuepress/myplugin/index.jsが読み込まれる.




 
 
 
 
 
 
 



module.exports = {
    ...
    plugins: [
        [require('./myplugin'), {
            directories: [
                id: 'post',
                sidebar: 'auto',
                targetDir: '_post',
            ]
        }],
    ],
}
1
2
3
4
5
6
7
8
9
10
11
12

myplugin/index.jsは以下のように記述する.

$ cat ./docs/.vuepress/myplugin/index.js

const path = require('path')

module.exports = (option, context) => {
    // config.jsに記載した引数はoptionに入っている
    const {
        directories = []
    } = option;

    return {
        extendPageData($page) {
            const {
                _filePath,           // file's absolute path
                _computed,           // access the client global computed mixins at build time, e.g _computed.$localePath.
                _content,            // file's raw content string
                _strippedContent,    // file's content string without frontmatter
                key,                 // page's unique hash key
                frontmatter,         // page's frontmatter object
                regularPath,         // current page's default link (follow the file hierarchy)
                path,                // current page's real link (use regularPath when permalink does not exist)
            } = $page

            for(const directory of directories) {
                const {
                    id,
                    sidebar,
                    targetDir,
                    permalink = '/:year/:month/:day/:slug'
                } = directory;

                if (regularPath.startsWith(`/${targetDir}/`)) {
                    frontmatter.permalink = permalink;
                    frontmatter.sidebar = sidebar;
                }
            }
        },
        additionalPages() {
            return [{
                path: '/readme/',
                filePath: path.resolve(__dirname, '../../../README.md')
            },]
        },
        async ready() {
            const { pages } = context;

            const categories = {};
            for(const { path, frontmatter } of pages) {
                if (!frontmatter || Object.keys(frontmatter).length === 0) continue;

                const { category: key } = frontmatter;
                if (!categories[key]) {
                    categories[key] = []
                }
                categories[key].push(path);
            }
            context.categories = categories;
        },

        async clientDynamicModules() {
            const PREFIX = 'myplugin';
            const { categories } = context;

            return [{
                name: `${PREFIX}/sample.js`,
                content: `export default ${JSON.stringify(categories)}`,
            }];
        },
        enhanceAppFiles: path.resolve(__dirname, 'client.js'),
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

更にclient.jsは以下のように記述する.

$ cat ./docs/.vuepress/myplugin/client.js

import data from '@dynamic/myplugin/sample';

export default ({ Vue, options, router, siteData }) => {
    console.log(data);

    const computed = {};
    computed.$categories = function() { return data; }
    Vue.mixin({ computed });
} 
1
2
3
4
5
6
7
8
9
10
11

README.mdはサンプルなので適当に.

echo "# Plugin REAME" >> ./docs/.vuepress/myplugin/REAMDE.md
1

ここまで準備できればVSCode上でデバック実行するとよい.

# extendPageData

extendPageData($page) {
    const {
        _filePath,           // file's absolute path
        _computed,           // access the client global computed mixins at build time, e.g _computed.$localePath.
        _content,            // file's raw content string
        _strippedContent,    // file's content string without frontmatter
        key,                 // page's unique hash key
        frontmatter,         // page's frontmatter object
        regularPath,         // current page's default link (follow the file hierarchy)
        path,                // current page's real link (use regularPath when permalink does not exist)
    } = $page

    for(const directory of directories) {
        const {
            id,
            sidebar,
            targetDir,
            permalink = '/:year/:month/:day/:slug'
        } = directory;

        if (regularPath.startsWith(`/${targetDir}/`)) {
            frontmatter.permalink = permalink;
            frontmatter.sidebar = sidebar;
            $page.id = id;
        }
    }
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

処理としては単純に,プラグイン引数をもとにtargetDirから始まるページが存在していたらfrontmatter.(permalink, sidebar)を指定値で上書きしている. 基本は$pageに新しい変数を追加したりfrontmatterを書き換えたりするのに利用するだけらしい.

VueComponent側で$site.pages[*].idという形で付与したidを利用することができる. 同様に$site.pages[*].frontmatterの値を確認すると,元のではなく上記で上書きされた値が取得できる.

参考: https://vuepress.vuejs.org/plugin/option-api.html#extendpagedata (opens new window)

# additionalPages

additionalPages() {
    return [{
        path: '/readme/',
        filePath: path.resolve(__dirname, '../../../README.md')
    },]
},
1
2
3
4
5
6

返り値に応じてページが追加される.追加されたページは更にexpandPageDataにもかけられる.

なおadditionalPages以外にも,context.addPages()を呼ぶことでもページの追加が可能. 後者の場合はpluginのどこから読んでもOKそうな感じ.

# ready

async ready() {
    const { pages } = context;

    const categories = {};
    for(const { path, frontmatter } of pages) {
        if (!frontmatter || Object.keys(frontmatter).length === 0) continue;

        const { category: key } = frontmatter;
        if (!categories[key]) {
            categories[key] = []
        }
        categories[key].push(path);
    }
    context.categories = categories;
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

expandPageDataおよびadditionalPagesが一通り終わってから呼び出される.

現時点で出そろったページに対して,frontmatter.categoryが登録されていればパスを保存している. 保存したcategoriescontextに登録することでclientDynamicModulesに渡す.

# clientDynamicModules

async clientDynamicModules() {
    const PREFIX = 'myplugin';
    const { categories } = context;

    return [{
        name: `${PREFIX}/sample.js`,
        content: `export default ${JSON.stringify(categories)}`,
    }];
},
1
2
3
4
5
6
7
8
9

redadyで作成したカテゴリ辞書をmyplugin/sample.jsとして出力している. ここで出力したjsファイルは enhanceAppFile で利用することができる.

# enhanceAppFile

 









import data from '@dynamic/myplugin/sample';

export default ({ Vue, options, router, siteData }) => {
    console.log(data);

    const computed = {};
    computed.$categories = function() { return data; }
    Vue.mixin({ computed });
} 
1
2
3
4
5
6
7
8
9

1行目でclientDynamicModulesが出力したデータを読み込みVueに登録している. これによりVueComponent側で $categories を利用して値を取得できる(値はconsole出力しているのでブラウザの開発者ツールで確認可能).

  1. readyでページ全体をスキャンしてメタデータを生成し,
  2. clientDynamicModulesでメタデータを出力し,
  3. enhanceAppFileでメタデータをVueComponentで利用できる形で読み込み,
  4. VueComponentでメタデータを使ったDOMを生成する

というのが大体の流れのよう.

Last Updated: 2022/3/7 15:18:37