prepare.js 8.3 KB
Newer Older
E
Evan You 已提交
1
const path = require('path')
2
const fs = require('fs-extra')
E
Evan You 已提交
3
const globby = require('globby')
A
Aliex Leung 已提交
4
const yamlParser = require('js-yaml')
5 6
const yaml = require('yaml-front-matter')
const createMarkdown = require('./markdown')
E
Evan You 已提交
7
const tempPath = path.resolve(__dirname, 'app/.temp')
E
Evan You 已提交
8
const { inferTitle, extractHeaders } = require('./util')
E
Evan You 已提交
9

E
Evan You 已提交
10
fs.ensureDirSync(tempPath)
E
Evan You 已提交
11

E
Evan You 已提交
12
const tempCache = new Map()
E
Evan You 已提交
13
async function writeTemp (file, content) {
E
Evan You 已提交
14 15 16
  // cache write to avoid hitting the dist if it didn't change
  const cached = tempCache.get(file)
  if (cached !== content) {
E
Evan You 已提交
17
    await fs.writeFile(path.join(tempPath, file), content)
E
Evan You 已提交
18 19 20 21
    tempCache.set(file, content)
  }
}

E
Evan You 已提交
22 23 24 25
module.exports = async function prepare (sourceDir) {
  // 1. load options
  const options = await resolveOptions(sourceDir)

E
Evan You 已提交
26 27
  // 2. generate routes & user components registration code
  const routesCode = await genRoutesFile(options)
E
tweaks  
Evan You 已提交
28
  const componentCode = await genComponentRegistrationFile(options)
E
Evan You 已提交
29

E
Evan You 已提交
30
  await writeTemp('routes.js', [
E
Evan You 已提交
31 32 33
    componentCode,
    routesCode
  ].join('\n\n'))
E
tweaks  
Evan You 已提交
34

E
Evan You 已提交
35
  // 3. generate siteData
E
tweaks  
Evan You 已提交
36
  const dataCode = `export const siteData = ${JSON.stringify(options.siteData, null, 2)}`
E
Evan You 已提交
37
  await writeTemp('siteData.js', dataCode)
E
tweaks  
Evan You 已提交
38

E
Evan You 已提交
39
  // 4. generate basic polyfill if need to support older browsers
40 41 42 43 44 45
  let polyfillCode = ``
  if (!options.siteConfig.evergreen) {
    polyfillCode =
`import 'es6-promise/auto'
if (!Object.assign) Object.assign = require('object-assign')`
  }
E
Evan You 已提交
46
  await writeTemp('polyfill.js', polyfillCode)
47

E
Evan You 已提交
48
  // 5. handle user override
E
Evan You 已提交
49
  if (options.useDefaultTheme) {
E
Evan You 已提交
50 51
    const overridePath = path.resolve(sourceDir, '.vuepress/override.styl')
    const hasUserOverride = fs.existsSync(overridePath)
E
Evan You 已提交
52
    await writeTemp(`override.styl`, hasUserOverride ? `@import(${JSON.stringify(overridePath)})` : ``)
E
Evan You 已提交
53 54
  }

方剑成 已提交
55 56 57 58 59 60 61 62 63 64
  // 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 () {}`
  )

E
Evan You 已提交
65 66 67 68
  return options
}

async function resolveOptions (sourceDir) {
E
Evan You 已提交
69 70
  const vuepressDir = path.resolve(sourceDir, '.vuepress')
  const configPath = path.resolve(vuepressDir, 'config.js')
A
Aliex Leung 已提交
71
  const configYmlPath = path.resolve(vuepressDir, 'config.yml')
72 73

  delete require.cache[configPath]
A
Aliex Leung 已提交
74 75 76 77 78 79 80
  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)
  }
E
Evan You 已提交
81

E
Evan You 已提交
82 83
  // normalize description
  if (siteConfig.description) {
E
Evan You 已提交
84 85 86
    (siteConfig.head || (siteConfig.head = [])).unshift([
      'meta', { name: 'description', content: siteConfig.description }
    ])
E
Evan You 已提交
87 88
  }

E
Evan You 已提交
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  // 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)
            }
          }
        }
      }
    })
  }

E
Evan You 已提交
107 108 109 110 111 112
  // resolve theme
  const useDefaultTheme = (
    !siteConfig.theme &&
    !fs.existsSync(path.resolve(vuepressDir, 'theme'))
  )

E
Evan You 已提交
113
  const options = {
E
Evan You 已提交
114
    siteConfig,
E
Evan You 已提交
115
    sourceDir,
E
Evan You 已提交
116 117 118
    outDir: siteConfig.dest
      ? path.resolve(siteConfig.dest)
      : path.resolve(sourceDir, '.vuepress/dist'),
E
Evan You 已提交
119
    publicPath: base,
120
    pageFiles: sort(await globby(['**/*.md', '!.vuepress', '!node_modules'], { cwd: sourceDir })),
E
Evan You 已提交
121 122
    pagesData: null,
    themePath: null,
E
Evan You 已提交
123
    notFoundPath: null,
124 125
    useDefaultTheme,
    markdown: createMarkdown(siteConfig)
E
Evan You 已提交
126 127
  }

E
Evan You 已提交
128
  if (useDefaultTheme) {
E
Evan You 已提交
129 130 131 132
    // use default theme
    options.themePath = path.resolve(__dirname, 'default-theme/Layout.vue')
    options.notFoundPath = path.resolve(__dirname, 'default-theme/NotFound.vue')
  } else {
E
Evan You 已提交
133 134
    let themeDir
    let themePath
E
Evan You 已提交
135
    // resolve custom theme
E
Evan You 已提交
136 137 138 139 140 141 142 143 144
    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.`)
      }
E
Evan You 已提交
145
    } else {
E
Evan You 已提交
146 147 148 149 150
      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.`)
      }
E
Evan You 已提交
151
    }
E
Evan You 已提交
152
    options.themePath = themePath
E
Evan You 已提交
153

154
    const notFoundPath = path.resolve(themeDir, 'NotFound.vue')
E
Evan You 已提交
155 156 157
    if (fs.existsSync(notFoundPath)) {
      options.notFoundPath = notFoundPath
    } else {
E
Evan You 已提交
158
      options.notFoundPath = path.resolve(__dirname, 'default-theme/NotFound.vue')
E
Evan You 已提交
159
    }
E
tweaks  
Evan You 已提交
160 161
  }

E
Evan You 已提交
162
  // resolve pages
E
Evan You 已提交
163
  const pagesData = await Promise.all(options.pageFiles.map(async (file) => {
E
tweaks  
Evan You 已提交
164
    const data = {
E
Evan You 已提交
165
      path: fileToPath(file)
E
Evan You 已提交
166
    }
E
Evan You 已提交
167 168

    // extract yaml frontmatter
E
Evan You 已提交
169
    const content = await fs.readFile(path.resolve(sourceDir, file), 'utf-8')
E
Evan You 已提交
170
    const frontmatter = yaml.loadFront(content)
E
Evan You 已提交
171
    // infer title
E
Evan You 已提交
172 173 174
    const title = inferTitle(frontmatter)
    if (title) {
      data.title = title
E
Evan You 已提交
175
    }
176 177 178 179 180
    const headers = extractHeaders(
      frontmatter.__content,
      ['h2', 'h3'],
      options.markdown
    )
E
Evan You 已提交
181 182 183
    if (headers.length) {
      data.headers = headers
    }
E
Evan You 已提交
184
    delete frontmatter.__content
E
Evan You 已提交
185 186
    if (Object.keys(frontmatter).length) {
      data.frontmatter = frontmatter
E
Evan You 已提交
187
    }
E
tweaks  
Evan You 已提交
188
    return data
E
Evan You 已提交
189
  }))
E
Evan You 已提交
190

E
Evan You 已提交
191
  // resolve site data
E
Evan You 已提交
192
  options.siteData = {
E
Evan You 已提交
193 194
    title: siteConfig.title || '',
    description: siteConfig.description || '',
E
Evan You 已提交
195
    base: siteConfig.base || '/',
E
Evan You 已提交
196
    pages: pagesData,
E
Evan You 已提交
197
    themeConfig: siteConfig.themeConfig || {}
E
Evan You 已提交
198
  }
E
Evan You 已提交
199 200 201 202

  return options
}

E
Evan You 已提交
203
async function genComponentRegistrationFile ({ sourceDir }) {
E
Evan You 已提交
204
  function genImport (file) {
E
Evan You 已提交
205
    const name = fileToComponentName(file)
E
Evan You 已提交
206
    const baseDir = path.resolve(sourceDir, '.vuepress/components')
E
Evan You 已提交
207 208 209 210 211
    const absolutePath = path.resolve(baseDir, file)
    const code = `Vue.component(${JSON.stringify(name)}, () => import(${JSON.stringify(absolutePath)}))`
    return code
  }
  const components = (await resolveComponents(sourceDir)) || []
E
Evan You 已提交
212
  return `import Vue from 'vue'\n` + components.map(genImport).join('\n')
E
Evan You 已提交
213 214
}

215
const indexRE = /\b(index|readme)\.md$/i
E
Evan You 已提交
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
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}`
E
Evan You 已提交
239 240 241
}

function isIndexFile (file) {
E
Evan You 已提交
242
  return indexRE.test(file)
E
Evan You 已提交
243 244
}

E
Evan You 已提交
245
async function resolveComponents (sourceDir) {
E
Evan You 已提交
246
  const componentDir = path.resolve(sourceDir, '.vuepress/components')
E
Evan You 已提交
247 248 249
  if (!fs.existsSync(componentDir)) {
    return
  }
E
Evan You 已提交
250
  return sort(await globby(['**/*.vue'], { cwd: componentDir }))
E
Evan You 已提交
251 252
}

E
Evan You 已提交
253 254 255 256
async function genRoutesFile ({ siteData: { pages }, sourceDir, pageFiles }) {
  function genRoute ({ path: pagePath }, index) {
    const file = pageFiles[index]
    const filePath = path.resolve(sourceDir, file)
257
    let code = `
E
Evan You 已提交
258
    {
E
Evan You 已提交
259 260 261 262 263 264 265 266
      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()
        })
      }
E
Evan You 已提交
267
    }`
268 269 270 271 272 273 274 275

    if (/\/$/.test(pagePath)) {
      code += `,{
        path: ${JSON.stringify(pagePath + 'index.html')},
        redirect: ${JSON.stringify(pagePath)}
      }`
    }

E
Evan You 已提交
276 277 278
    return code
  }

E
tweaks  
Evan You 已提交
279
  return (
280
    `import Theme from '@theme'\n` +
E
tweaks  
Evan You 已提交
281 282
    `export const routes = [${pages.map(genRoute).join(',')}\n]`
  )
E
Evan You 已提交
283
}
E
Evan You 已提交
284 285 286 287 288 289 290 291

function sort (arr) {
  return arr.sort((a, b) => {
    if (a < b) return -1
    if (a > b) return 1
    return 0
  })
}