# 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',
]
}],
],
}
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'),
}
};
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 });
}
2
3
4
5
6
7
8
9
10
11
README.md
はサンプルなので適当に.
echo "# Plugin REAME" >> ./docs/.vuepress/myplugin/REAMDE.md
ここまで準備できれば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;
}
}
},
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')
},]
},
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;
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
expandPageData
およびadditionalPages
が一通り終わってから呼び出される.
現時点で出そろったページに対して,frontmatter.category
が登録されていればパスを保存している.
保存したcategories
はcontext
に登録することでclientDynamicModules
に渡す.
# clientDynamicModules
async clientDynamicModules() {
const PREFIX = 'myplugin';
const { categories } = context;
return [{
name: `${PREFIX}/sample.js`,
content: `export default ${JSON.stringify(categories)}`,
}];
},
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 });
}
2
3
4
5
6
7
8
9
1行目でclientDynamicModules
が出力したデータを読み込みVueに登録している.
これによりVueComponent
側で $categories
を利用して値を取得できる(値はconsole出力しているのでブラウザの開発者ツールで確認可能).
ready
でページ全体をスキャンしてメタデータを生成し,clientDynamicModules
でメタデータを出力し,enhanceAppFile
でメタデータをVueComponent
で利用できる形で読み込み,VueComponent
でメタデータを使ったDOMを生成する
というのが大体の流れのよう.