1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Merge branch 'next-export' into v3-beta

This commit is contained in:
Arunoda Susiripala 2017-05-15 09:32:39 +05:30
commit d4c62b1bea
27 changed files with 1202 additions and 387 deletions

4
.gitignore vendored
View file

@ -6,10 +6,12 @@ dist
node_modules
# logs
npm-debug.log
*.log
# coverage
.nyc_output
coverage
# test output
test/**/out
.DS_Store

View file

@ -22,6 +22,7 @@ const commands = new Set([
'init',
'build',
'start',
'export',
defaultCommand
])

67
bin/next-export Normal file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env node
import { resolve, join } from 'path'
import { existsSync } from 'fs'
import parseArgs from 'minimist'
import exportApp from '../server/export'
import { printAndExit } from '../lib/utils'
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
s: 'silent',
o: 'outdir'
},
boolean: ['h'],
default: {
s: false,
o: null
}
})
if (argv.help) {
console.log(`
Description
Exports the application for production deployment
Usage
$ next export [options] <dir>
<dir> represents where the compiled dist folder should go.
If no directory is provided, the dist folder will be created in the current directory.
You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration, otherwise it will be created inside '.next'
Options
-h - list this help
-o - set the output dir (defaults to 'out')
-s - do not print any messages to console
`)
process.exit(0)
}
const dir = resolve(argv._[0] || '.')
// Check if pages dir exists and warn if not
if (!existsSync(dir)) {
printAndExit(`> No such directory exists as the project root: ${dir}`)
}
if (!existsSync(join(dir, 'pages'))) {
if (existsSync(join(dir, '..', 'pages'))) {
printAndExit('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?')
}
printAndExit('> Couldn\'t find a `pages` directory. Please create one under the project root')
}
const options = {
silent: argv.silent,
outdir: argv.outdir ? resolve(argv.outdir) : resolve(dir, 'out')
}
exportApp(dir, options)
.catch((err) => {
console.error(err)
process.exit(1)
})

View file

@ -1,7 +1,9 @@
/* global __NEXT_DATA__ */
import { resolve, format, parse } from 'url'
import React, { Component, Children } from 'react'
import PropTypes from 'prop-types'
import Router from './router'
import Router, { _rewriteUrlForNextExport } from './router'
import { warn, execOnce, getLocationOrigin } from './utils'
export default class Link extends Component {
@ -122,6 +124,15 @@ export default class Link extends Component {
props.href = as || href
}
// Add the ending slash to the paths. So, we can serve the
// "<page>/index.html" directly.
if (
typeof __NEXT_DATA__ !== 'undefined' &&
__NEXT_DATA__.nextExport
) {
props.href = _rewriteUrlForNextExport(props.href)
}
return React.cloneElement(child, props)
}
}

View file

@ -1,4 +1,4 @@
/* global window, document */
/* global window, document, __NEXT_DATA__ */
import mitt from 'mitt'
const webpackModule = module
@ -48,6 +48,12 @@ export default class PageLoader {
this.registerEvents.on(route, fire)
// If the page is loading via SSR, we need to wait for it
// rather downloading it again.
if (document.getElementById(`__NEXT_PAGE__${route}`)) {
return
}
// Load the script if not asked to load yet.
if (!this.loadingRoutes[route]) {
this.loadScript(route)
@ -59,6 +65,10 @@ export default class PageLoader {
loadScript (route) {
route = this.normalizeRoute(route)
if (__NEXT_DATA__.nextExport) {
route = route === '/' ? '/index.js' : `${route}/index.js`
}
const script = document.createElement('script')
const url = `${this.assetPrefix}/_next/${encodeURIComponent(this.buildId)}/page${route}`
script.src = url
@ -100,5 +110,10 @@ export default class PageLoader {
route = this.normalizeRoute(route)
delete this.pageCache[route]
delete this.loadingRoutes[route]
const script = document.getElementById(`__NEXT_PAGE__${route}`)
if (script) {
script.parentNode.removeChild(script)
}
}
}

View file

@ -85,3 +85,23 @@ export function _notifyBuildIdMismatch (nextRoute) {
window.location.href = nextRoute
}
}
export function _rewriteUrlForNextExport (url) {
// If there are no query strings
if (!/\?/.test(url)) {
return rewritePath(url)
}
const [path, qs] = url.split('?')
const newPath = rewritePath(path)
return `${newPath}?${qs}`
function rewritePath (path) {
// If ends with slash simply return that path
if (/\/$/.test(path)) {
return path
}
return `${path}/`
}
}

View file

@ -1,9 +1,11 @@
/* global __NEXT_DATA__ */
import { parse, format } from 'url'
import mitt from 'mitt'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL } from '../utils'
import { _notifyBuildIdMismatch } from './'
import { _notifyBuildIdMismatch, _rewriteUrlForNextExport } from './'
export default class Router {
constructor (pathname, query, as, { pageLoader, Component, ErrorComponent, err } = {}) {
@ -118,7 +120,13 @@ export default class Router {
// If url and as provided as an object representation,
// we'll format them into the string version here.
const url = typeof _url === 'object' ? format(_url) : _url
const as = typeof _as === 'object' ? format(_as) : _as
let as = typeof _as === 'object' ? format(_as) : _as
// Add the ending slash to the paths. So, we can serve the
// "<page>/index.html" directly for the SSR page.
if (__NEXT_DATA__.nextExport) {
as = _rewriteUrlForNextExport(as)
}
this.abortComponentLoad(as)
const { pathname, query } = parse(url, true)

View file

@ -104,6 +104,8 @@
"cheerio": "0.22.0",
"chromedriver": "2.29.0",
"coveralls": "2.13.1",
"cross-env": "4.0.0",
"express": "4.15.2",
"cross-env": "5.0.0",
"fly": "2.0.6",
"fly-babel": "2.1.1",
@ -118,7 +120,9 @@
"nyc": "10.3.2",
"react": "15.5.3",
"react-dom": "15.5.3",
"recursive-copy": "^2.0.6",
"standard": "9.0.2",
"walk": "^2.3.9",
"wd": "1.2.0"
},
"peerDependencies": {

View file

@ -39,6 +39,7 @@ Next.js is a minimalistic framework for server-rendered React applications.
- [Customizing babel config](#customizing-babel-config)
- [CDN support with Asset Prefix](#cdn-support-with-asset-prefix)
- [Production deployment](#production-deployment)
- [Static HTML export](#static-html-export)
- [Recipes](#recipes)
- [FAQ](#faq)
- [Contributing](#contributing)
@ -767,6 +768,75 @@ Next.js can be deployed to other hosting solutions too. Please have a look at th
Note: we recommend putting `.next`, or your custom dist folder (Please have a look at ['Custom Config'](You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration.)), in `.npmignore` or `.gitignore`. Otherwise, use `files` or `now.files` to opt-into a whitelist of files you want to deploy (and obviously exclude `.next` or your custom dist folder)
## Static HTML export
This is a way to run your Next.js app as a standalone static app without any Node.js server. The export app supports almost every feature of Next.js including dyanmic urls, prefetching, preloading and dynamic imports.
### Usage
Simply develop your app as you normally do with Next.js. Then create a custom Next.js [config](https://github.com/zeit/next.js#custom-configuration) as shown below:
```js
// next.config.js
module.exports = {
exportPathMap: function () {
return {
"/": { page: "/" },
"/about": { page: "/about" },
"/p/hello-nextjs": { page: "/post", query: { title: "hello-nextjs" } },
"/p/learn-nextjs": { page: "/post", query: { title: "learn-nextjs" } },
"/p/deploy-nextjs": { page: "/post", query: { title: "deploy-nextjs" } }
}
},
}
```
In that, you specify what are the pages you need to export as static HTML.
Then simply run these commands:
```sh
next build
next export
```
For that you may need to add a NPM script to `package.json` like this:
```json
{
"scripts": {
"build": "next build && next export"
}
}
```
And run it at once with:
```sh
npm run build
```
Then you've a static version of your app in the “out" directory.
> You can also customize the output directory. For that run `next export -h` for the help.
Now you can deploy that directory to any static hosting service.
For an example, simply visit the “out” directory and run following command to deploy your app to [ZEIT now](https://zeit.co/now).
```sh
now
```
### Limitation
With next export, we build HTML version of your app when you run the command `next export`. In that time, we'll run the `getInitialProps` functions of your pages.
So, you could only use `pathname`, `query` and `asPath` fields of the `context` object passed to `getInitialProps`. You can't use `req` or `res` fields.
> Basically, you won't be able to render HTML content dynamically as we pre-build HTML files. If you need that, you need run your app with `next start`.
## Recipes
- [Setting up 301 redirects](https://www.raygesualdo.com/posts/301-redirects-with-nextjs/)

View file

@ -74,6 +74,7 @@ export default async function createCompiler (dir, { dev = false, quiet = false,
}
const plugins = [
new webpack.IgnorePlugin(/(precomputed)/, /node_modules.+(elliptic)/),
new webpack.LoaderOptionsPlugin({
options: {
context: dir,

View file

@ -67,11 +67,12 @@ export class Head extends Component {
render () {
const { head, styles, __NEXT_DATA__ } = this.context._documentProps
const { pathname, buildId, assetPrefix } = __NEXT_DATA__
const { pathname, buildId, assetPrefix, nextExport } = __NEXT_DATA__
const pagePathname = getPagePathname(pathname, nextExport)
return <head>
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page${pathname}`} as='script' />
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error`} as='script' />
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} as='script' />
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error/index.js`} as='script' />
{this.getPreloadMainLinks()}
{(head || []).map((h, i) => React.cloneElement(h, { key: i }))}
{styles || null}
@ -133,7 +134,8 @@ export class NextScript extends Component {
render () {
const { staticMarkup, __NEXT_DATA__ } = this.context._documentProps
const { pathname, buildId, assetPrefix } = __NEXT_DATA__
const { pathname, nextExport, buildId, assetPrefix } = __NEXT_DATA__
const pagePathname = getPagePathname(pathname, nextExport)
return <div>
{staticMarkup ? null : <script dangerouslySetInnerHTML={{
@ -147,9 +149,16 @@ export class NextScript extends Component {
}
`
}} />}
<script async type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page${pathname}`} />
<script async type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page/_error`} />
<script async id={`__NEXT_PAGE__${pathname}`} type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} />
<script async id={`__NEXT_PAGE__/_error`} type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page/_error/index.js`} />
{staticMarkup ? null : this.getScripts()}
</div>
}
}
function getPagePathname (pathname, nextExport) {
if (!nextExport) return pathname
if (pathname === '/') return '/index.js'
return `${pathname}/index.js`
}

136
server/export.js Normal file
View file

@ -0,0 +1,136 @@
import del from 'del'
import cp from 'recursive-copy'
import mkdirp from 'mkdirp-then'
import walk from 'walk'
import { resolve, join, dirname, sep } from 'path'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import getConfig from './config'
import { renderToHTML } from './render'
import { printAndExit } from '../lib/utils'
export default async function (dir, options) {
dir = resolve(dir)
const outDir = options.outdir
const nextDir = join(dir, '.next')
log(` Exporting to: ${outDir}\n`)
if (!existsSync(nextDir)) {
console.error('Build your with "next build" before running "next start".')
process.exit(1)
}
const config = getConfig(dir)
const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8')
const buildStats = require(join(nextDir, 'build-stats.json'))
// Initialize the output directory
await del(outDir)
await mkdirp(join(outDir, '_next', buildStats['app.js'].hash))
await mkdirp(join(outDir, '_next', buildId))
// Copy files
await cp(
join(nextDir, 'app.js'),
join(outDir, '_next', buildStats['app.js'].hash, 'app.js')
)
// Copy static directory
if (existsSync(join(dir, 'static'))) {
log(' copying "static" directory')
await cp(
join(dir, 'static'),
join(outDir, 'static')
)
}
await copyPages(nextDir, outDir, buildId)
// Get the exportPathMap from the `next.config.js`
if (typeof config.exportPathMap !== 'function') {
printAndExit(
'> Could not found "exportPathMap" function inside "next.config.js"\n' +
'> "next export" uses that function build html pages.'
)
}
const exportPathMap = await config.exportPathMap()
const exportPaths = Object.keys(exportPathMap)
// Start the rendering process
const renderOpts = {
dir,
buildStats,
buildId,
nextExport: true,
assetPrefix: config.assetPrefix.replace(/\/$/, ''),
dev: false,
staticMarkup: false,
hotReloader: null
}
// We need this for server rendering the Link component.
global.__NEXT_DATA__ = {
nextExport: true
}
for (const path of exportPaths) {
log(` exporing path: ${path}`)
const { page, query } = exportPathMap[path]
const req = { url: path }
const res = {}
const htmlFilename = path === '/' ? 'index.html' : `${path}${sep}index.html`
const baseDir = join(outDir, dirname(htmlFilename))
const htmlFilepath = join(outDir, htmlFilename)
await mkdirp(baseDir)
const html = await renderToHTML(req, res, page, query, renderOpts)
writeFileSync(htmlFilepath, html, 'utf8')
}
// Add an empty line to the console for the better readability.
log('')
function log (message) {
if (options.silent) return
console.log(message)
}
}
function copyPages (nextDir, outDir, buildId) {
// TODO: do some proper error handling
return new Promise((resolve, reject) => {
const nextBundlesDir = join(nextDir, 'bundles', 'pages')
const walker = walk.walk(nextBundlesDir, { followLinks: false })
walker.on('file', (root, stat, next) => {
const filename = stat.name
const fullFilePath = `${root}${sep}${filename}`
const relativeFilePath = fullFilePath.replace(nextBundlesDir, '')
// We should not expose this page to the client side since
// it has no use in the client side.
if (relativeFilePath === '/_document.js') {
next()
return
}
let destFilePath = null
if (/index\.js$/.test(filename)) {
destFilePath = join(outDir, '_next', buildId, 'page', relativeFilePath)
} else {
const newRelativeFilePath = relativeFilePath.replace(/\.js/, `${sep}index.js`)
destFilePath = join(outDir, '_next', buildId, 'page', newRelativeFilePath)
}
cp(fullFilePath, destFilePath)
.then(next)
.catch(reject)
})
walker.on('end', resolve)
})
}

View file

@ -123,7 +123,7 @@ export default class Server {
await this.serveStatic(req, res, p)
},
'/_next/:buildId/page/_error': async (req, res, params) => {
'/_next/:buildId/page/_error*': async (req, res, params) => {
if (!this.handleBuildId(params.buildId, res)) {
const error = new Error('INVALID_BUILD_ID')
const customFields = { buildIdMismatched: true }

View file

@ -40,7 +40,8 @@ async function doRender (req, res, pathname, query, {
assetPrefix,
dir = process.cwd(),
dev = false,
staticMarkup = false
staticMarkup = false,
nextExport = false
} = {}) {
page = page || pathname
@ -98,9 +99,11 @@ async function doRender (req, res, pathname, query, {
buildId,
buildStats,
assetPrefix,
nextExport,
err: (err) ? serializeError(dev, err) : null
},
dev,
dir,
staticMarkup,
...docProps
})

View file

@ -0,0 +1,12 @@
module.exports = {
exportPathMap: function () {
return {
'/': { page: '/' },
'/about': { page: '/about' },
'/counter': { page: '/counter' },
'/dynamic': { page: '/dynamic', query: { text: 'cool dynamic text' } },
'/dynamic/one': { page: '/dynamic', query: { text: 'next export is nice' } },
'/dynamic/two': { page: '/dynamic', query: { text: 'zeit is awesome' } }
}
}
}

View file

@ -0,0 +1,12 @@
import Link from 'next/link'
export default () => (
<div id='about-page'>
<div>
<Link href='/'>
<a>Go Back</a>
</Link>
</div>
<p>This is the About page</p>
</div>
)

View file

@ -0,0 +1,30 @@
import React from 'react'
import Link from 'next/link'
let counter = 0
export default class Counter extends React.Component {
increaseCounter () {
counter++
this.forceUpdate()
}
render () {
return (
<div id='counter-page'>
<div>
<Link href='/'>
<a id='go-back'>Go Back</a>
</Link>
</div>
<p>Counter: {counter}</p>
<button
id='counter-increase'
onClick={() => this.increaseCounter()}
>
Increase
</button>
</div>
)
}
}

View file

@ -0,0 +1,18 @@
import Link from 'next/link'
const DynamicPage = ({ text }) => (
<div id='dynamic-page'>
<div>
<Link href='/'>
<a>Go Back</a>
</Link>
</div>
<p>{ text }</p>
</div>
)
DynamicPage.getInitialProps = ({ query }) => {
return { text: query.text }
}
export default DynamicPage

View file

@ -0,0 +1,56 @@
import Link from 'next/link'
import Router from 'next/router'
function routeToAbout (e) {
e.preventDefault()
Router.push('/about')
}
export default () => (
<div id='home-page'>
<div>
<Link href='/about'>
<a id='about-via-link'>About via Link</a>
</Link>
<a
href='#'
onClick={routeToAbout}
id='about-via-router'
>
About via Router
</a>
<Link href='/counter'>
<a id='counter'>Counter</a>
</Link>
<Link
href='/dynamic?text=cool+dynamic+text'
>
<a id='get-initial-props'>getInitialProps</a>
</Link>
<Link
href='/dynamic?text=next+export+is+nice'
as='/dynamic/one'
>
<a id='dynamic-1'>Dynamic 1</a>
</Link>
<Link
href='/dynamic?text=zeit+is+awesome'
as='/dynamic/two'
>
<a id='dynamic-2'>Dynamic 2</a>
</Link>
<Link href='/level1'>
<a id='level1-home-page'>Level1 home page</a>
</Link>
<Link href='/level1/about'>
<a id='level1-about-page'>Level1 about page</a>
</Link>
</div>
<p>This is the home page</p>
<style jsx>{`
a {
margin: 0 10px 0 0;
}
`}</style>
</div>
)

View file

@ -0,0 +1,12 @@
import Link from 'next/link'
export default () => (
<div id='level1-about-page'>
<div>
<Link href='/'>
<a>Go Back</a>
</Link>
</div>
<p>This is the Level1 about page</p>
</div>
)

View file

@ -0,0 +1,12 @@
import Link from 'next/link'
export default () => (
<div id='level1-home-page'>
<div>
<Link href='/'>
<a>Go Back</a>
</Link>
</div>
<p>This is the Level1 home page</p>
</div>
)

View file

@ -0,0 +1,120 @@
/* global describe, it, expect */
import webdriver from 'next-webdriver'
export default function (context) {
describe('Render via browser', () => {
it('should render the home page', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#home-page p').text()
expect(text).toBe('This is the home page')
browser.close()
})
it('should do navigations via Link', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#about-via-link').click()
.waitForElementByCss('#about-page')
.elementByCss('#about-page p').text()
expect(text).toBe('This is the About page')
browser.close()
})
it('should do navigations via Router', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#about-via-router').click()
.waitForElementByCss('#about-page')
.elementByCss('#about-page p').text()
expect(text).toBe('This is the About page')
browser.close()
})
it('should do run client side javascript', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#counter').click()
.waitForElementByCss('#counter-page')
.elementByCss('#counter-increase').click()
.elementByCss('#counter-increase').click()
.elementByCss('#counter-page p').text()
expect(text).toBe('Counter: 2')
browser.close()
})
it('should render pages using getInitialProps', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#get-initial-props').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
expect(text).toBe('cool dynamic text')
browser.close()
})
it('should render dynamic pages with custom urls', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#dynamic-1').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
expect(text).toBe('next export is nice')
browser.close()
})
it('should support client side naviagtion', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#counter').click()
.waitForElementByCss('#counter-page')
.elementByCss('#counter-increase').click()
.elementByCss('#counter-increase').click()
.elementByCss('#counter-page p').text()
expect(text).toBe('Counter: 2')
// let's go back and come again to this page:
const textNow = await browser
.elementByCss('#go-back').click()
.waitForElementByCss('#home-page')
.elementByCss('#counter').click()
.waitForElementByCss('#counter-page')
.elementByCss('#counter-page p').text()
expect(textNow).toBe('Counter: 2')
browser.close()
})
describe('pages in the nested level: level1', () => {
it('should render the home page', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#level1-home-page').click()
.waitForElementByCss('#level1-home-page')
.elementByCss('#level1-home-page p').text()
expect(text).toBe('This is the Level1 home page')
browser.close()
})
it('should render the about page', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#level1-about-page').click()
.waitForElementByCss('#level1-about-page')
.elementByCss('#level1-about-page p').text()
expect(text).toBe('This is the Level1 about page')
browser.close()
})
})
})
}

View file

@ -0,0 +1,31 @@
/* global jasmine, describe, beforeAll, afterAll */
import { join } from 'path'
import {
nextBuild,
nextExport,
startStaticServer,
stopApp
} from 'next-test-utils'
import ssr from './ssr'
import browser from './browser'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 40000
const appDir = join(__dirname, '../')
const context = {}
describe('Static Export', () => {
beforeAll(async () => {
const outdir = join(appDir, 'out')
await nextBuild(appDir)
await nextExport(appDir, { outdir })
context.server = await startStaticServer(join(appDir, 'out'))
context.port = context.server.address().port
})
afterAll(() => stopApp(context.server))
ssr(context)
browser(context)
})

View file

@ -0,0 +1,21 @@
/* global describe, it, expect */
import { renderViaHTTP } from 'next-test-utils'
export default function (context) {
describe('Render via SSR', () => {
it('should render the home page', async () => {
const html = await renderViaHTTP(context.port, '/')
expect(html).toMatch(/This is the home page/)
})
it('should render a page with getInitialProps', async() => {
const html = await renderViaHTTP(context.port, '/dynamic')
expect(html).toMatch(/cool dynamic text/)
})
it('should render a dynamically rendered custom url page', async() => {
const html = await renderViaHTTP(context.port, '/dynamic/one')
expect(html).toMatch(/next export is nice/)
})
})
}

View file

@ -1,13 +1,16 @@
import fetch from 'node-fetch'
import qs from 'querystring'
import http from 'http'
import express from 'express'
import server from '../../dist/server/next'
import build from '../../dist/server/build'
import _export from '../../dist/server/export'
import _pkg from '../../package.json'
export const nextServer = server
export const nextBuild = build
export const nextExport = _export
export const pkg = _pkg
export function renderViaAPI (app, pathname, query = {}) {
@ -31,7 +34,9 @@ export async function startApp (app) {
}
export async function stopApp (app) {
await server.__app.close()
if (server.__app) {
await server.__app.close()
}
await promiseCall(server, 'close')
}
@ -52,3 +57,12 @@ function promiseCall (obj, method, ...args) {
export function waitFor (millis) {
return new Promise((resolve) => setTimeout(resolve, millis))
}
export async function startStaticServer (dir) {
const app = express()
const server = http.createServer(app)
app.use(express.static(dir))
await promiseCall(server, 'listen')
return server
}

View file

@ -20,6 +20,7 @@ describe('Router', () => {
const request = { clone: () => null }
describe('.prefetch()', () => {
it('should prefetch a given page', async () => {
global.__NEXT_DATA__ = {}
const pageLoader = new PageLoader()
const router = new Router('/', {}, '/', { pageLoader })
const route = '/routex'
@ -29,6 +30,7 @@ describe('Router', () => {
})
it('should only run two jobs at a time', async () => {
global.__NEXT_DATA__ = {}
// delay loading pages for an hour
const pageLoader = new PageLoader({ delay: 1000 * 3600 })
const router = new Router('/', {}, '/', { pageLoader })
@ -46,6 +48,7 @@ describe('Router', () => {
})
it('should run all the jobs', async () => {
global.__NEXT_DATA__ = {}
const pageLoader = new PageLoader()
const router = new Router('/', {}, '/', { pageLoader })
const routes = ['route1', 'route2', 'route3', 'route4']

873
yarn.lock

File diff suppressed because it is too large Load diff