Skip to content

Commit

Permalink
fix($core): PascalCase layouts cannot be used with camelCase nor hyph…
Browse files Browse the repository at this point in the history
…en-delimited (close: #1391)
  • Loading branch information
ulivz committed Mar 9, 2019
1 parent 0306574 commit 3e91eba
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 37 deletions.
7 changes: 4 additions & 3 deletions packages/@vuepress/core/lib/client/components/Content.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Vue from 'vue'
import { isPageExists, getPageAsyncComponent } from '../util'
import { getPageAsyncComponent } from '../util'

export default {
props: {
Expand All @@ -11,8 +11,9 @@ export default {
},
render (h) {
const pageKey = this.pageKey || this.$parent.$page.key
if (isPageExists(pageKey)) {
Vue.component(pageKey, getPageAsyncComponent(pageKey))
const pageComponent = getPageAsyncComponent(pageKey)
if (pageComponent) {
Vue.component(pageKey, pageComponent)
return h(pageKey)
}
return h('')
Expand Down
11 changes: 4 additions & 7 deletions packages/@vuepress/core/lib/client/components/GlobalLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
</template>

<script>
import Vue from 'vue'
export default {
computed: {
layout () {
if (this.$page.path) {
if (
this.$vuepress.isLayoutExists(this.$page.frontmatter.layout)
|| Boolean(Vue.component(this.$page.frontmatter.layout))
) {
return this.$page.frontmatter.layout
const layout = this.$page.frontmatter.layout
if (layout && (this.$vuepress.getLayoutAsyncComponent(layout)
|| this.$vuepress.getVueComponent(layout))) {
return layout
}
return 'Layout'
}
Expand Down
16 changes: 6 additions & 10 deletions packages/@vuepress/core/lib/client/plugins/VuePress.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import Store from './Store'
import {
isPageExists,
isPageLoaded,
getPageAsyncComponent,
isLayoutExists,
isLayoutLoaded,
getLayoutAsyncComponent
getLayoutAsyncComponent,
getAsyncComponent,
getVueComponent
} from '../util'

class VuePress extends Store {}

Object.assign(VuePress.prototype, {
isPageExists,
isPageLoaded,
getPageAsyncComponent,
isLayoutExists,
isLayoutLoaded,
getLayoutAsyncComponent
getLayoutAsyncComponent,
getAsyncComponent,
getVueComponent
})

export default {
Expand Down
83 changes: 68 additions & 15 deletions packages/@vuepress/core/lib/client/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,89 @@ import Vue from 'vue'
import layoutComponents from '@internal/layout-components'
import pageComponents from '@internal/page-components'

const asyncComponents = Object.assign({}, layoutComponents, pageComponents)

export function isPageExists (pageKey) {
return Boolean(pageComponents[pageKey])
/**
* Create a cached version of a pure function.
*/
function cached (fn) {
const cache = Object.create(null)
// eslint-disable-next-line func-names
return function cachedFn (str) {
const hit = cache[str]
// eslint-disable-next-line no-return-assign
return hit || (cache[str] = fn(str))
}
}

export function isPageLoaded (pageKey) {
return Boolean(Vue.component(pageKey))
/**
* Camelize a hyphen-delimited string.
*/
const camelizeRE = /-(\w)/g
const camelize = cached(str => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})

/**
* Hyphenate a camelCase string.
*/
const hyphenateRE = /\B([A-Z])/g
const hyphenate = cached(str => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
})

/**
* Capitalize a string.
*/
const capitalize = cached(str => {
return str.charAt(0).toUpperCase() + str.slice(1)
})

/**
* This method was for securely getting Vue component when components
* are named in different style.
*
* e.g. a component named `a-b` can be also getted by `AB`, It's the
* same the other way round
*
* @param {function} getter a function of getting component by name
* @param {string} name component's name
* @returns {Component|AsyncComponent}
*/
export function getComponent (getter, name) {
if (!name) return
if (getter(name)) return getter(name)

const isKebabCase = name.includes('-')
if (isKebabCase) return getter(capitalize(camelize(name)))

return getter(capitalize(name)) || getter(hyphenate(name))
}

const asyncComponents = Object.assign({}, layoutComponents, pageComponents)
const asyncComponentsGetter = name => asyncComponents[name]
const pageComponentsGetter = layout => pageComponents[layout]
const layoutComponentsGetter = layout => layoutComponents[layout]
const globalComponentsGetter = name => Vue.component(name)

export function getPageAsyncComponent (pageKey) {
return pageComponents[pageKey]
return getComponent(pageComponentsGetter, pageKey)
}

export function isLayoutExists (layout) {
return Boolean(layoutComponents[layout])
export function getLayoutAsyncComponent (layout) {
return getComponent(layoutComponentsGetter, layout)
}

export function isLayoutLoaded (layout) {
return Boolean(Vue.component(layout))
export function getAsyncComponent (name) {
return getComponent(asyncComponentsGetter, name)
}

export function getLayoutAsyncComponent (pageKey) {
return layoutComponents[pageKey]
export function getVueComponent (name) {
return getComponent(globalComponentsGetter, name)
}

export function ensureAsyncComponentsLoaded (...names) {
return Promise.all(names.filter(v => v).map(async (name) => {
if (!Vue.component(name) && asyncComponents[name]) {
const comp = await asyncComponents[name]()
if (!getVueComponent(name) && getAsyncComponent(name)) {
const comp = await getAsyncComponent(name)()
Vue.component(name, comp.default)
}
}))
Expand Down
3 changes: 2 additions & 1 deletion packages/docs/docs/theme/option-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ export default {
computed: {
layout () {
if (this.$page.path) {
if (this.$vuepress.isLayoutExists(this.$frontmatter.layout)) {
if (this.$frontmatter.layout) {
// You can also check whether layout exists first as the default global layout does.
return this.$frontmatter.layout
}
return 'Layout'
Expand Down
3 changes: 2 additions & 1 deletion packages/docs/docs/zh/theme/option-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ export default {
computed: {
layout () {
if (this.$page.path) {
if (this.$vuepress.isLayoutExists(this.$frontmatter.layout)) {
if (this.$frontmatter.layout) {
// 你也可以像默认的 globalLayout 一样首先检测 layout 是否存在
return this.$frontmatter.layout
}
return 'Layout'
Expand Down

0 comments on commit 3e91eba

Please sign in to comment.