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

E
Evan You 已提交
9 10
mkdirp(tempPath)

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

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

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

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

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

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

E
Evan You 已提交
47 48 49 50
  return options
}

async function resolveOptions (sourceDir) {
E
Evan You 已提交
51 52
  const vuepressDir = path.resolve(sourceDir, '.vuepress')
  const configPath = path.resolve(vuepressDir, 'config.js')
53 54

  delete require.cache[configPath]
E
Evan You 已提交
55
  const siteConfig = fs.existsSync(configPath) ? require(configPath) : {}
E
Evan You 已提交
56

E
Evan You 已提交
57 58
  // normalize description
  if (siteConfig.description) {
E
Evan You 已提交
59 60 61
    (siteConfig.head || (siteConfig.head = [])).unshift([
      'meta', { name: 'description', content: siteConfig.description }
    ])
E
Evan You 已提交
62 63
  }

E
Evan You 已提交
64
  const options = {
E
Evan You 已提交
65
    siteConfig,
E
Evan You 已提交
66
    sourceDir,
E
Evan You 已提交
67 68 69
    outDir: siteConfig.dest
      ? path.resolve(siteConfig.dest)
      : path.resolve(sourceDir, '.vuepress/dist'),
E
Evan You 已提交
70
    publicPath: siteConfig.base || '/',
E
Evan You 已提交
71
    pageFiles: sort(await globby(['**/*.md', '!.vuepress'], { cwd: sourceDir })),
E
Evan You 已提交
72 73 74
    pagesData: null,
    themePath: null,
    notFoundPath: null
E
Evan You 已提交
75 76
  }

E
Evan You 已提交
77 78 79 80 81 82
  // resolve theme
  const hasTheme = (
    siteConfig.theme ||
    fs.existsSync(path.resolve(vuepressDir, 'theme'))
  )

E
Evan You 已提交
83 84 85 86 87
  if (!hasTheme) {
    // 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 已提交
88 89
    // resolve custom theme
    const themeDir = siteConfig.theme
E
Evan You 已提交
90
      ? path.resolve(__dirname, `../../vuepress-theme-${siteConfig.theme}`)
E
Evan You 已提交
91
      : path.resolve(sourceDir, 'theme')
E
Evan You 已提交
92

E
Evan You 已提交
93 94 95 96
    const themePath = path.resolve(themeDir, 'Layout.vue')
    if (fs.existsSync(themePath)) {
      options.themePath = themePath
    } else {
E
Evan You 已提交
97 98 99
      throw new Error(`[vuepress] Cannot resolve Layout.vue file for custom theme${
        siteConfig.theme ? ` "${siteConfig.theme}"` : ``
      }.`)
E
Evan You 已提交
100 101 102 103 104 105
    }

    const notFoundPath = path.resolve(themeDir, '/NotFound.vue')
    if (fs.existsSync(notFoundPath)) {
      options.notFoundPath = notFoundPath
    } else {
E
Evan You 已提交
106
      options.notFoundPath = path.resolve(__dirname, 'default-theme/NotFound.vue')
E
Evan You 已提交
107
    }
E
tweaks  
Evan You 已提交
108 109
  }

E
Evan You 已提交
110 111
  // resolve pages
  const pagesData = options.pageFiles.map(file => {
E
tweaks  
Evan You 已提交
112
    const data = {
E
Evan You 已提交
113
      path: fileToPath(file)
E
Evan You 已提交
114
    }
E
Evan You 已提交
115 116

    // extract yaml frontmatter
E
Evan You 已提交
117
    const content = fs.readFileSync(path.resolve(sourceDir, file), 'utf-8')
E
Evan You 已提交
118
    const frontmatter = yaml.loadFront(content)
E
Evan You 已提交
119
    // infer title
E
Evan You 已提交
120 121 122
    const title = inferTitle(frontmatter)
    if (title) {
      data.title = title
E
Evan You 已提交
123
    }
E
Evan You 已提交
124 125 126 127
    const headers = extractHeaders(frontmatter.__content, ['h2', 'h3'])
    if (headers.length) {
      data.headers = headers
    }
E
Evan You 已提交
128
    delete frontmatter.__content
E
Evan You 已提交
129 130
    if (Object.keys(frontmatter).length) {
      data.frontmatter = frontmatter
E
Evan You 已提交
131
    }
E
tweaks  
Evan You 已提交
132
    return data
E
Evan You 已提交
133 134
  })

E
Evan You 已提交
135
  // resolve site data
E
Evan You 已提交
136
  options.siteData = {
E
Evan You 已提交
137 138
    title: siteConfig.title || '',
    description: siteConfig.description || '',
E
Evan You 已提交
139
    base: siteConfig.base || '/',
E
Evan You 已提交
140
    pages: pagesData,
E
Evan You 已提交
141
    themeConfig: siteConfig.themeConfig || {}
E
Evan You 已提交
142
  }
E
Evan You 已提交
143 144 145 146

  return options
}

E
Evan You 已提交
147
async function genComponentRegistrationFile ({ sourceDir }) {
E
Evan You 已提交
148
  function genImport (file) {
E
Evan You 已提交
149
    const name = fileToComponentName(file)
E
Evan You 已提交
150
    const baseDir = path.resolve(sourceDir, '.vuepress/components')
E
Evan You 已提交
151 152 153 154 155
    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 已提交
156
  return `import Vue from 'vue'\n` + components.map(genImport).join('\n')
E
Evan You 已提交
157 158
}

E
Evan You 已提交
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
const indexRE = /\breadme\.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}`
E
Evan You 已提交
183 184 185
}

function isIndexFile (file) {
E
Evan You 已提交
186
  return indexRE.test(file)
E
Evan You 已提交
187 188
}

E
Evan You 已提交
189
async function resolveComponents (sourceDir) {
E
Evan You 已提交
190
  const componentDir = path.resolve(sourceDir, '.vuepress/components')
E
Evan You 已提交
191 192 193
  if (!fs.existsSync(componentDir)) {
    return
  }
E
Evan You 已提交
194
  return sort(await globby(['**/*.vue'], { cwd: componentDir }))
E
Evan You 已提交
195 196
}

E
Evan You 已提交
197 198 199 200
async function genRoutesFile ({ siteData: { pages }, sourceDir, pageFiles }) {
  function genRoute ({ path: pagePath }, index) {
    const file = pageFiles[index]
    const filePath = path.resolve(sourceDir, file)
E
Evan You 已提交
201 202
    const code = `
    {
E
Evan You 已提交
203 204 205 206 207 208 209 210
      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 已提交
211 212 213 214
    }`
    return code
  }

E
tweaks  
Evan You 已提交
215
  return (
E
Evan You 已提交
216
    `import Theme from '~theme'\n` +
E
tweaks  
Evan You 已提交
217 218
    `export const routes = [${pages.map(genRoute).join(',')}\n]`
  )
E
Evan You 已提交
219
}
E
Evan You 已提交
220 221 222 223 224 225 226 227

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