dev.js 4.4 KB
Newer Older
E
cli  
Evan You 已提交
1
module.exports = async function dev (sourceDir, cliOptions = {}) {
E
Evan You 已提交
2
  const fs = require('fs')
E
wip  
Evan You 已提交
3
  const path = require('path')
E
Evan You 已提交
4 5
  const chalk = require('chalk')
  const webpack = require('webpack')
E
Evan You 已提交
6
  const chokidar = require('chokidar')
E
Evan You 已提交
7 8
  const serve = require('webpack-serve')
  const convert = require('koa-connect')
E
Evan You 已提交
9
  const mount = require('koa-mount')
10
  const range = require('koa-range')
E
Evan You 已提交
11
  const serveStatic = require('koa-static')
E
Evan You 已提交
12 13
  const history = require('connect-history-api-fallback')

E
tweaks  
Evan You 已提交
14
  const prepare = require('./prepare')
U
ULIVZ 已提交
15
  const logger = require('./util/logger')
E
Evan You 已提交
16
  const HeadPlugin = require('./webpack/HeadPlugin')
U
ULIVZ 已提交
17
  const DevLogPlugin = require('./webpack/DevLogPlugin')
E
Evan You 已提交
18 19
  const createClientConfig = require('./webpack/createClientConfig')
  const { applyUserWebpackConfig } = require('./util')
20
  const { frontmatterEmitter } = require('./webpack/markdownLoader')
E
Evan You 已提交
21

U
ULIVZ 已提交
22
  logger.wait('\nExtracting site metadata...')
U
ULIVZ 已提交
23
  const options = await prepare(sourceDir, false /* isProd */)
E
Evan You 已提交
24

E
tweaks  
Evan You 已提交
25
  // setup watchers to update options and dynamically generated files
E
Evan You 已提交
26
  const update = () => {
U
ULIVZ 已提交
27
    options.plugin.hooks.updated.run()
E
Evan You 已提交
28
    prepare(sourceDir).catch(err => {
U
ULIVZ 已提交
29
      console.error(logger.error(chalk.red(err.stack), false))
E
Evan You 已提交
30 31
    })
  }
32 33

  // watch add/remove of files
E
tweaks  
Evan You 已提交
34
  const pagesWatcher = chokidar.watch([
35 36
    '**/*.md',
    '.vuepress/components/**/*.vue'
E
tweaks  
Evan You 已提交
37
  ], {
38
    cwd: sourceDir,
E
Evan You 已提交
39
    ignored: '.vuepress/**/*.md',
E
tweaks  
Evan You 已提交
40 41
    ignoreInitial: true
  })
E
Evan You 已提交
42 43 44 45
  pagesWatcher.on('add', update)
  pagesWatcher.on('unlink', update)
  pagesWatcher.on('addDir', update)
  pagesWatcher.on('unlinkDir', update)
E
tweaks  
Evan You 已提交
46

47 48
  // watch config file
  const configWatcher = chokidar.watch([
49 50 51 52 53 54 55
    '.vuepress/config.js',
    '.vuepress/config.yml',
    '.vuepress/config.toml'
  ], {
    cwd: sourceDir,
    ignoreInitial: true
  })
56 57 58 59 60
  configWatcher.on('change', update)

  // also listen for frontmatter changes from markdown files
  frontmatterEmitter.on('update', update)

E
tweaks  
Evan You 已提交
61
  // resolve webpack config
E
Evan You 已提交
62
  let config = createClientConfig(options, cliOptions)
E
Evan You 已提交
63

E
Evan You 已提交
64
  config
E
Evan You 已提交
65
    .plugin('html')
66 67 68
    // using a fork of html-webpack-plugin to avoid it requiring webpack
    // internals from an incompatible version.
    .use(require('vuepress-html-webpack-plugin'), [{
E
Evan You 已提交
69 70
      template: path.resolve(__dirname, 'app/index.dev.html')
    }])
E
Evan You 已提交
71

E
Evan You 已提交
72
  config
E
Evan You 已提交
73
    .plugin('site-data')
E
Evan You 已提交
74 75 76
    .use(HeadPlugin, [{
      tags: options.siteConfig.head || []
    }])
E
Evan You 已提交
77

U
ULIVZ 已提交
78 79 80 81 82 83 84 85 86 87 88
  const port = await resolvePort(cliOptions.port || options.siteConfig.port)
  const { host, displayHost } = await resolveHost(cliOptions.host || options.siteConfig.host)

  config
  .plugin('vuepress-log')
  .use(DevLogPlugin, [{
    port,
    displayHost,
    publicPath: options.publicPath
  }])

E
Evan You 已提交
89 90 91 92 93 94
  config = config.toConfig()
  const userConfig = options.siteConfig.configureWebpack
  if (userConfig) {
    config = applyUserWebpackConfig(userConfig, config, false /* isServer */)
  }

E
Evan You 已提交
95 96
  const compiler = webpack(config)

97
  const nonExistentDir = path.resolve(__dirname, 'non-existent')
E
Evan You 已提交
98
  await serve({
99 100 101
    // avoid project cwd from being served. Otherwise if the user has index.html
    // in cwd it would break the server
    content: [nonExistentDir],
E
Evan You 已提交
102
    compiler,
103
    host,
104
    dev: { logLevel: 'warn' },
105
    hot: {
106
      port: parseInt(port) + 1,
107 108
      logLevel: 'error'
    },
E
Evan You 已提交
109 110
    logLevel: 'error',
    port,
E
Evan You 已提交
111
    add: app => {
112 113 114 115
      // apply plugin options to extend dev server.
      const { plugin } = options
      plugin.options.enhanceDevServer.run(app)

E
Evan You 已提交
116
      const userPublic = path.resolve(sourceDir, '.vuepress/public')
117 118 119 120

      // enable range request
      app.use(range)

E
Evan You 已提交
121
      // respect base when serving static files...
E
Evan You 已提交
122
      if (fs.existsSync(userPublic)) {
E
Evan You 已提交
123
        app.use(mount(options.publicPath, serveStatic(userPublic)))
E
Evan You 已提交
124 125 126 127 128 129 130 131
      }

      app.use(convert(history({
        rewrites: [
          { from: /\.html$/, to: '/' }
        ]
      })))
    }
E
Evan You 已提交
132 133
  })
}
U
ULIVZ 已提交
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

function resolveHost (host) {
  // webpack-serve hot updates doesn't work properly over 0.0.0.0 on Windows,
  // but localhost does not allow visiting over network :/
  const defaultHost = process.platform === 'win32' ? 'localhost' : '0.0.0.0'
  host = host || defaultHost
  const displayHost = host === defaultHost && process.platform !== 'win32'
    ? 'localhost'
    : host
  return {
    displayHost,
    host
  }
}

async function resolvePort (port) {
  const portfinder = require('portfinder')
  portfinder.basePort = port || 8080
  port = await portfinder.getPortPromise()
  return port
}