Files
vuepress/packages/@vuepress/core/lib/prepare/loadTheme.js

189 lines
5.1 KiB
JavaScript

'use strict'
/**
* Module dependencies.
*/
const {
fs, path,
moduleResolver: { getThemeResolver },
datatypes: { isString },
logger, chalk
} = require('@vuepress/shared-utils')
/**
* Resolve theme.
*
* Resolving Priority:
*
* 1. If the theme was a absolute path and that path exists, respect it
* as the theme directory.
* 2. If 'theme' directory located at vuepressDir exists, respect it as
* the theme directory.
* 3. If 'theme' was a shortcut string, resolve it from deps.
*
* @param {string} theme
* @param {string} sourceDir
* @param {string} vuepressDir
* @returns {Promise}
*/
module.exports = async function loadTheme (ctx) {
const { siteConfig, cliOptions, sourceDir, vuepressDir, pluginAPI } = ctx
const theme = siteConfig.theme || cliOptions.theme
const themeResolver = getThemeResolver()
const localThemePath = path.resolve(vuepressDir, 'theme')
const useLocalTheme
= !fs.existsSync(theme)
&& fs.existsSync(localThemePath)
&& (fs.readdirSync(localThemePath)).length > 0
let themePath = null // Mandatory
let themeEntryFile = {} // Optional
let themeName
let themeShortcut
let parentThemePath = null // Optional
let parentThemeEntryFile = {} // Optional
if (useLocalTheme) {
themePath = localThemePath
logger.tip(`Apply theme located at ${chalk.gray(themePath)}...`)
} else if (isString(theme)) {
const resolved = themeResolver.resolve(theme, sourceDir)
const { entry, name, shortcut } = resolved
if (entry === null) {
throw new Error(`Cannot resolve theme ${theme}.`)
}
themePath = normalizeThemePath(resolved)
themeName = name
themeShortcut = shortcut
logger.tip(`Apply theme ${chalk.gray(themeName)}`)
} else {
throw new Error(`[vuepress] You must specify a theme, or create a local custom theme. \n For more details, refer to https://vuepress.vuejs.org/guide/custom-themes.html#custom-themes. \n`)
}
try {
themeEntryFile = pluginAPI.normalizePlugin(themePath, ctx.themeConfig)
} catch (error) {
themeEntryFile = {}
}
themeEntryFile.name = '@vuepress/internal-theme-entry-file'
themeEntryFile.shortcut = null
// handle theme api
const layoutDirs = [
path.resolve(themePath, '.'),
path.resolve(themePath, 'layouts')
]
if (themeEntryFile.extend) {
const resolved = themeResolver.resolve(themeEntryFile.extend, sourceDir)
if (resolved.entry === null) {
throw new Error(`Cannot resolve parent theme ${themeEntryFile.extend}.`)
}
parentThemePath = normalizeThemePath(resolved)
try {
parentThemeEntryFile = pluginAPI.normalizePlugin(parentThemePath, ctx.themeConfig)
} catch (error) {
parentThemeEntryFile = {}
}
parentThemeEntryFile.name = '@vuepress/internal-parent-theme-entry-file'
parentThemeEntryFile.shortcut = null
layoutDirs.unshift(
path.resolve(parentThemePath, '.'),
path.resolve(parentThemePath, 'layouts'),
)
themeEntryFile.alias = Object.assign(
themeEntryFile.alias || {},
{ '@parent-theme': parentThemePath }
)
}
// normalize component name
const getComponentName = filename => {
filename = filename.slice(0, -4)
if (filename === '404') {
filename = 'NotFound'
}
return filename
}
const readdirSync = dir => fs.existsSync(dir) && fs.readdirSync(dir) || []
// built-in named layout or not.
const isInternal = componentName => componentName === 'Layout'
|| componentName === 'NotFound'
const layoutComponentMap = layoutDirs
.map(
layoutDir => readdirSync(layoutDir)
.filter(filename => filename.endsWith('.vue'))
.map(filename => {
const componentName = getComponentName(filename)
return {
filename,
componentName,
isInternal: isInternal(componentName),
path: path.resolve(layoutDir, filename)
}
})
)
.reduce((arr, next) => {
arr.push(...next)
return arr
}, [])
.reduce((map, component) => {
map[component.componentName] = component
return map
}, {})
const { Layout = {}, NotFound = {}} = layoutComponentMap
// layout component does not exist.
if (!Layout || !fs.existsSync(Layout.path)) {
throw new Error(`[vuepress] Cannot resolve Layout.vue file in \n ${Layout.path}`)
}
// use default 404 component.
if (!NotFound || !fs.existsSync(NotFound.path)) {
layoutComponentMap.NotFound = {
filename: 'NotFound.vue',
componentName: 'NotFound',
path: path.resolve(__dirname, '../app/components/NotFound.vue'),
isInternal: true
}
}
return {
themePath,
layoutComponentMap,
themeEntryFile,
themeName,
themeShortcut,
parentThemePath,
parentThemeEntryFile
}
}
function normalizeThemePath (resolved) {
const { entry, name, fromDep } = resolved
if (fromDep) {
const pkgPath = require.resolve(name)
return path.parse(pkgPath).dir
} else if (entry.endsWith('.js') || entry.endsWith('.vue')) {
return path.parse(entry).dir
} else {
return entry
}
}