forked from vuejs/vuepress
-
Notifications
You must be signed in to change notification settings - Fork 0
/
prepare.js
307 lines (274 loc) · 8.86 KB
/
prepare.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
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, loadPlugin, isAsyncFunction } = 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 () {}`
)
// 7. handle user plugins
if (options.siteConfig.plugins && Array.isArray(options.siteConfig.plugins)) {
const { plugins } = options.siteConfig
for (const plugin of plugins) {
let pluginRequired
if (typeof plugin === 'string') {
pluginRequired = loadPlugin(plugin, options)
}
if (typeof plugin === 'object') {
pluginRequired = loadPlugin(plugin.resolve, options)
if (plugin.options) {
pluginRequired = pluginRequired(plugin.options)
}
}
if (isAsyncFunction(pluginRequired)) {
await pluginRequired(options)
} else {
pluginRequired(options)
}
}
}
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
})
}