mirror of
https://github.com/wahyd4/vuepress.git
synced 2026-08-09 05:16:23 +10:00
refactor: refine core implementation (#762)
This commit is contained in:
@@ -65,12 +65,12 @@ module.exports = async function build (sourceDir, cliOptions = {}) {
|
||||
|
||||
// render pages
|
||||
logger.wait('Rendering static HTML...')
|
||||
for (const page of options.siteData.pages) {
|
||||
for (const page of options.pages) {
|
||||
await renderPage(page)
|
||||
}
|
||||
|
||||
// if the user does not have a custom 404.md, generate the theme's default
|
||||
if (!options.siteData.pages.some(p => p.path === '/404.html')) {
|
||||
if (!options.pages.some(p => p.path === '/404.html')) {
|
||||
await renderPage({ path: '/404.html' })
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ module.exports = async function dev (sourceDir, cliOptions = {}) {
|
||||
.use(DevLogPlugin, [{
|
||||
port,
|
||||
displayHost,
|
||||
publicPath: options.publicPath
|
||||
publicPath: options.base
|
||||
}])
|
||||
|
||||
config = config.toConfig()
|
||||
@@ -118,7 +118,7 @@ module.exports = async function dev (sourceDir, cliOptions = {}) {
|
||||
|
||||
// respect base when serving static files...
|
||||
if (fs.existsSync(userPublic)) {
|
||||
app.use(mount(options.publicPath, serveStatic(userPublic)))
|
||||
app.use(mount(options.base, serveStatic(userPublic)))
|
||||
}
|
||||
|
||||
app.use(convert(history({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
function genImportAsyncComponentFile (pages) {
|
||||
return `export function loadComponent (key) {
|
||||
switch (key) {
|
||||
${pages.map(({ key, filePath }) => ` case "${key}": return import("${filePath}");`).join('\n')}
|
||||
${pages.map(({ key, _filePath }) => ` case "${key}": return import("${_filePath}");`).join('\n')}
|
||||
}
|
||||
}`
|
||||
}
|
||||
@@ -11,7 +11,7 @@ module.exports = (options, context) => ({
|
||||
|
||||
// @internal/async-component
|
||||
async clientDynamicModules () {
|
||||
const importAsyncComponentCode = genImportAsyncComponentFile(context.siteData.pages)
|
||||
const importAsyncComponentCode = genImportAsyncComponentFile(context.pages)
|
||||
return {
|
||||
name: 'async-component.js',
|
||||
content: importAsyncComponentCode,
|
||||
|
||||
@@ -3,7 +3,7 @@ module.exports = (options, context) => ({
|
||||
|
||||
// @internal/routes
|
||||
async clientDynamicModules () {
|
||||
const routesCode = await genRoutesFile(context.siteData.pages)
|
||||
const routesCode = await genRoutesFile(context.pages)
|
||||
return { name: 'routes.js', content: routesCode, dirname: 'internal' }
|
||||
}
|
||||
})
|
||||
@@ -14,11 +14,15 @@ module.exports = (options, context) => ({
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function genRoutesFile (pages) {
|
||||
function genRoute ({ path: pagePath, filePath, key: componentName }) {
|
||||
function genRoute ({
|
||||
path: pagePath,
|
||||
key: componentName,
|
||||
frontmatter = {}
|
||||
}) {
|
||||
let code = `
|
||||
{
|
||||
name: ${JSON.stringify(componentName)},
|
||||
path: ${JSON.stringify(pagePath)},
|
||||
path: ${JSON.stringify(frontmatter.permalink || pagePath)},
|
||||
component: ThemeLayout,
|
||||
beforeEnter: (to, from, next) => {
|
||||
registerComponent(${JSON.stringify(componentName)}).then(() => next())
|
||||
|
||||
@@ -3,7 +3,7 @@ module.exports = (options, context) => ({
|
||||
|
||||
// @internal/siteData
|
||||
async clientDynamicModules () {
|
||||
const code = `export const siteData = ${JSON.stringify(context.siteData, null, 2)}`
|
||||
const code = `export const siteData = ${JSON.stringify(context.getSiteData(), null, 2)}`
|
||||
return { name: 'siteData.js', content: code, dirname: 'internal' }
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
const path = require('path')
|
||||
const { writeTemp } = require('@vuepress/shared-utils')
|
||||
|
||||
class PluginContext {
|
||||
constructor (options) {
|
||||
Object.defineProperty(this, '_options', {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: options
|
||||
})
|
||||
}
|
||||
|
||||
get base () {
|
||||
return this._options.siteConfig.base
|
||||
}
|
||||
|
||||
get isProd () {
|
||||
return this._options.isProd
|
||||
}
|
||||
|
||||
get sourceDir () {
|
||||
return this._options.sourceDir
|
||||
}
|
||||
|
||||
get publicDir () {
|
||||
path.resolve(this.sourceDir, '.vuepress/public')
|
||||
}
|
||||
|
||||
get outDir () {
|
||||
return this._options.outDir
|
||||
}
|
||||
|
||||
get themePath () {
|
||||
return this._options.themePath
|
||||
}
|
||||
|
||||
get publicPath () {
|
||||
return this._options.publicPath
|
||||
}
|
||||
|
||||
get themeConfig () {
|
||||
return this._options.themeConfig
|
||||
}
|
||||
|
||||
get siteConfig () {
|
||||
return this._options.siteConfig
|
||||
}
|
||||
|
||||
get siteData () {
|
||||
return this._options.siteData
|
||||
}
|
||||
|
||||
get self () {
|
||||
return this._options.self
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(PluginContext.prototype, {
|
||||
resolve: path.resolve,
|
||||
writeTemp
|
||||
})
|
||||
|
||||
module.exports = PluginContext
|
||||
@@ -107,11 +107,14 @@ exports.hydratePlugin = function ({ config, name, shortcut, isLocal }, pluginOpt
|
||||
exports.normalizePluginsConfig = function (pluginsConfig) {
|
||||
const { valid, warnMsg } = assertTypes(pluginsConfig, [Object, Array])
|
||||
if (!valid) {
|
||||
logger.warn(
|
||||
`[${chalk.gray('config')}] ` +
|
||||
`Invalid value for "plugin" field : ${warnMsg}`
|
||||
)
|
||||
if (pluginsConfig !== undefined) {
|
||||
logger.warn(
|
||||
`[${chalk.gray('config')}] ` +
|
||||
`Invalid value for "plugin" field : ${warnMsg}`
|
||||
)
|
||||
}
|
||||
pluginsConfig = []
|
||||
return pluginsConfig
|
||||
}
|
||||
if (Array.isArray(pluginsConfig)) {
|
||||
pluginsConfig = pluginsConfig.map(item => {
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
const path = require('path')
|
||||
const createMarkdown = require('../markdown/index')
|
||||
const loadConfig = require('./loadConfig')
|
||||
const { globby } = require('@vuepress/shared-utils')
|
||||
const { sort } = require('./util')
|
||||
const { fs, logger, chalk } = require('@vuepress/shared-utils')
|
||||
|
||||
const Page = require('./Page')
|
||||
const PluginAPI = require('../plugin-api/index')
|
||||
|
||||
module.exports = class AppContext {
|
||||
/**
|
||||
* Instantiate the app context with a new API
|
||||
* @param { string } sourceDir
|
||||
* @param {{
|
||||
* isProd: boolean,
|
||||
* plugins: pluginsConfig,
|
||||
* theme: themeNameConfig
|
||||
* }} options
|
||||
*/
|
||||
constructor (sourceDir, options) {
|
||||
this.sourceDir = sourceDir
|
||||
this._options = options
|
||||
this.isProd = options.isProd
|
||||
this.vuepressDir = path.resolve(sourceDir, '.vuepress')
|
||||
this.siteConfig = loadConfig(this.vuepressDir)
|
||||
this.base = this.siteConfig.base || '/'
|
||||
this.themeConfig = this.siteConfig.themeConfig || {}
|
||||
this.outDir = this.siteConfig.dest
|
||||
? path.resolve(this.siteConfig.dest)
|
||||
: path.resolve(sourceDir, '.vuepress/dist')
|
||||
|
||||
this.markdown = createMarkdown(this.siteConfig)
|
||||
this.pluginAPI = new PluginAPI(this)
|
||||
this.pages = [] // Array<Page>
|
||||
}
|
||||
|
||||
/**
|
||||
* Load pages, load plugins, apply plugins / plugin options, etc.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async process () {
|
||||
this.normalizeHeadTagUrls()
|
||||
await this.resolveTheme()
|
||||
this.resolvePlugins()
|
||||
|
||||
await this.resolvePages()
|
||||
await this.pluginAPI.options.additionalPages.values.map(async ({ path, permalink }) => {
|
||||
await this.addPage(path, { permalink })
|
||||
})
|
||||
|
||||
await this.pluginAPI.options.ready.apply()
|
||||
this.pluginAPI.options.extendMarkdown.syncApply(this.markdown)
|
||||
await this.pluginAPI.options.clientDynamicModules.apply()
|
||||
await this.pluginAPI.options.globalUIComponents.apply()
|
||||
await this.pluginAPI.options.enhanceAppFiles.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply internal and user plugins
|
||||
*/
|
||||
resolvePlugins () {
|
||||
const themeConfig = this.themeConfig
|
||||
const siteConfig = this.siteConfig
|
||||
|
||||
const shouldUseLastUpdated = (
|
||||
themeConfig.lastUpdated ||
|
||||
Object.keys(siteConfig.locales && themeConfig.locales || {})
|
||||
.some(base => themeConfig.locales[base].lastUpdated)
|
||||
)
|
||||
|
||||
this.pluginAPI
|
||||
// internl core plugins
|
||||
.use(require('../internal-plugins/siteData'))
|
||||
.use(require('../internal-plugins/routes'))
|
||||
.use(require('../internal-plugins/rootMixins'))
|
||||
.use(require('../internal-plugins/importAsyncComponent'))
|
||||
.use(require('../internal-plugins/enhanceApp'))
|
||||
.use(require('../internal-plugins/overrideCSS'))
|
||||
.use(require('../internal-plugins/data-mixins'))
|
||||
// user plugin
|
||||
.useByPluginsConfig(this._options.plugins)
|
||||
.useByPluginsConfig(this.siteConfig.plugins)
|
||||
.useByPluginsConfig(this.themeplugins)
|
||||
// built-in plugins
|
||||
.use('@vuepress/last-updated', shouldUseLastUpdated)
|
||||
.use('@vuepress/register-components', {
|
||||
componentsDir: [
|
||||
path.resolve(this.sourceDir, '.vuepress/components'),
|
||||
path.resolve(this.themePath, 'components')
|
||||
]
|
||||
})
|
||||
.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* normalize head tag urls for base
|
||||
*/
|
||||
normalizeHeadTagUrls () {
|
||||
if (this.base !== '/' && this.siteConfig.head) {
|
||||
this.siteConfig.head.forEach(tag => {
|
||||
const attrs = tag[1]
|
||||
if (attrs) {
|
||||
for (const name in attrs) {
|
||||
if (name === 'src' || name === 'href') {
|
||||
const value = attrs[name]
|
||||
if (value.charAt(0) === '/') {
|
||||
attrs[name] = this.base + value.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all page source files located in sourceDir
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async resolvePages () {
|
||||
// resolve pageFiles
|
||||
const patterns = ['**/*.md', '!.vuepress', '!node_modules']
|
||||
if (this.siteConfig.dest) {
|
||||
// #654 exclude dest folder when dest dir was set in
|
||||
// sourceDir but not in '.vuepress'
|
||||
const outDirRelative = path.relative(this.sourceDir, this.outDir)
|
||||
if (!outDirRelative.includes('..')) {
|
||||
patterns.push('!' + outDirRelative)
|
||||
}
|
||||
}
|
||||
const pageFiles = sort(await globby(patterns, { cwd: this.sourceDir }))
|
||||
|
||||
await Promise.all(pageFiles.map(async (relative) => {
|
||||
const filePath = path.resolve(this.sourceDir, relative)
|
||||
await this.addPage(filePath, { relative })
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a page
|
||||
* @param { string } filePath
|
||||
* @param { string } relative relative path of source markdown file.
|
||||
* @param { string } permalink the URL (excluding the domain name)
|
||||
* for your pages, posts.
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
async addPage (filePath, { relative, permalink }) {
|
||||
const page = new Page(filePath, { relative, permalink })
|
||||
await page.process(this.markdown)
|
||||
await this.pluginAPI.options.extendPageData.apply(page)
|
||||
this.pages.push(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve theme
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
async resolveTheme () {
|
||||
const theme = this.siteConfig.theme || this._options.theme
|
||||
const requireResolve = (target) => {
|
||||
return require.resolve(target, {
|
||||
paths: [
|
||||
path.resolve(__dirname, '../../node_modules'),
|
||||
path.resolve(this.sourceDir)
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// resolve theme
|
||||
const localThemePath = path.resolve(this.vuepressDir, 'theme')
|
||||
const useLocalTheme = await fs.exists(localThemePath)
|
||||
|
||||
let themePath = null
|
||||
let themeLayoutPath = null
|
||||
let themeNotFoundPath = null
|
||||
let themeIndexFile = null
|
||||
let themePlugins = []
|
||||
|
||||
if (useLocalTheme) {
|
||||
logger.tip(`\nApply theme located at ${localThemePath}...`)
|
||||
|
||||
// use local custom theme
|
||||
themePath = localThemePath
|
||||
themeLayoutPath = path.resolve(localThemePath, 'Layout.vue')
|
||||
themeNotFoundPath = path.resolve(localThemePath, 'NotFound.vue')
|
||||
if (!fs.existsSync(themeLayoutPath)) {
|
||||
throw new Error(`[vuepress] Cannot resolve Layout.vue file in .vuepress/theme.`)
|
||||
}
|
||||
if (!fs.existsSync(themeNotFoundPath)) {
|
||||
throw new Error(`[vuepress] Cannot resolve NotFound.vue file in .vuepress/theme.`)
|
||||
}
|
||||
} else if (theme) {
|
||||
// use external theme
|
||||
try {
|
||||
// backward-compatible 0.x.x.
|
||||
themeLayoutPath = requireResolve(`vuepress-theme-${theme}/Layout.vue`)
|
||||
themePath = path.dirname(themeLayoutPath)
|
||||
themeNotFoundPath = path.resolve(themeLayoutPath, 'NotFound.vue')
|
||||
} catch (e) {
|
||||
try {
|
||||
themeIndexFile = requireResolve(`vuepress-theme-${theme}/index.js`)
|
||||
} catch (e) {
|
||||
try {
|
||||
themeIndexFile = requireResolve(`@vuepress/theme-${theme}`)
|
||||
themePath = path.dirname(themeIndexFile)
|
||||
themeIndexFile = require(themeIndexFile)
|
||||
themeLayoutPath = themeIndexFile.layout
|
||||
themeNotFoundPath = themeIndexFile.notFound
|
||||
themePlugins = themeIndexFile.plugins
|
||||
} catch (e) {
|
||||
throw new Error(`[vuepress] Failed to load custom theme "${theme}". File vuepress-theme-${theme}/Layout.vue does not exist.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.tip(`\nApply theme ${chalk.gray(theme)}`)
|
||||
} 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`)
|
||||
}
|
||||
|
||||
this.themePath = themePath
|
||||
this.themeLayoutPath = themeLayoutPath
|
||||
this.themeNotFoundPath = themeNotFoundPath
|
||||
this.themeplugins = themePlugins
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data to be delivered to the client.
|
||||
* @returns {{
|
||||
* title: string,
|
||||
* description: string,
|
||||
* base: string,
|
||||
* pages: Page[],
|
||||
* themeConfig: ThemeConfig,
|
||||
* locales: Locales
|
||||
* }}
|
||||
*/
|
||||
getSiteData () {
|
||||
return {
|
||||
title: this.siteConfig.title || '',
|
||||
description: this.siteConfig.description || '',
|
||||
base: this.base,
|
||||
pages: this.pages.map(page => page.toJson()),
|
||||
themeConfig: this.siteConfig.themeConfig || {},
|
||||
locales: this.siteConfig.locales
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
const path = require('path')
|
||||
const slugify = require('../markdown/slugify')
|
||||
const { fs } = require('@vuepress/shared-utils')
|
||||
const { encodePath, fileToPath } = require('./util')
|
||||
const {
|
||||
inferTitle,
|
||||
extractHeaders,
|
||||
parseFrontmatter
|
||||
} = require('../util/index')
|
||||
|
||||
module.exports = class Page {
|
||||
constructor (filePath, {
|
||||
relative,
|
||||
routePath
|
||||
}) {
|
||||
this._filePath = filePath
|
||||
if (relative) {
|
||||
this._routePath = encodePath(fileToPath(relative))
|
||||
} else {
|
||||
this._routePath = routePath
|
||||
}
|
||||
this.path = this._routePath
|
||||
}
|
||||
|
||||
async process (markdown) {
|
||||
this.key = 'v-' + Math.random().toString(16).slice(2)
|
||||
this.content = await fs.readFile(this._filePath, 'utf-8')
|
||||
const frontmatter = parseFrontmatter(this.content)
|
||||
|
||||
// infer title
|
||||
const title = inferTitle(frontmatter)
|
||||
if (title) {
|
||||
this.title = title
|
||||
}
|
||||
|
||||
// headers
|
||||
const headers = extractHeaders(
|
||||
frontmatter.content,
|
||||
['h2', 'h3'],
|
||||
markdown
|
||||
)
|
||||
if (headers.length) {
|
||||
this.headers = headers
|
||||
}
|
||||
|
||||
this.frontmatter = frontmatter.data
|
||||
|
||||
if (frontmatter.excerpt) {
|
||||
const { html } = markdown.render(frontmatter.excerpt)
|
||||
this.excerpt = html
|
||||
}
|
||||
}
|
||||
|
||||
get filename () {
|
||||
return path.parse(this._filePath).name
|
||||
}
|
||||
|
||||
get slug () {
|
||||
return slugify(this.filename)
|
||||
}
|
||||
|
||||
toJson () {
|
||||
const json = {}
|
||||
Object.keys(this).reduce((json, key) => {
|
||||
if (!key.startsWith('_')) {
|
||||
json[key] = this[key]
|
||||
}
|
||||
return json
|
||||
}, json)
|
||||
return json
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,14 @@
|
||||
const path = require('path')
|
||||
const resolveOptions = require('./resolveOptions')
|
||||
const resolveSiteData = require('./resolveSiteData')
|
||||
const PluginAPI = require('../plugin-api/index')
|
||||
const PluginContext = require('../plugin-api/context')
|
||||
const AppContext = require('./AppContext')
|
||||
|
||||
module.exports = async function prepare ({
|
||||
sourceDir,
|
||||
isProd,
|
||||
cliOptions
|
||||
cliOptions: {
|
||||
plugins,
|
||||
theme
|
||||
}
|
||||
}) {
|
||||
// 1. load options
|
||||
const options = await resolveOptions(sourceDir, cliOptions)
|
||||
options.isProd = isProd
|
||||
|
||||
// 2. apply plugins
|
||||
const { siteConfig, themeConfig, themePath, themePlugins, cliPlugins, markdown } = options
|
||||
const pluginContext = new PluginContext(options)
|
||||
const pluginAPI = new PluginAPI(pluginContext)
|
||||
options.pluginAPI = pluginAPI
|
||||
|
||||
const shouldUseLastUpdated = (
|
||||
themeConfig.lastUpdated ||
|
||||
Object.keys(siteConfig.locales && themeConfig.locales || {})
|
||||
.some(base => themeConfig.locales[base].lastUpdated)
|
||||
)
|
||||
|
||||
pluginAPI
|
||||
// internl core plugins
|
||||
.use(require('../internal-plugins/routes'))
|
||||
.use(require('../internal-plugins/rootMixins'))
|
||||
.use(require('../internal-plugins/importAsyncComponent'))
|
||||
.use(require('../internal-plugins/enhanceApp'))
|
||||
.use(require('../internal-plugins/siteData'))
|
||||
.use(require('../internal-plugins/overrideCSS'))
|
||||
.use(require('../internal-plugins/data-mixins'))
|
||||
// user plugin
|
||||
.useByPluginsConfig(cliPlugins)
|
||||
.useByPluginsConfig(siteConfig.plugins)
|
||||
.useByPluginsConfig(themePlugins)
|
||||
// built-in plugins
|
||||
.use('@vuepress/last-updated', shouldUseLastUpdated)
|
||||
.use('@vuepress/register-components', {
|
||||
componentsDir: [
|
||||
path.resolve(sourceDir, '.vuepress/components'),
|
||||
path.resolve(themePath, 'components')
|
||||
]
|
||||
})
|
||||
.apply()
|
||||
|
||||
// 3. resolve siteData
|
||||
// SiteData must be resolved after the plugin initialization
|
||||
// because plugins can be able to extend the sitedata.
|
||||
options.siteData = await resolveSiteData(options)
|
||||
|
||||
// 4. ready hook, user can do some options transformation here.
|
||||
await pluginAPI.options.ready.apply()
|
||||
|
||||
// 5. apply plugin options
|
||||
pluginAPI.options.extendMarkdown.syncApply(markdown)
|
||||
await pluginAPI.options.clientDynamicModules.apply()
|
||||
await pluginAPI.options.globalUIComponents.apply()
|
||||
await pluginAPI.options.enhanceAppFiles.apply()
|
||||
|
||||
return options
|
||||
const appContext = new AppContext(sourceDir, { plugins, theme, isProd })
|
||||
await appContext.process()
|
||||
return appContext
|
||||
}
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
const path = require('path')
|
||||
const createMarkdown = require('../markdown/index')
|
||||
const loadConfig = require('./loadConfig')
|
||||
const { sort } = require('./util')
|
||||
const { chalk, fs, logger, globby } = require('@vuepress/shared-utils')
|
||||
|
||||
module.exports = async function resolveOptions (sourceDir, cliOptions) {
|
||||
function requireResolve (target) {
|
||||
return require.resolve(target, {
|
||||
paths: [
|
||||
path.resolve(__dirname, '../../node_modules'),
|
||||
path.resolve(sourceDir)
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const vuepressDir = path.resolve(sourceDir, '.vuepress')
|
||||
const siteConfig = loadConfig(vuepressDir)
|
||||
|
||||
// normalize head tag urls for base
|
||||
const base = siteConfig.base || '/'
|
||||
if (base !== '/' && siteConfig.head) {
|
||||
siteConfig.head.forEach(tag => {
|
||||
const attrs = tag[1]
|
||||
if (attrs) {
|
||||
for (const name in attrs) {
|
||||
if (name === 'src' || name === 'href') {
|
||||
const value = attrs[name]
|
||||
if (value.charAt(0) === '/') {
|
||||
attrs[name] = base + value.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// resolve outDir
|
||||
const outDir = siteConfig.dest
|
||||
? path.resolve(siteConfig.dest)
|
||||
: path.resolve(sourceDir, '.vuepress/dist')
|
||||
|
||||
// resolve theme
|
||||
const localThemePath = path.resolve(vuepressDir, 'theme')
|
||||
const useLocalTheme = fs.existsSync(localThemePath)
|
||||
const theme = siteConfig.theme || cliOptions.theme
|
||||
|
||||
let themePath = null
|
||||
let themeLayoutPath = null
|
||||
let themeNotFoundPath = null
|
||||
let themeIndexFile = null
|
||||
let themePlugins = []
|
||||
|
||||
if (useLocalTheme) {
|
||||
logger.tip(`\nApply theme located at ${localThemePath}...`)
|
||||
|
||||
// use local custom theme
|
||||
themePath = localThemePath
|
||||
themeLayoutPath = path.resolve(localThemePath, 'Layout.vue')
|
||||
themeNotFoundPath = path.resolve(localThemePath, 'NotFound.vue')
|
||||
if (!fs.existsSync(themeLayoutPath)) {
|
||||
throw new Error(`[vuepress] Cannot resolve Layout.vue file in .vuepress/theme.`)
|
||||
}
|
||||
if (!fs.existsSync(themeNotFoundPath)) {
|
||||
throw new Error(`[vuepress] Cannot resolve NotFound.vue file in .vuepress/theme.`)
|
||||
}
|
||||
} else if (theme) {
|
||||
// use external theme
|
||||
try {
|
||||
// backward-compatible 0.x.x.
|
||||
themeLayoutPath = requireResolve(`vuepress-theme-${theme}/Layout.vue`)
|
||||
themePath = path.dirname(themeLayoutPath)
|
||||
themeNotFoundPath = path.resolve(themeLayoutPath, 'NotFound.vue')
|
||||
} catch (e) {
|
||||
try {
|
||||
themeIndexFile = requireResolve(`vuepress-theme-${theme}/index.js`)
|
||||
} catch (e) {
|
||||
try {
|
||||
themeIndexFile = requireResolve(`@vuepress/theme-${theme}`)
|
||||
themePath = path.dirname(themeIndexFile)
|
||||
themeIndexFile = require(themeIndexFile)
|
||||
themeLayoutPath = themeIndexFile.layout
|
||||
themeNotFoundPath = themeIndexFile.notFound
|
||||
themePlugins = themeIndexFile.plugins
|
||||
} catch (e) {
|
||||
throw new Error(`[vuepress] Failed to load custom theme "${theme}". File vuepress-theme-${theme}/Layout.vue does not exist.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.tip(`\nApply theme ${chalk.gray(theme)}`)
|
||||
} 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`)
|
||||
}
|
||||
|
||||
// resolve theme config
|
||||
const themeConfig = siteConfig.themeConfig || {}
|
||||
|
||||
// resolve markdown
|
||||
const markdown = createMarkdown(siteConfig)
|
||||
|
||||
// resolve pageFiles
|
||||
const patterns = ['**/*.md', '!.vuepress', '!node_modules']
|
||||
if (siteConfig.dest) {
|
||||
// #654 exclude dest folder when dest dir was set in
|
||||
// sourceDir but not in '.vuepress'
|
||||
const outDirRelative = path.relative(sourceDir, outDir)
|
||||
if (!outDirRelative.includes('..')) {
|
||||
patterns.push('!' + outDirRelative)
|
||||
}
|
||||
}
|
||||
const pageFiles = sort(await globby(patterns, { cwd: sourceDir }))
|
||||
|
||||
const options = {
|
||||
siteConfig,
|
||||
themeConfig,
|
||||
sourceDir,
|
||||
outDir,
|
||||
publicPath: base,
|
||||
pageFiles,
|
||||
themePath,
|
||||
themeLayoutPath,
|
||||
themeNotFoundPath,
|
||||
themePlugins,
|
||||
cliPlugins: cliOptions.plugins || [],
|
||||
markdown
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
const { fs } = require('@vuepress/shared-utils')
|
||||
const path = require('path')
|
||||
const { encodePath, fileToPath } = require('./util')
|
||||
const {
|
||||
inferTitle,
|
||||
extractHeaders,
|
||||
parseFrontmatter
|
||||
} = require('../util/index')
|
||||
|
||||
module.exports = async function ({
|
||||
sourceDir,
|
||||
pageFiles,
|
||||
pluginAPI,
|
||||
markdown,
|
||||
siteConfig,
|
||||
themeConfig,
|
||||
publicPath
|
||||
}) {
|
||||
async function getPageData ({ filePath, routePath, base }) {
|
||||
const key = 'v-' + Math.random().toString(16).slice(2)
|
||||
const data = { key, path: routePath, filePath }
|
||||
|
||||
const content = await fs.readFile(filePath, 'utf-8')
|
||||
|
||||
// extract yaml frontmatter
|
||||
const frontmatter = parseFrontmatter(content)
|
||||
// infer title
|
||||
const title = inferTitle(frontmatter)
|
||||
if (title) {
|
||||
data.title = title
|
||||
}
|
||||
const headers = extractHeaders(
|
||||
frontmatter.content,
|
||||
['h2', 'h3'],
|
||||
markdown
|
||||
)
|
||||
if (headers.length) {
|
||||
data.headers = headers
|
||||
}
|
||||
|
||||
data.frontmatter = frontmatter.data
|
||||
|
||||
if (frontmatter.excerpt) {
|
||||
const { html } = markdown.render(frontmatter.excerpt)
|
||||
data.excerpt = html
|
||||
}
|
||||
|
||||
await pluginAPI.options.extendPageData.apply(data)
|
||||
return data
|
||||
}
|
||||
|
||||
// resolve pagesData
|
||||
const pagesData = await Promise.all(pageFiles.map(async (base) => {
|
||||
const filePath = path.resolve(sourceDir, base)
|
||||
const routePath = encodePath(fileToPath(base))
|
||||
return getPageData({ filePath, routePath, base })
|
||||
}))
|
||||
|
||||
// resolve additional pagesData
|
||||
const additionalPagesData = await Promise.all(
|
||||
pluginAPI.options.additionalPages.values.map(async ({ route: routePath, path: filePath }) => {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`[vuepress] Cannot resolve additional page: ${filePath}`)
|
||||
}
|
||||
return getPageData({ filePath, routePath })
|
||||
})
|
||||
)
|
||||
|
||||
const siteData = {
|
||||
title: siteConfig.title || '',
|
||||
description: siteConfig.description || '',
|
||||
base: publicPath,
|
||||
pages: [
|
||||
...pagesData,
|
||||
...additionalPagesData
|
||||
],
|
||||
themeConfig,
|
||||
locales: siteConfig.locales
|
||||
}
|
||||
|
||||
return siteData
|
||||
}
|
||||
@@ -2,14 +2,12 @@ const path = require('path')
|
||||
|
||||
module.exports = function createBaseConfig ({
|
||||
siteConfig,
|
||||
siteData,
|
||||
sourceDir,
|
||||
outDir,
|
||||
publicPath,
|
||||
base: publicPath,
|
||||
themePath,
|
||||
themeLayoutPath,
|
||||
themeNotFoundPath,
|
||||
isAlgoliaSearch,
|
||||
markdown
|
||||
}, { debug } = {}, isServer) {
|
||||
const Config = require('webpack-chain')
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = (options, context) => ({
|
||||
enhanceAppFiles: [
|
||||
context.resolve(__dirname, 'client.js')
|
||||
path.resolve(__dirname, 'client.js')
|
||||
],
|
||||
|
||||
globalUIComponents: 'BackToTop'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = (pluginOptions = {}, context) => ({
|
||||
name: 'i18n-ui',
|
||||
|
||||
@@ -5,14 +7,14 @@ module.exports = (pluginOptions = {}, context) => ({
|
||||
enabled: !context.isProd,
|
||||
|
||||
enhanceAppFiles: [
|
||||
context.resolve(__dirname, 'client.js')
|
||||
path.resolve(__dirname, 'client.js')
|
||||
],
|
||||
|
||||
additionalPages: [
|
||||
{
|
||||
override: false,
|
||||
route: pluginOptions.route || '/i18n/',
|
||||
path: context.resolve(__dirname, 'index.md')
|
||||
permalink: pluginOptions.route || '/i18n/',
|
||||
path: path.resolve(__dirname, 'index.md')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const spawn = require('cross-spawn')
|
||||
|
||||
module.exports = (options = {}, context) => ({
|
||||
extendPageData ({ filePath }) {
|
||||
extendPageData ({ _filePath }) {
|
||||
const { transformer } = options
|
||||
const timestamp = getGitLastUpdatedTimeStamp(filePath)
|
||||
const timestamp = getGitLastUpdatedTimeStamp(_filePath)
|
||||
const lastUpdated = typeof transformer === 'function' ? transformer(timestamp) : timestamp
|
||||
return { lastUpdated }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user