index.js 5.8 KB
Newer Older
N
nkzawa 已提交
1
import http from 'http'
N
nkzawa 已提交
2 3
import { resolve, join } from 'path'
import { parse } from 'url'
N
nkzawa 已提交
4 5
import send from 'send'
import Router from './router'
N
nkzawa 已提交
6
import { render, renderJSON, errorToJSON } from './render'
N
nkzawa 已提交
7
import HotReloader from './hot-reloader'
N
nkzawa 已提交
8
import { resolveFromList } from './resolve'
N
nkzawa 已提交
9 10

export default class Server {
N
nkzawa 已提交
11
  constructor ({ dir = '.', dev = false, hotReload = false }) {
N
nkzawa 已提交
12 13
    this.dir = resolve(dir)
    this.dev = dev
14
    this.hotReloader = hotReload ? new HotReloader(this.dir, this.dev) : null
N
nkzawa 已提交
15 16 17
    this.router = new Router()

    this.http = http.createServer((req, res) => {
N
nkzawa 已提交
18 19 20
      this.run(req, res)
      .catch((err) => {
        console.error(err)
N
Naoyuki Kanezawa 已提交
21
        res.statusCode = 500
D
Dan Zajdband 已提交
22
        res.end('error')
N
nkzawa 已提交
23 24
      })
    })
N
nkzawa 已提交
25 26

    this.defineRoutes()
N
nkzawa 已提交
27 28 29
  }

  async start (port) {
N
nkzawa 已提交
30 31 32 33 34 35 36 37 38 39 40 41 42
    if (this.hotReloader) {
      await this.hotReloader.start()
    }

    await new Promise((resolve, reject) => {
      this.http.listen(port, (err) => {
        if (err) return reject(err)
        resolve()
      })
    })
  }

  defineRoutes () {
43 44 45 46 47
    this.router.get('/_next/commons.js', async (req, res, params) => {
      const p = join(this.dir, '.next/commons.js')
      await this.serveStatic(req, res, p)
    })

N
nkzawa 已提交
48
    this.router.get('/_next/:path+', async (req, res, params) => {
N
nkzawa 已提交
49
      const p = join(__dirname, '..', 'client', ...(params.path || []))
N
nkzawa 已提交
50 51 52 53
      await this.serveStatic(req, res, p)
    })

    this.router.get('/static/:path+', async (req, res, params) => {
N
nkzawa 已提交
54
      const p = join(this.dir, 'static', ...(params.path || []))
N
nkzawa 已提交
55
      await this.serveStatic(req, res, p)
N
nkzawa 已提交
56 57
    })

N
nkzawa 已提交
58 59
    this.router.get('/:path+.json', async (req, res) => {
      await this.renderJSON(req, res)
N
nkzawa 已提交
60 61
    })

N
nkzawa 已提交
62 63
    this.router.get('/:path*', async (req, res) => {
      await this.render(req, res)
N
nkzawa 已提交
64 65 66 67
    })
  }

  async run (req, res) {
68 69 70 71
    if (this.hotReloader) {
      await this.hotReloader.run(req, res)
    }

N
nkzawa 已提交
72 73 74 75 76 77 78 79
    const fn = this.router.match(req, res)
    if (fn) {
      await fn()
    } else {
      await this.render404(req, res)
    }
  }

N
nkzawa 已提交
80
  async render (req, res) {
81 82
    const { pathname, query } = parse(req.url, true)
    const ctx = { req, res, pathname, query }
N
nkzawa 已提交
83

N
Naoyuki Kanezawa 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
    const compilationErr = this.getCompilationError(req.url)
    if (compilationErr) {
      await this.doRender(res, 500, '/_error-debug', { ...ctx, err: compilationErr })
      return
    }

    try {
      await this.doRender(res, 200, req.url, ctx)
    } catch (err) {
      const compilationErr2 = this.getCompilationError('/_error')
      if (compilationErr2) {
        await this.doRender(res, 500, '/_error-debug', { ...ctx, err: compilationErr2 })
        return
      }

      if (err.code !== 'ENOENT') {
        console.error(err)
        const url = this.dev ? '/_error-debug' : '/_error'
        await this.doRender(res, 500, url, { ...ctx, err })
        return
      }
N
nkzawa 已提交
105 106

      try {
N
Naoyuki Kanezawa 已提交
107 108 109 110
        await this.doRender(res, 404, '/_error', { ...ctx, err })
      } catch (err2) {
        if (this.dev) {
          await this.doRender(res, 500, '/_error-debug', { ...ctx, err: err2 })
N
Naoyuki Kanezawa 已提交
111
        } else {
N
Naoyuki Kanezawa 已提交
112
          throw err2
N
nkzawa 已提交
113
        }
N
nkzawa 已提交
114 115
      }
    }
N
Naoyuki Kanezawa 已提交
116 117 118 119 120 121 122 123
  }

  async doRender (res, statusCode, url, ctx) {
    const { dir, dev } = this

    // need to set statusCode before `render`
    // since it can be used on getInitialProps
    res.statusCode = statusCode
N
nkzawa 已提交
124

N
Naoyuki Kanezawa 已提交
125
    const html = await render(url, ctx, { dir, dev })
N
nkzawa 已提交
126
    sendHTML(res, html)
N
nkzawa 已提交
127 128
  }

N
nkzawa 已提交
129
  async renderJSON (req, res) {
N
Naoyuki Kanezawa 已提交
130 131 132 133 134
    const compilationErr = this.getCompilationError(req.url)
    if (compilationErr) {
      await this.doRenderJSON(res, 500, '/_error-debug.json', compilationErr)
      return
    }
N
nkzawa 已提交
135

N
Naoyuki Kanezawa 已提交
136 137 138 139 140 141 142 143
    try {
      await this.doRenderJSON(res, 200, req.url)
    } catch (err) {
      const compilationErr2 = this.getCompilationError('/_error.json')
      if (compilationErr2) {
        await this.doRenderJSON(res, 500, '/_error-debug.json', compilationErr2)
        return
      }
N
nkzawa 已提交
144

N
Naoyuki Kanezawa 已提交
145 146 147 148 149
      if (err.code === 'ENOENT') {
        await this.doRenderJSON(res, 404, '/_error.json')
      } else {
        console.error(err)
        await this.doRenderJSON(res, 500, '/_error.json')
N
nkzawa 已提交
150 151
      }
    }
N
Naoyuki Kanezawa 已提交
152 153 154 155 156 157 158 159
  }

  async doRenderJSON (res, statusCode, url, err) {
    const { dir } = this
    const json = await renderJSON(url, { dir })
    if (err) {
      json.err = errorToJSON(err)
    }
N
nkzawa 已提交
160 161

    const data = JSON.stringify(json)
N
nkzawa 已提交
162
    res.setHeader('Content-Type', 'application/json')
N
nkzawa 已提交
163
    res.setHeader('Content-Length', Buffer.byteLength(data))
N
Naoyuki Kanezawa 已提交
164
    res.statusCode = statusCode
N
nkzawa 已提交
165
    res.end(data)
N
nkzawa 已提交
166 167
  }

N
nkzawa 已提交
168
  async render404 (req, res) {
169 170
    const { pathname, query } = parse(req.url, true)
    const ctx = { req, res, pathname, query }
N
Naoyuki Kanezawa 已提交
171

N
Naoyuki Kanezawa 已提交
172 173 174 175
    const compilationErr = this.getCompilationError('/_error')
    if (compilationErr) {
      await this.doRender(res, 500, '/_error-debug', { ...ctx, err: compilationErr })
      return
N
Naoyuki Kanezawa 已提交
176
    }
N
nkzawa 已提交
177

N
Naoyuki Kanezawa 已提交
178 179 180 181 182 183 184 185 186
    try {
      await this.doRender(res, 404, '/_error', ctx)
    } catch (err) {
      if (this.dev) {
        await this.doRender(res, 500, '/_error-debug', { ...ctx, err })
      } else {
        throw err
      }
    }
N
nkzawa 已提交
187 188
  }

N
nkzawa 已提交
189 190
  serveStatic (req, res, path) {
    return new Promise((resolve, reject) => {
N
nkzawa 已提交
191
      send(req, path)
N
nkzawa 已提交
192
      .on('error', (err) => {
D
Dan Zajdband 已提交
193
        if (err.code === 'ENOENT') {
N
nkzawa 已提交
194 195 196 197 198 199 200 201 202
          this.render404(req, res).then(resolve, reject)
        } else {
          reject(err)
        }
      })
      .pipe(res)
      .on('finish', resolve)
    })
  }
N
nkzawa 已提交
203 204 205 206 207 208 209 210 211 212 213 214

  getCompilationError (url) {
    if (!this.hotReloader) return

    const errors = this.hotReloader.getCompilationErrors()
    if (!errors.size) return

    const p = parse(url || '/').pathname.replace(/\.json$/, '')
    const id = join(this.dir, '.next', 'bundles', 'pages', p)
    const path = resolveFromList(id, errors.keys())
    if (path) return errors.get(path)[0]
  }
N
nkzawa 已提交
215
}
N
nkzawa 已提交
216 217 218 219 220 221

function sendHTML (res, html) {
  res.setHeader('Content-Type', 'text/html')
  res.setHeader('Content-Length', Buffer.byteLength(html))
  res.end(html)
}