mirror of
https://github.com/wahyd4/vuepress.git
synced 2026-08-11 06:16:08 +10:00
286 lines
8.2 KiB
JavaScript
286 lines
8.2 KiB
JavaScript
const path = require('path')
|
|
const fs = require('fs-extra')
|
|
const globby = require('globby')
|
|
const yamlParser = require('js-yaml')
|
|
const yaml = require('yaml-front-matter')
|
|
const createMarkdown = require('./markdown')
|
|
const tempPath = path.resolve(__dirname, 'app/.temp')
|
|
const { inferTitle, extractHeaders } = 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)
|
|
}
|
|
}
|
|
|
|
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
|
|
if (options.useDefaultTheme) {
|
|
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 enhancePath = path.resolve(sourceDir, '.vuepress/enhanceApp.js')
|
|
const hasEnhancePath = fs.existsSync(enhancePath)
|
|
await writeTemp(
|
|
'enhanceApp.js',
|
|
hasEnhancePath
|
|
? `export { default } from ${JSON.stringify(enhancePath)}`
|
|
: `export default function () {}`
|
|
)
|
|
|
|
return options
|
|
}
|
|
|
|
async function resolveOptions (sourceDir) {
|
|
const vuepressDir = path.resolve(sourceDir, '.vuepress')
|
|
const configPath = path.resolve(vuepressDir, 'config.js')
|
|
const configYmlPath = path.resolve(vuepressDir, 'config.yml')
|
|
|
|
delete require.cache[configPath]
|
|
let siteConfig = {}
|
|
if (fs.existsSync(configYmlPath)) {
|
|
const content = await fs.readFile(configYmlPath, 'utf-8')
|
|
siteConfig = yamlParser.safeLoad(content)
|
|
} else if (fs.existsSync(configPath)) {
|
|
siteConfig = require(configPath)
|
|
}
|
|
|
|
// 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 theme
|
|
const useDefaultTheme = (
|
|
!siteConfig.theme &&
|
|
!fs.existsSync(path.resolve(vuepressDir, 'theme'))
|
|
)
|
|
|
|
const options = {
|
|
siteConfig,
|
|
sourceDir,
|
|
outDir: siteConfig.dest
|
|
? path.resolve(siteConfig.dest)
|
|
: path.resolve(sourceDir, '.vuepress/dist'),
|
|
publicPath: base,
|
|
pageFiles: sort(await globby(['**/*.md', '!.vuepress', '!node_modules'], { cwd: sourceDir })),
|
|
pagesData: null,
|
|
themePath: null,
|
|
notFoundPath: null,
|
|
useDefaultTheme,
|
|
markdown: createMarkdown(siteConfig)
|
|
}
|
|
|
|
if (useDefaultTheme) {
|
|
// use default theme
|
|
options.themePath = path.resolve(__dirname, 'default-theme/Layout.vue')
|
|
options.notFoundPath = path.resolve(__dirname, 'default-theme/NotFound.vue')
|
|
} else {
|
|
let themeDir
|
|
let themePath
|
|
// resolve custom theme
|
|
if (siteConfig.theme) {
|
|
try {
|
|
themePath = require.resolve(`vuepress-theme-${siteConfig.theme}/Layout.vue`)
|
|
themeDir = path.dirname(themePath)
|
|
} catch (e) {
|
|
throw new Error(`[vuepress] Failed to load custom theme "${
|
|
siteConfig.theme
|
|
}". File vuepress-theme-${siteConfig.theme}/Layout.vue does not exist.`)
|
|
}
|
|
} else {
|
|
themeDir = path.resolve(vuepressDir, 'theme')
|
|
themePath = path.resolve(themeDir, 'Layout.vue')
|
|
if (!fs.existsSync(themePath)) {
|
|
throw new Error(`[vuepress] Cannot resolve Layout.vue file in .vuepress/theme.`)
|
|
}
|
|
}
|
|
options.themePath = themePath
|
|
|
|
const notFoundPath = path.resolve(themeDir, 'NotFound.vue')
|
|
if (fs.existsSync(notFoundPath)) {
|
|
options.notFoundPath = notFoundPath
|
|
} else {
|
|
options.notFoundPath = path.resolve(__dirname, 'default-theme/NotFound.vue')
|
|
}
|
|
}
|
|
|
|
// resolve pages
|
|
const pagesData = await Promise.all(options.pageFiles.map(async (file) => {
|
|
const data = {
|
|
path: fileToPath(file)
|
|
}
|
|
|
|
// extract yaml frontmatter
|
|
const content = await fs.readFile(path.resolve(sourceDir, file), 'utf-8')
|
|
const frontmatter = yaml.loadFront(content)
|
|
// infer title
|
|
const title = inferTitle(frontmatter)
|
|
if (title) {
|
|
data.title = title
|
|
}
|
|
const headers = extractHeaders(
|
|
frontmatter.__content,
|
|
['h2', 'h3'],
|
|
options.markdown
|
|
)
|
|
if (headers.length) {
|
|
data.headers = headers
|
|
}
|
|
delete frontmatter.__content
|
|
if (Object.keys(frontmatter).length) {
|
|
data.frontmatter = frontmatter
|
|
}
|
|
return data
|
|
}))
|
|
|
|
// resolve site data
|
|
options.siteData = {
|
|
title: siteConfig.title || '',
|
|
description: siteConfig.description || '',
|
|
base: siteConfig.base || '/',
|
|
pages: pagesData,
|
|
themeConfig: siteConfig.themeConfig || {},
|
|
locales: siteConfig.locales
|
|
}
|
|
|
|
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 = /\b(index|readme)\.md$/i
|
|
const extRE = /\.(vue|md)$/
|
|
|
|
function fileToPath (file) {
|
|
if (isIndexFile(file)) {
|
|
// README.md -> /
|
|
// foo/README.md -> /foo/
|
|
return '/' + file.replace(indexRE, '')
|
|
} 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: Theme,
|
|
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
|
|
}
|
|
|
|
return (
|
|
`import Theme from '@theme'\n` +
|
|
`export const routes = [${pages.map(genRoute).join(',')}\n]`
|
|
)
|
|
}
|
|
|
|
function sort (arr) {
|
|
return arr.sort((a, b) => {
|
|
if (a < b) return -1
|
|
if (a > b) return 1
|
|
return 0
|
|
})
|
|
}
|