Skip to content

Commit 3e91eba

Browse files
committed
fix($core): PascalCase layouts cannot be used with camelCase nor hyphen-delimited (close: #1391)
1 parent 0306574 commit 3e91eba

File tree

6 files changed

+86
-37
lines changed

6 files changed

+86
-37
lines changed

packages/@vuepress/core/lib/client/components/Content.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Vue from 'vue'
2-
import { isPageExists, getPageAsyncComponent } from '../util'
2+
import { getPageAsyncComponent } from '../util'
33

44
export default {
55
props: {
@@ -11,8 +11,9 @@ export default {
1111
},
1212
render (h) {
1313
const pageKey = this.pageKey || this.$parent.$page.key
14-
if (isPageExists(pageKey)) {
15-
Vue.component(pageKey, getPageAsyncComponent(pageKey))
14+
const pageComponent = getPageAsyncComponent(pageKey)
15+
if (pageComponent) {
16+
Vue.component(pageKey, pageComponent)
1617
return h(pageKey)
1718
}
1819
return h('')

packages/@vuepress/core/lib/client/components/GlobalLayout.vue

+4-7
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@
33
</template>
44

55
<script>
6-
import Vue from 'vue'
7-
86
export default {
97
computed: {
108
layout () {
119
if (this.$page.path) {
12-
if (
13-
this.$vuepress.isLayoutExists(this.$page.frontmatter.layout)
14-
|| Boolean(Vue.component(this.$page.frontmatter.layout))
15-
) {
16-
return this.$page.frontmatter.layout
10+
const layout = this.$page.frontmatter.layout
11+
if (layout && (this.$vuepress.getLayoutAsyncComponent(layout)
12+
|| this.$vuepress.getVueComponent(layout))) {
13+
return layout
1714
}
1815
return 'Layout'
1916
}

packages/@vuepress/core/lib/client/plugins/VuePress.js

+6-10
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
import Store from './Store'
22
import {
3-
isPageExists,
4-
isPageLoaded,
53
getPageAsyncComponent,
6-
isLayoutExists,
7-
isLayoutLoaded,
8-
getLayoutAsyncComponent
4+
getLayoutAsyncComponent,
5+
getAsyncComponent,
6+
getVueComponent
97
} from '../util'
108

119
class VuePress extends Store {}
1210

1311
Object.assign(VuePress.prototype, {
14-
isPageExists,
15-
isPageLoaded,
1612
getPageAsyncComponent,
17-
isLayoutExists,
18-
isLayoutLoaded,
19-
getLayoutAsyncComponent
13+
getLayoutAsyncComponent,
14+
getAsyncComponent,
15+
getVueComponent
2016
})
2117

2218
export default {

packages/@vuepress/core/lib/client/util.js

+68-15
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,89 @@ import Vue from 'vue'
22
import layoutComponents from '@internal/layout-components'
33
import pageComponents from '@internal/page-components'
44

5-
const asyncComponents = Object.assign({}, layoutComponents, pageComponents)
6-
7-
export function isPageExists (pageKey) {
8-
return Boolean(pageComponents[pageKey])
5+
/**
6+
* Create a cached version of a pure function.
7+
*/
8+
function cached (fn) {
9+
const cache = Object.create(null)
10+
// eslint-disable-next-line func-names
11+
return function cachedFn (str) {
12+
const hit = cache[str]
13+
// eslint-disable-next-line no-return-assign
14+
return hit || (cache[str] = fn(str))
15+
}
916
}
1017

11-
export function isPageLoaded (pageKey) {
12-
return Boolean(Vue.component(pageKey))
18+
/**
19+
* Camelize a hyphen-delimited string.
20+
*/
21+
const camelizeRE = /-(\w)/g
22+
const camelize = cached(str => {
23+
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
24+
})
25+
26+
/**
27+
* Hyphenate a camelCase string.
28+
*/
29+
const hyphenateRE = /\B([A-Z])/g
30+
const hyphenate = cached(str => {
31+
return str.replace(hyphenateRE, '-$1').toLowerCase()
32+
})
33+
34+
/**
35+
* Capitalize a string.
36+
*/
37+
const capitalize = cached(str => {
38+
return str.charAt(0).toUpperCase() + str.slice(1)
39+
})
40+
41+
/**
42+
* This method was for securely getting Vue component when components
43+
* are named in different style.
44+
*
45+
* e.g. a component named `a-b` can be also getted by `AB`, It's the
46+
* same the other way round
47+
*
48+
* @param {function} getter a function of getting component by name
49+
* @param {string} name component's name
50+
* @returns {Component|AsyncComponent}
51+
*/
52+
export function getComponent (getter, name) {
53+
if (!name) return
54+
if (getter(name)) return getter(name)
55+
56+
const isKebabCase = name.includes('-')
57+
if (isKebabCase) return getter(capitalize(camelize(name)))
58+
59+
return getter(capitalize(name)) || getter(hyphenate(name))
1360
}
1461

62+
const asyncComponents = Object.assign({}, layoutComponents, pageComponents)
63+
const asyncComponentsGetter = name => asyncComponents[name]
64+
const pageComponentsGetter = layout => pageComponents[layout]
65+
const layoutComponentsGetter = layout => layoutComponents[layout]
66+
const globalComponentsGetter = name => Vue.component(name)
67+
1568
export function getPageAsyncComponent (pageKey) {
16-
return pageComponents[pageKey]
69+
return getComponent(pageComponentsGetter, pageKey)
1770
}
1871

19-
export function isLayoutExists (layout) {
20-
return Boolean(layoutComponents[layout])
72+
export function getLayoutAsyncComponent (layout) {
73+
return getComponent(layoutComponentsGetter, layout)
2174
}
2275

23-
export function isLayoutLoaded (layout) {
24-
return Boolean(Vue.component(layout))
76+
export function getAsyncComponent (name) {
77+
return getComponent(asyncComponentsGetter, name)
2578
}
2679

27-
export function getLayoutAsyncComponent (pageKey) {
28-
return layoutComponents[pageKey]
80+
export function getVueComponent (name) {
81+
return getComponent(globalComponentsGetter, name)
2982
}
3083

3184
export function ensureAsyncComponentsLoaded (...names) {
3285
return Promise.all(names.filter(v => v).map(async (name) => {
33-
if (!Vue.component(name) && asyncComponents[name]) {
34-
const comp = await asyncComponents[name]()
86+
if (!getVueComponent(name) && getAsyncComponent(name)) {
87+
const comp = await getAsyncComponent(name)()
3588
Vue.component(name, comp.default)
3689
}
3790
}))

packages/docs/docs/theme/option-api.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ export default {
111111
computed: {
112112
layout () {
113113
if (this.$page.path) {
114-
if (this.$vuepress.isLayoutExists(this.$frontmatter.layout)) {
114+
if (this.$frontmatter.layout) {
115+
// You can also check whether layout exists first as the default global layout does.
115116
return this.$frontmatter.layout
116117
}
117118
return 'Layout'

packages/docs/docs/zh/theme/option-api.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ export default {
111111
computed: {
112112
layout () {
113113
if (this.$page.path) {
114-
if (this.$vuepress.isLayoutExists(this.$frontmatter.layout)) {
114+
if (this.$frontmatter.layout) {
115+
// 你也可以像默认的 globalLayout 一样首先检测 layout 是否存在
115116
return this.$frontmatter.layout
116117
}
117118
return 'Layout'

0 commit comments

Comments
 (0)