next-serverless-loader.ts 6.0 KB
Newer Older
1
import { loader } from 'webpack'
2 3
import { join } from 'path'
import { parse } from 'querystring'
4 5 6 7 8
import {
  BUILD_MANIFEST,
  REACT_LOADABLE_MANIFEST,
} from '../../../next-server/lib/constants'
import { isDynamicRoute } from '../../../next-server/lib/router/utils'
9
import { API_ROUTE } from '../../../lib/constants'
T
Tim Neutkens 已提交
10 11

export type ServerlessLoaderQuery = {
12 13 14 15 16 17
  page: string
  distDir: string
  absolutePagePath: string
  absoluteAppPath: string
  absoluteDocumentPath: string
  absoluteErrorPath: string
18
  buildId: string
19 20
  assetPrefix: string
  ampBindInitData: boolean | string
T
Tim Neutkens 已提交
21
  generateEtags: string
22
  canonicalBase: string
T
Tim Neutkens 已提交
23 24
}

25
const nextServerlessLoader: loader.Loader = function() {
T
Tim Neutkens 已提交
26 27 28 29
  const {
    distDir,
    absolutePagePath,
    page,
30
    buildId,
31
    canonicalBase,
T
Tim Neutkens 已提交
32
    assetPrefix,
J
JJ Kasper 已提交
33
    ampBindInitData,
T
Tim Neutkens 已提交
34 35 36
    absoluteAppPath,
    absoluteDocumentPath,
    absoluteErrorPath,
37
    generateEtags,
38 39
  }: ServerlessLoaderQuery =
    typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query
T
Tim Neutkens 已提交
40
  const buildManifest = join(distDir, BUILD_MANIFEST).replace(/\\/g, '/')
41 42 43 44
  const reactLoadableManifest = join(distDir, REACT_LOADABLE_MANIFEST).replace(
    /\\/g,
    '/'
  )
J
JJ Kasper 已提交
45
  const escapedBuildId = buildId.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&')
46

47
  if (page.match(API_ROUTE)) {
48 49 50 51
    return `
    ${
      isDynamicRoute(page)
        ? `
52 53
      import { getRouteMatcher } from 'next/dist/next-server/lib/router/utils/route-matcher';
      import { getRouteRegex } from 'next/dist/next-server/lib/router/utils/route-regex';
54 55 56
      `
        : ``
    }
57
      import { parse } from 'url'
58
      import { apiResolver } from 'next/dist/next-server/server/api-utils'
59

60 61 62
      export default (req, res) => {
        const params = ${
          isDynamicRoute(page)
63
            ? `getRouteMatcher(getRouteRegex('${page}'))(parse(req.url).pathname)`
64 65 66 67 68 69 70 71
            : `{}`
        }
        const resolver = require('${absolutePagePath}')
        apiResolver(req, res, params, resolver)
      }
    `
  } else {
    return `
T
Tim Neutkens 已提交
72
    import {parse} from 'url'
73 74
    import {renderToHTML} from 'next/dist/next-server/server/render';
    import {sendHTML} from 'next/dist/next-server/server/send-html';
J
Joe Haddad 已提交
75
    ${
76
      isDynamicRoute(page)
77
        ? `import {getRouteMatcher, getRouteRegex} from 'next/dist/next-server/lib/router/utils';`
J
Joe Haddad 已提交
78 79
        : ''
    }
T
Tim Neutkens 已提交
80 81 82 83 84
    import buildManifest from '${buildManifest}';
    import reactLoadableManifest from '${reactLoadableManifest}';
    import Document from '${absoluteDocumentPath}';
    import Error from '${absoluteErrorPath}';
    import App from '${absoluteAppPath}';
85 86
    import * as ComponentInfo from '${absolutePagePath}';
    const Component = ComponentInfo.default
J
JJ Kasper 已提交
87
    export default Component
J
JJ Kasper 已提交
88 89 90 91 92 93
    export const unstable_getStaticProps = ComponentInfo['unstable_getStaticProp' + 's']
    ${
      isDynamicRoute(page)
        ? "export const unstable_getStaticParams = ComponentInfo['unstable_getStaticParam' + 's']"
        : ''
    }
94
    export const config = ComponentInfo['confi' + 'g'] || {}
J
JJ Kasper 已提交
95 96
    export const _app = App
    export async function renderReqToHTML(req, res, fromExport) {
T
Tim Neutkens 已提交
97 98 99 100
      const options = {
        App,
        Document,
        buildManifest,
J
JJ Kasper 已提交
101
        unstable_getStaticProps,
T
Tim Neutkens 已提交
102
        reactLoadableManifest,
103
        canonicalBase: "${canonicalBase}",
104
        buildId: "${buildId}",
J
JJ Kasper 已提交
105
        assetPrefix: "${assetPrefix}",
106
        ampBindInitData: ${ampBindInitData === true ||
J
JJ Kasper 已提交
107 108 109 110 111 112 113
          ampBindInitData === 'true'},
      }
      let sprData = false

      if (req.url.match(/_next\\/data/)) {
        sprData = true
        req.url = req.url
J
JJ Kasper 已提交
114
          .replace(new RegExp('/_next/data/${escapedBuildId}/'), '/')
J
JJ Kasper 已提交
115
          .replace(/\\.json$/, '')
T
Tim Neutkens 已提交
116 117
      }
      const parsedUrl = parse(req.url, true)
J
JJ Kasper 已提交
118 119 120
      const renderOpts = Object.assign(
        {
          Component,
121
          pageConfig: config,
J
JJ Kasper 已提交
122
          dataOnly: req.headers && (req.headers.accept || '').indexOf('application/amp.bind+json') !== -1,
123
          nextExport: fromExport
J
JJ Kasper 已提交
124 125 126
        },
        options,
      )
T
Tim Neutkens 已提交
127 128
      try {
        ${page === '/_error' ? `res.statusCode = 404` : ''}
J
Joe Haddad 已提交
129
        ${
130
          isDynamicRoute(page)
J
JJ Kasper 已提交
131
            ? `const params = fromExport && !unstable_getStaticProps ? {} : getRouteMatcher(getRouteRegex("${page}"))(parsedUrl.pathname) || {};`
J
Joe Haddad 已提交
132 133
            : `const params = {};`
        }
134 135 136 137 138 139 140 141 142 143 144
        ${
          // Temporary work around -- `x-now-route-params` is a platform header
          // _only_ set for `Prerender` requests. We should move this logic
          // into our builder to ensure we're decoupled. However, this entails
          // removing reliance on `req.url` and using `req.query` instead
          // (which is needed for "custom routes" anyway).
          isDynamicRoute(page)
            ? `const nowParams = (req.headers && req.headers["x-now-route-params"]) ? querystring.parse(req.headers["x-now-route-params"]) : null;`
            : `const nowParams = null;`
        }
        const result = await renderToHTML(req, res, "${page}", Object.assign({}, unstable_getStaticProps ? {} : parsedUrl.query, nowParams ? nowParams : params, sprData ? { _nextSprData: '1' } : {}), renderOpts)
J
JJ Kasper 已提交
145 146

        if (fromExport) return { html: result, renderOpts }
T
Tim Neutkens 已提交
147 148 149 150
        return result
      } catch (err) {
        if (err.code === 'ENOENT') {
          res.statusCode = 404
151
          const result = await renderToHTML(req, res, "/_error", parsedUrl.query, Object.assign({}, options, {
T
Tim Neutkens 已提交
152
            Component: Error
153
          }))
T
Tim Neutkens 已提交
154 155 156 157
          return result
        } else {
          console.error(err)
          res.statusCode = 500
158
          const result = await renderToHTML(req, res, "/_error", parsedUrl.query, Object.assign({}, options, {
T
Tim Neutkens 已提交
159 160
            Component: Error,
            err
161
          }))
T
Tim Neutkens 已提交
162 163 164 165
          return result
        }
      }
    }
166
    export async function render (req, res) {
T
Tim Neutkens 已提交
167
      try {
168
        const html = await renderReqToHTML(req, res)
T
Tim Neutkens 已提交
169 170 171 172 173 174 175 176
        sendHTML(req, res, html, {generateEtags: ${generateEtags}})
      } catch(err) {
        console.error(err)
        res.statusCode = 500
        res.end('Internal Server Error')
      }
    }
  `
177
  }
T
Tim Neutkens 已提交
178 179 180
}

export default nextServerlessLoader