mirror of
https://github.com/wahyd4/vuepress.git
synced 2026-08-24 04:35:55 +10:00
349 lines
9.6 KiB
JavaScript
349 lines
9.6 KiB
JavaScript
const path = require('path')
|
|
const fs = require('fs-extra')
|
|
const globby = require('globby')
|
|
const createMarkdown = require('./markdown')
|
|
const loadConfig = require('./util/loadConfig')
|
|
const tempPath = path.resolve(__dirname, 'app/.temp')
|
|
const {
|
|
inferTitle,
|
|
extractHeaders,
|
|
parseFrontmatter,
|
|
getGitLastUpdatedTimeStamp
|
|
} = require('./util')
|
|
|
|
fs.ensureDirSync(tempPath)
|
|
|
|
const tempCache = new Map()
|
|
async function writeTemp (file, content) {
|
|
// cache write to avoid hitting the dist if it didn't change
|
|
const cached = tempCache.get(file)
|
|
if (cached !== content) {
|
|
await fs.writeFile(path.join(tempPath, file), content)
|
|
tempCache.set(file, content)
|
|
}
|
|
}
|
|
|
|
async function writeEnhanceTemp (destName, srcPath) {
|
|
await writeTemp(
|
|
destName,
|
|
fs.existsSync(srcPath)
|
|
? `export { default } from ${JSON.stringify(srcPath)}`
|
|
: `export default function () {}`
|
|
)
|
|
}
|
|
|
|
module.exports = async function prepare (sourceDir) {
|
|
// 1. load options
|
|
const options = await resolveOptions(sourceDir)
|
|
|
|
// 2. generate routes & user components registration code
|
|
const routesCode = await genRoutesFile(options)
|
|
const componentCode = await genComponentRegistrationFile(options)
|
|
|
|
await writeTemp('routes.js', [
|
|
componentCode,
|
|
routesCode
|
|
].join('\n\n'))
|
|
|
|
// 3. generate siteData
|
|
const dataCode = `export const siteData = ${JSON.stringify(options.siteData, null, 2)}`
|
|
await writeTemp('siteData.js', dataCode)
|
|
|
|
// 4. generate basic polyfill if need to support older browsers
|
|
let polyfillCode = ``
|
|
if (!options.siteConfig.evergreen) {
|
|
polyfillCode =
|
|
`import 'es6-promise/auto'
|
|
if (!Object.assign) Object.assign = require('object-assign')`
|
|
}
|
|
await writeTemp('polyfill.js', polyfillCode)
|
|
|
|
// 5. handle user override
|
|
const overridePath = path.resolve(sourceDir, '.vuepress/override.styl')
|
|
const hasUserOverride = fs.existsSync(overridePath)
|
|
await writeTemp(`override.styl`, hasUserOverride ? `@import(${JSON.stringify(overridePath)})` : ``)
|
|
|
|
// 6. handle enhanceApp.js
|
|
const enhanceAppPath = path.resolve(sourceDir, '.vuepress/enhanceApp.js')
|
|
await writeEnhanceTemp('enhanceApp.js', enhanceAppPath)
|
|
|
|
// 7. handle the theme enhanceApp.js
|
|
await writeEnhanceTemp('themeEnhanceApp.js', options.themeEnhanceAppPath)
|
|
|
|
return options
|
|
}
|
|
|
|
async function resolveOptions (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 useDefaultTheme = (
|
|
!siteConfig.theme &&
|
|
!fs.existsSync(path.resolve(vuepressDir, 'theme'))
|
|
)
|
|
const defaultThemePath = path.resolve(__dirname, 'default-theme')
|
|
let themePath = null
|
|
let themeLayoutPath = null
|
|
let themeNotFoundPath = null
|
|
let themeEnhanceAppPath = null
|
|
|
|
if (useDefaultTheme) {
|
|
// use default theme
|
|
themePath = defaultThemePath
|
|
themeLayoutPath = path.resolve(defaultThemePath, 'Layout.vue')
|
|
themeNotFoundPath = path.resolve(defaultThemePath, 'NotFound.vue')
|
|
} else {
|
|
// resolve theme Layout
|
|
if (siteConfig.theme) {
|
|
// use external theme
|
|
try {
|
|
themeLayoutPath = require.resolve(`vuepress-theme-${siteConfig.theme}/Layout.vue`, {
|
|
paths: [
|
|
path.resolve(__dirname, '../node_modules'),
|
|
path.resolve(sourceDir)
|
|
]
|
|
})
|
|
themePath = path.dirname(themeLayoutPath)
|
|
} catch (e) {
|
|
throw new Error(`[vuepress] Failed to load custom theme "${
|
|
siteConfig.theme
|
|
}". File vuepress-theme-${siteConfig.theme}/Layout.vue does not exist.`)
|
|
}
|
|
} else {
|
|
// use custom theme
|
|
themePath = path.resolve(vuepressDir, 'theme')
|
|
themeLayoutPath = path.resolve(themePath, 'Layout.vue')
|
|
if (!fs.existsSync(themeLayoutPath)) {
|
|
throw new Error(`[vuepress] Cannot resolve Layout.vue file in .vuepress/theme.`)
|
|
}
|
|
}
|
|
|
|
// resolve theme NotFound
|
|
themeNotFoundPath = path.resolve(themePath, 'NotFound.vue')
|
|
if (!fs.existsSync(themeNotFoundPath)) {
|
|
themeNotFoundPath = path.resolve(defaultThemePath, 'NotFound.vue')
|
|
}
|
|
|
|
// resolve theme enhanceApp
|
|
themeEnhanceAppPath = path.resolve(themePath, 'enhanceApp.js')
|
|
if (!fs.existsSync(themeEnhanceAppPath)) {
|
|
themeEnhanceAppPath = null
|
|
}
|
|
}
|
|
|
|
// resolve theme config
|
|
const themeConfig = siteConfig.themeConfig || {}
|
|
|
|
// resolve algolia
|
|
const isAlgoliaSearch = (
|
|
themeConfig.algolia ||
|
|
Object.keys(siteConfig.locales && themeConfig.locales || {})
|
|
.some(base => themeConfig.locales[base].algolia)
|
|
)
|
|
|
|
// resolve markdown
|
|
const markdown = createMarkdown(siteConfig)
|
|
|
|
// resolve pageFiles
|
|
const pageFiles = sort(await globby(['**/*.md', '!.vuepress', '!node_modules'], { cwd: sourceDir }))
|
|
|
|
// resolve lastUpdated
|
|
const shouldResolveLastUpdated = (
|
|
themeConfig.lastUpdated ||
|
|
Object.keys(siteConfig.locales && themeConfig.locales || {})
|
|
.some(base => themeConfig.locales[base].lastUpdated)
|
|
)
|
|
|
|
// resolve pagesData
|
|
const pagesData = await Promise.all(pageFiles.map(async (file) => {
|
|
const filepath = path.resolve(sourceDir, file)
|
|
const data = {
|
|
path: fileToPath(file)
|
|
}
|
|
|
|
if (shouldResolveLastUpdated) {
|
|
data.lastUpdated = getGitLastUpdatedTimeStamp(filepath)
|
|
}
|
|
|
|
// extract yaml frontmatter
|
|
const content = await fs.readFile(filepath, 'utf-8')
|
|
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
|
|
}
|
|
if (Object.keys(frontmatter.data).length) {
|
|
data.frontmatter = frontmatter.data
|
|
}
|
|
if (frontmatter.excerpt) {
|
|
const { html } = markdown.render(frontmatter.excerpt)
|
|
data.excerpt = html
|
|
}
|
|
return data
|
|
}))
|
|
|
|
// resolve site data
|
|
const siteData = {
|
|
title: siteConfig.title || '',
|
|
description: siteConfig.description || '',
|
|
base,
|
|
pages: pagesData,
|
|
themeConfig,
|
|
locales: siteConfig.locales
|
|
}
|
|
|
|
const options = {
|
|
siteConfig,
|
|
siteData,
|
|
sourceDir,
|
|
outDir,
|
|
publicPath: base,
|
|
pageFiles,
|
|
pagesData,
|
|
themePath,
|
|
themeLayoutPath,
|
|
themeNotFoundPath,
|
|
themeEnhanceAppPath,
|
|
useDefaultTheme,
|
|
isAlgoliaSearch,
|
|
markdown
|
|
}
|
|
|
|
return options
|
|
}
|
|
|
|
async function genComponentRegistrationFile ({ sourceDir }) {
|
|
function genImport (file) {
|
|
const name = fileToComponentName(file)
|
|
const baseDir = path.resolve(sourceDir, '.vuepress/components')
|
|
const absolutePath = path.resolve(baseDir, file)
|
|
const code = `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))`
|
|
return code
|
|
}
|
|
const components = (await resolveComponents(sourceDir)) || []
|
|
return `import Vue from 'vue'\n` + components.map(genImport).join('\n')
|
|
}
|
|
|
|
const indexRE = /(^|.*\/)(index|readme)\.md$/i
|
|
const extRE = /\.(vue|md)$/
|
|
|
|
function fileToPath (file) {
|
|
if (isIndexFile(file)) {
|
|
// README.md -> /
|
|
// foo/README.md -> /foo/
|
|
return file.replace(indexRE, '/$1')
|
|
} else {
|
|
// foo.md -> /foo.html
|
|
// foo/bar.md -> /foo/bar.html
|
|
return `/${file.replace(extRE, '').replace(/\\/g, '/')}.html`
|
|
}
|
|
}
|
|
|
|
function fileToComponentName (file) {
|
|
let normalizedName = file
|
|
.replace(/\/|\\/g, '-')
|
|
.replace(extRE, '')
|
|
if (isIndexFile(file)) {
|
|
normalizedName = normalizedName.replace(/readme$/i, 'index')
|
|
}
|
|
const pagePrefix = /\.md$/.test(file) ? `page-` : ``
|
|
return `${pagePrefix}${normalizedName}`
|
|
}
|
|
|
|
function isIndexFile (file) {
|
|
return indexRE.test(file)
|
|
}
|
|
|
|
async function resolveComponents (sourceDir) {
|
|
const componentDir = path.resolve(sourceDir, '.vuepress/components')
|
|
if (!fs.existsSync(componentDir)) {
|
|
return
|
|
}
|
|
return sort(await globby(['**/*.vue'], { cwd: componentDir }))
|
|
}
|
|
|
|
async function genRoutesFile ({ siteData: { pages }, sourceDir, pageFiles }) {
|
|
function genRoute ({ path: pagePath }, index) {
|
|
const file = pageFiles[index]
|
|
const filePath = path.resolve(sourceDir, file)
|
|
let code = `
|
|
{
|
|
path: ${JSON.stringify(pagePath)},
|
|
component: ThemeLayout,
|
|
beforeEnter: (to, from, next) => {
|
|
import(${JSON.stringify(filePath)}).then(comp => {
|
|
Vue.component(${JSON.stringify(fileToComponentName(file))}, comp.default)
|
|
next()
|
|
})
|
|
}
|
|
}`
|
|
|
|
if (/\/$/.test(pagePath)) {
|
|
code += `,
|
|
{
|
|
path: ${JSON.stringify(pagePath + 'index.html')},
|
|
redirect: ${JSON.stringify(pagePath)}
|
|
}`
|
|
}
|
|
|
|
return code
|
|
}
|
|
|
|
const notFoundRoute = `,
|
|
{
|
|
path: '*',
|
|
component: ThemeNotFound
|
|
}`
|
|
|
|
return (
|
|
`import ThemeLayout from '@themeLayout'\n` +
|
|
`import ThemeNotFound from '@themeNotFound'\n` +
|
|
`import { injectMixins } from '@app/util'\n` +
|
|
`import rootMixins from '@app/root-mixins'\n\n` +
|
|
`injectMixins(ThemeLayout, rootMixins)\n` +
|
|
`injectMixins(ThemeNotFound, rootMixins)\n\n` +
|
|
`export const routes = [${pages.map(genRoute).join(',')}${notFoundRoute}\n]`
|
|
)
|
|
}
|
|
|
|
function sort (arr) {
|
|
return arr.sort((a, b) => {
|
|
if (a < b) return -1
|
|
if (a > b) return 1
|
|
return 0
|
|
})
|
|
}
|