Skip to content

Commit

Permalink
Add support for hot reloading API endpoints under a new /api folder…
Browse files Browse the repository at this point in the history
…; scripts must contain a default export of the form: `({ req, res, params }, { dir, dev }) => Object`
  • Loading branch information
jmfirth committed Oct 26, 2016
1 parent 3d44b58 commit c42b562
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 8 deletions.
15 changes: 8 additions & 7 deletions server/build/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import DynamicEntryPlugin from './plugins/dynamic-entry-plugin'
export default async function createCompiler (dir, { hotReload = false } = {}) {
dir = resolve(dir)

const pages = await glob('pages/**/*.js', { cwd: dir })
const entries = await glob('+(pages|api)/**/*.js', { cwd: dir })

const entry = {}
const defaultEntries = hotReload ? ['webpack/hot/dev-server'] : []
for (const p of pages) {
for (const p of entries) {
entry[join('bundles', p)] = defaultEntries.concat(['./' + p])
}

const nextPagesDir = join(__dirname, '..', '..', 'pages')
const nextApiDir = join(__dirname, '..', '..', 'api')

const errorEntry = join('bundles', 'pages', '_error.js')
const defaultErrorPath = join(nextPagesDir, '_error.js')
Expand Down Expand Up @@ -56,9 +57,9 @@ export default async function createCompiler (dir, { hotReload = false } = {}) {
const loaders = [{
test: /\.js$/,
loader: 'emit-file-loader',
include: [dir, nextPagesDir],
include: [dir, nextPagesDir, nextApiDir],
exclude (str) {
return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0
return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0 && str.indexOf(nextApiDir) !== 0
},
query: {
name: 'dist/[path][name].[ext]'
Expand All @@ -67,14 +68,14 @@ export default async function createCompiler (dir, { hotReload = false } = {}) {
.concat(hotReload ? [{
test: /\.js$/,
loader: 'hot-self-accept-loader',
include: join(dir, 'pages')
include: [join(dir, 'pages'), join(dir, 'api')]
}] : [])
.concat([{
test: /\.js$/,
loader: 'babel',
include: [dir, nextPagesDir],
include: [dir, nextPagesDir, nextApiDir],
exclude (str) {
return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0
return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0 && str.indexOf(nextApiDir) !== 0
},
query: {
presets: ['es2015', 'react'],
Expand Down
15 changes: 14 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { resolve, join } from 'path'
import { parse } from 'url'
import send from 'send'
import Router from './router'
import { render, renderJSON, errorToJSON } from './render'
import { render, renderAPI, renderJSON, errorToJSON } from './render'
import HotReloader from './hot-reloader'
import { resolveFromList } from './resolve'

Expand Down Expand Up @@ -50,6 +50,10 @@ export default class Server {
await this.serveStatic(req, res, p)
})

this.router.get('/api/:path+', async (req, res, params) => {
await this.renderAPI(req, res, params)
})

this.router.get('/:path+.json', async (req, res) => {
await this.renderJSON(req, res)
})
Expand Down Expand Up @@ -96,6 +100,15 @@ export default class Server {
sendHTML(res, html)
}

async renderAPI (req, res, params) {
const { dir, dev } = this
const data = await renderAPI(req.url, { req, res, params }, { dir, dev })
const json = JSON.stringify(data)
res.setHeader('Content-Type', 'application/json')
res.setHeader('Content-Length', Buffer.byteLength(json))
res.end(json)
}

async renderJSON (req, res) {
const { dir } = this
const opts = { dir }
Expand Down
24 changes: 24 additions & 0 deletions server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,30 @@ export async function render (url, ctx = {}, {
return '<!DOCTYPE html>' + renderToStaticMarkup(doc)
}

export async function renderAPI (url, { req, res, params }, {
dir = process.cwd(),
dev = false
} = {}) {
try {
const mod = await requireModule(join(dir, '.next', 'dist', getPath(url)))
const fn = mod.default

if (!fn) {
throw new Error('Endpoint is malformed; `export default function ({req, res, params}, {dir, dev}) { /* ... */ }`')
}

return {
success: true,
body: fn({ req, res, params }, { dir, dev })
}
} catch (e) {
return {
error: true,
message: dev ? e.message : 'Internal server error'
}
}
}

export async function renderJSON (url, { dir = process.cwd() } = {}) {
const path = getPath(url)
const component = await read(join(dir, '.next', 'bundles', 'pages', path))
Expand Down

1 comment on commit c42b562

@malixsys
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it!
You should update for the new parameters like query

Please sign in to comment.