Files
vuepress/packages/@vuepress/core/lib/theme-api/index.js
T
2019-02-26 23:53:38 +08:00

153 lines
3.8 KiB
JavaScript

const { logger, fs, path: { resolve }} = require('@vuepress/shared-utils')
const readdirSync = dir => fs.existsSync(dir) && fs.readdirSync(dir) || []
module.exports = class ThemeAPI {
constructor (theme, parentTheme) {
this.theme = theme
this.parentTheme = parentTheme || {}
this.existsParentTheme = !!this.parentTheme.path
this.vuepressPlugin = {
name: '@vuepress/internal-theme-api',
alias: {}
}
this.init()
}
setAlias (alias) {
this.vuepressPlugin.alias = {
...this.vuepressPlugin.alias,
...alias
}
}
init () {
const alias = {
'@current-theme': this.theme.path
}
if (this.existsParentTheme) {
alias['@parent-theme'] = this.parentTheme.path
}
this.componentMap = this.getComponents()
this.layoutComponentMap = this.getLayoutComponentMap()
Object.keys(this.componentMap).forEach((name) => {
const { filename, path } = this.componentMap[name]
alias[`@theme/components/${filename}`] = path
})
Object.keys(this.layoutComponentMap).forEach((name) => {
const { filename, path } = this.layoutComponentMap[name]
alias[`@theme/layouts/${filename}`] = path
})
alias['@theme'] = this.theme.path
this.setAlias(alias)
}
getComponents () {
const componentDirs = [
resolve(this.theme.path, 'components')
]
if (this.existsParentTheme) {
componentDirs.unshift(
resolve(this.parentTheme.path, 'components'),
)
}
return resolveSFCs(componentDirs)
}
getLayoutComponentMap () {
const layoutDirs = [
resolve(this.theme.path, '.'),
resolve(this.theme.path, 'layouts')
]
if (this.existsParentTheme) {
layoutDirs.unshift(
resolve(this.parentTheme.path, '.'),
resolve(this.parentTheme.path, 'layouts'),
)
}
// built-in named layout or not.
const layoutComponentMap = resolveSFCs(layoutDirs)
const { Layout = {}, NotFound = {}} = layoutComponentMap
// layout component does not exist.
if (!Layout || !fs.existsSync(Layout.path)) {
const fallbackLayoutPath = resolve(__dirname, 'Layout.fallback.vue')
layoutComponentMap.Layout = {
filename: 'Layout.vue',
componentName: 'Layout',
path: fallbackLayoutPath,
isInternal: true
}
logger.warn(
`[vuepress] Cannot resolve Layout.vue file in \n ${Layout.path},`
+ `fallback to default layout: ${fallbackLayoutPath}`
)
}
if (!NotFound || !fs.existsSync(NotFound.path)) {
layoutComponentMap.NotFound = {
filename: 'NotFound.vue',
componentName: 'NotFound',
path: resolve(__dirname, '../app/components/NotFound.vue'),
isInternal: true
}
}
return layoutComponentMap
}
}
/**
* Resolve Vue SFCs, return a Map
*
* @param dirs
* @returns {*}
*/
function resolveSFCs (dirs) {
return dirs.map(
layoutDir => readdirSync(layoutDir)
.filter(filename => filename.endsWith('.vue'))
.map(filename => {
const componentName = getComponentName(filename)
return {
filename,
componentName,
isInternal: isInternal(componentName),
path: resolve(layoutDir, filename)
}
})
).reduce((arr, next) => {
arr.push(...next)
return arr
}, []).reduce((map, component) => {
map[component.componentName] = component
return map
}, {})
}
/**
* normalize component name
*
* @param {strin} filename
* @returns {string}
*/
function getComponentName (filename) {
filename = filename.slice(0, -4)
if (filename === '404') {
filename = 'NotFound'
}
return filename
}
/**
* Whether it's agreed layout component
*
* @param name
* @returns {boolean}
*/
function isInternal (name) {
return name === 'Layout' || name === 'NotFound'
}