mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Merge branch 'v3-beta' of github.com:zeit/next.js into v3-beta
This commit is contained in:
commit
d4eb9455b8
|
@ -8,7 +8,6 @@ addons:
|
||||||
- google-chrome-stable
|
- google-chrome-stable
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "4"
|
|
||||||
- "6"
|
- "6"
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
environment:
|
environment:
|
||||||
matrix:
|
matrix:
|
||||||
- nodejs_version: "4"
|
|
||||||
- nodejs_version: "6"
|
- nodejs_version: "6"
|
||||||
|
|
||||||
# Install scripts. (runs after repo cloning)
|
# Install scripts. (runs after repo cloning)
|
||||||
|
|
|
@ -54,7 +54,8 @@ export default function dynamicComponent (p, o) {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadComponent () {
|
loadComponent () {
|
||||||
promise.then((AsyncComponent) => {
|
promise.then((m) => {
|
||||||
|
const AsyncComponent = m.default || m
|
||||||
// Set a readable displayName for the wrapper component
|
// Set a readable displayName for the wrapper component
|
||||||
const asyncCompName = getDisplayName(AsyncComponent)
|
const asyncCompName = getDisplayName(AsyncComponent)
|
||||||
if (asyncCompName) {
|
if (asyncCompName) {
|
||||||
|
@ -65,7 +66,7 @@ export default function dynamicComponent (p, o) {
|
||||||
this.setState({ AsyncComponent })
|
this.setState({ AsyncComponent })
|
||||||
} else {
|
} else {
|
||||||
if (this.isServer) {
|
if (this.isServer) {
|
||||||
registerChunk(AsyncComponent.__webpackChunkName)
|
registerChunk(m.__webpackChunkName)
|
||||||
}
|
}
|
||||||
this.state.AsyncComponent = AsyncComponent
|
this.state.AsyncComponent = AsyncComponent
|
||||||
}
|
}
|
||||||
|
@ -100,9 +101,10 @@ export default function dynamicComponent (p, o) {
|
||||||
|
|
||||||
const loadModule = (name) => {
|
const loadModule = (name) => {
|
||||||
const promise = modulePromiseMap[name]
|
const promise = modulePromiseMap[name]
|
||||||
promise.then((Component) => {
|
promise.then((m) => {
|
||||||
|
const Component = m.default || m
|
||||||
if (this.isServer) {
|
if (this.isServer) {
|
||||||
registerChunk(Component.__webpackChunkName)
|
registerChunk(m.__webpackChunkName)
|
||||||
}
|
}
|
||||||
moduleMap[name] = Component
|
moduleMap[name] = Component
|
||||||
remainingPromises--
|
remainingPromises--
|
||||||
|
|
11
lib/link.js
11
lib/link.js
|
@ -15,9 +15,12 @@ export default class Link extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = exact({
|
static propTypes = exact({
|
||||||
href: PropTypes.string,
|
href: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
as: PropTypes.string,
|
as: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
prefetch: PropTypes.bool,
|
prefetch: PropTypes.bool,
|
||||||
|
replace: PropTypes.bool,
|
||||||
|
shallow: PropTypes.bool,
|
||||||
|
passHref: PropTypes.bool,
|
||||||
children: PropTypes.oneOfType([
|
children: PropTypes.oneOfType([
|
||||||
PropTypes.element,
|
PropTypes.element,
|
||||||
(props, propName) => {
|
(props, propName) => {
|
||||||
|
@ -29,9 +32,7 @@ export default class Link extends Component {
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
]).isRequired,
|
]).isRequired
|
||||||
shallow: PropTypes.bool,
|
|
||||||
passHref: PropTypes.bool
|
|
||||||
})
|
})
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
|
|
|
@ -118,12 +118,14 @@
|
||||||
"coveralls": "2.13.1",
|
"coveralls": "2.13.1",
|
||||||
"cross-env": "5.0.1",
|
"cross-env": "5.0.1",
|
||||||
"express": "4.15.2",
|
"express": "4.15.2",
|
||||||
|
"fkill": "^5.0.0",
|
||||||
"husky": "0.14.3",
|
"husky": "0.14.3",
|
||||||
"jest-cli": "20.0.4",
|
"jest-cli": "20.0.4",
|
||||||
"lint-staged": "^4.0.0",
|
"lint-staged": "^4.0.0",
|
||||||
"node-fetch": "1.7.1",
|
"node-fetch": "1.7.1",
|
||||||
"node-notifier": "5.1.2",
|
"node-notifier": "5.1.2",
|
||||||
"nyc": "11.0.3",
|
"nyc": "11.0.3",
|
||||||
|
"portfinder": "^1.0.13",
|
||||||
"react": "15.5.4",
|
"react": "15.5.4",
|
||||||
"react-dom": "15.5.4",
|
"react-dom": "15.5.4",
|
||||||
"standard": "9.0.2",
|
"standard": "9.0.2",
|
||||||
|
|
|
@ -13,7 +13,6 @@ const buildImport = (args) => (template(`
|
||||||
eval('require.ensure = function (deps, callback) { callback(require) }')
|
eval('require.ensure = function (deps, callback) { callback(require) }')
|
||||||
require.ensure([], (require) => {
|
require.ensure([], (require) => {
|
||||||
let m = require(SOURCE)
|
let m = require(SOURCE)
|
||||||
m = m.default || m
|
|
||||||
m.__webpackChunkName = '${args.name}.js'
|
m.__webpackChunkName = '${args.name}.js'
|
||||||
resolve(m);
|
resolve(m);
|
||||||
}, 'chunks/${args.name}.js');
|
}, 'chunks/${args.name}.js');
|
||||||
|
@ -23,13 +22,12 @@ const buildImport = (args) => (template(`
|
||||||
const weakId = require.resolveWeak(SOURCE)
|
const weakId = require.resolveWeak(SOURCE)
|
||||||
try {
|
try {
|
||||||
const weakModule = __webpack_require__(weakId)
|
const weakModule = __webpack_require__(weakId)
|
||||||
return resolve(weakModule.default || weakModule)
|
return resolve(weakModule)
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
require.ensure([], (require) => {
|
require.ensure([], (require) => {
|
||||||
try {
|
try {
|
||||||
let m = require(SOURCE)
|
let m = require(SOURCE)
|
||||||
m = m.default || m
|
|
||||||
resolve(m)
|
resolve(m)
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
reject(error)
|
reject(error)
|
||||||
|
|
19
test/integration/basic/pages/hmr/counter.js
Normal file
19
test/integration/basic/pages/hmr/counter.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default class Counter extends React.Component {
|
||||||
|
state = { count: 0 }
|
||||||
|
|
||||||
|
incr () {
|
||||||
|
const { count } = this.state
|
||||||
|
this.setState({ count: count + 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>COUNT: {this.state.count}</p>
|
||||||
|
<button onClick={() => this.incr()}>Increment</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,16 @@ import { readFileSync, writeFileSync, renameSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { waitFor } from 'next-test-utils'
|
import { waitFor } from 'next-test-utils'
|
||||||
|
|
||||||
|
async function check (contentFn, regex) {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const newContent = await contentFn()
|
||||||
|
if (regex.test(newContent)) break
|
||||||
|
await waitFor(1000)
|
||||||
|
} catch (ex) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default (context, render) => {
|
export default (context, render) => {
|
||||||
describe('Hot Module Reloading', () => {
|
describe('Hot Module Reloading', () => {
|
||||||
describe('syntax error', () => {
|
describe('syntax error', () => {
|
||||||
|
@ -21,18 +31,45 @@ export default (context, render) => {
|
||||||
// change the content
|
// change the content
|
||||||
writeFileSync(aboutPagePath, erroredContent, 'utf8')
|
writeFileSync(aboutPagePath, erroredContent, 'utf8')
|
||||||
|
|
||||||
const errorMessage = await browser
|
await check(
|
||||||
.waitForElementByCss('pre')
|
() => browser.elementByCss('body').text(),
|
||||||
.elementByCss('pre').text()
|
/Unterminated JSX contents/
|
||||||
expect(errorMessage.includes('Unterminated JSX contents')).toBeTruthy()
|
)
|
||||||
|
|
||||||
// add the original content
|
// add the original content
|
||||||
writeFileSync(aboutPagePath, originalContent, 'utf8')
|
writeFileSync(aboutPagePath, originalContent, 'utf8')
|
||||||
|
|
||||||
const newContent = await browser
|
await check(
|
||||||
.waitForElementByCss('.hmr-about-page')
|
() => browser.elementByCss('body').text(),
|
||||||
.elementByCss('p').text()
|
/This is the about page/
|
||||||
expect(newContent).toBe('This is the about page.')
|
)
|
||||||
|
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show the error on all pages', async () => {
|
||||||
|
const aboutPagePath = join(__dirname, '../', 'pages', 'hmr', 'about.js')
|
||||||
|
|
||||||
|
const originalContent = readFileSync(aboutPagePath, 'utf8')
|
||||||
|
const erroredContent = originalContent.replace('</div>', 'div')
|
||||||
|
|
||||||
|
// change the content
|
||||||
|
writeFileSync(aboutPagePath, erroredContent, 'utf8')
|
||||||
|
|
||||||
|
const browser = await webdriver(context.appPort, '/hmr/contact')
|
||||||
|
|
||||||
|
await check(
|
||||||
|
() => browser.elementByCss('body').text(),
|
||||||
|
/Unterminated JSX contents/
|
||||||
|
)
|
||||||
|
|
||||||
|
// add the original content
|
||||||
|
writeFileSync(aboutPagePath, originalContent, 'utf8')
|
||||||
|
|
||||||
|
await check(
|
||||||
|
() => browser.elementByCss('body').text(),
|
||||||
|
/This is the contact page/
|
||||||
|
)
|
||||||
|
|
||||||
browser.close()
|
browser.close()
|
||||||
})
|
})
|
||||||
|
@ -52,27 +89,81 @@ export default (context, render) => {
|
||||||
renameSync(contactPagePath, newContactPagePath)
|
renameSync(contactPagePath, newContactPagePath)
|
||||||
|
|
||||||
// wait until the 404 page comes
|
// wait until the 404 page comes
|
||||||
while (true) {
|
await check(
|
||||||
try {
|
() => browser.elementByCss('body').text(),
|
||||||
const pageContent = await browser.elementByCss('body').text()
|
/This page could not be found/
|
||||||
if (/This page could not be found/.test(pageContent)) break
|
)
|
||||||
} catch (ex) {}
|
|
||||||
|
|
||||||
await waitFor(1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename the file back to the original filename
|
// Rename the file back to the original filename
|
||||||
renameSync(newContactPagePath, contactPagePath)
|
renameSync(newContactPagePath, contactPagePath)
|
||||||
|
|
||||||
// wait until the page comes back
|
// wait until the page comes back
|
||||||
while (true) {
|
await check(
|
||||||
try {
|
() => browser.elementByCss('body').text(),
|
||||||
const pageContent = await browser.elementByCss('body').text()
|
/This is the contact page/
|
||||||
if (/This is the contact page/.test(pageContent)) break
|
)
|
||||||
} catch (ex) {}
|
|
||||||
|
|
||||||
await waitFor(1000)
|
browser.close()
|
||||||
}
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('editing a page', () => {
|
||||||
|
it('should detect the changes and display it', async () => {
|
||||||
|
const browser = await webdriver(context.appPort, '/hmr/about')
|
||||||
|
const text = await browser
|
||||||
|
.elementByCss('p').text()
|
||||||
|
expect(text).toBe('This is the about page.')
|
||||||
|
|
||||||
|
const aboutPagePath = join(__dirname, '../', 'pages', 'hmr', 'about.js')
|
||||||
|
|
||||||
|
const originalContent = readFileSync(aboutPagePath, 'utf8')
|
||||||
|
const editedContent = originalContent.replace('This is the about page', 'COOL page')
|
||||||
|
|
||||||
|
// change the content
|
||||||
|
writeFileSync(aboutPagePath, editedContent, 'utf8')
|
||||||
|
|
||||||
|
await check(
|
||||||
|
() => browser.elementByCss('body').text(),
|
||||||
|
/COOL page/
|
||||||
|
)
|
||||||
|
|
||||||
|
// add the original content
|
||||||
|
writeFileSync(aboutPagePath, originalContent, 'utf8')
|
||||||
|
|
||||||
|
await check(
|
||||||
|
() => browser.elementByCss('body').text(),
|
||||||
|
/This is the about page/
|
||||||
|
)
|
||||||
|
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not reload unrelated pages', async () => {
|
||||||
|
const browser = await webdriver(context.appPort, '/hmr/counter')
|
||||||
|
const text = await browser
|
||||||
|
.elementByCss('button').click()
|
||||||
|
.elementByCss('button').click()
|
||||||
|
.elementByCss('p').text()
|
||||||
|
expect(text).toBe('COUNT: 2')
|
||||||
|
|
||||||
|
const aboutPagePath = join(__dirname, '../', 'pages', 'hmr', 'about.js')
|
||||||
|
|
||||||
|
const originalContent = readFileSync(aboutPagePath, 'utf8')
|
||||||
|
const editedContent = originalContent.replace('This is the about page', 'COOL page')
|
||||||
|
|
||||||
|
// Change the about.js page
|
||||||
|
writeFileSync(aboutPagePath, editedContent, 'utf8')
|
||||||
|
|
||||||
|
// wait for 5 seconds
|
||||||
|
await waitFor(5000)
|
||||||
|
|
||||||
|
// Check whether the this page has reloaded or not.
|
||||||
|
const newText = await browser
|
||||||
|
.elementByCss('p').text()
|
||||||
|
expect(newText).toBe('COUNT: 2')
|
||||||
|
|
||||||
|
// restore the about page content.
|
||||||
|
writeFileSync(aboutPagePath, originalContent, 'utf8')
|
||||||
|
|
||||||
browser.close()
|
browser.close()
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,34 +2,25 @@
|
||||||
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import {
|
import {
|
||||||
nextServer,
|
|
||||||
renderViaAPI,
|
|
||||||
renderViaHTTP,
|
renderViaHTTP,
|
||||||
startApp,
|
findPort,
|
||||||
stopApp
|
launchApp,
|
||||||
|
killApp
|
||||||
} from 'next-test-utils'
|
} from 'next-test-utils'
|
||||||
|
|
||||||
// test suits
|
// test suits
|
||||||
import xPoweredBy from './xpowered-by'
|
|
||||||
import rendering from './rendering'
|
import rendering from './rendering'
|
||||||
import misc from './misc'
|
|
||||||
import clientNavigation from './client-navigation'
|
import clientNavigation from './client-navigation'
|
||||||
import hmr from './hmr'
|
import hmr from './hmr'
|
||||||
import dynamic from './dynamic'
|
import dynamic from './dynamic'
|
||||||
|
|
||||||
const context = {}
|
const context = {}
|
||||||
context.app = nextServer({
|
|
||||||
dir: join(__dirname, '../'),
|
|
||||||
dev: true,
|
|
||||||
quiet: true
|
|
||||||
})
|
|
||||||
|
|
||||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
|
||||||
|
|
||||||
describe('Basic Features', () => {
|
describe('Basic Features', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
context.server = await startApp(context.app)
|
context.appPort = await findPort()
|
||||||
context.appPort = context.server.address().port
|
context.server = await launchApp(join(__dirname, '../'), context.appPort, true)
|
||||||
|
|
||||||
// pre-build all pages at the start
|
// pre-build all pages at the start
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -55,15 +46,16 @@ describe('Basic Features', () => {
|
||||||
renderViaHTTP(context.appPort, '/nav/as-path'),
|
renderViaHTTP(context.appPort, '/nav/as-path'),
|
||||||
renderViaHTTP(context.appPort, '/nav/as-path-using-router'),
|
renderViaHTTP(context.appPort, '/nav/as-path-using-router'),
|
||||||
|
|
||||||
renderViaHTTP(context.appPort, '/nested-cdm/index')
|
renderViaHTTP(context.appPort, '/nested-cdm/index'),
|
||||||
|
|
||||||
|
renderViaHTTP(context.appPort, '/hmr/about'),
|
||||||
|
renderViaHTTP(context.appPort, '/hmr/contact'),
|
||||||
|
renderViaHTTP(context.appPort, '/hmr/counter')
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
afterAll(() => stopApp(context.server))
|
afterAll(() => killApp(context.server))
|
||||||
|
|
||||||
rendering(context, 'Rendering via API', (p, q) => renderViaAPI(context.app, p, q))
|
|
||||||
rendering(context, 'Rendering via HTTP', (p, q) => renderViaHTTP(context.appPort, p, q))
|
rendering(context, 'Rendering via HTTP', (p, q) => renderViaHTTP(context.appPort, p, q))
|
||||||
xPoweredBy(context)
|
|
||||||
misc(context)
|
|
||||||
clientNavigation(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
clientNavigation(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
||||||
dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
||||||
hmr(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
hmr(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
/* global describe, test, expect */
|
|
||||||
|
|
||||||
export default function (context) {
|
|
||||||
describe('Misc', () => {
|
|
||||||
test('finishes response', async () => {
|
|
||||||
const res = {
|
|
||||||
finished: false,
|
|
||||||
end () {
|
|
||||||
this.finished = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const html = await context.app.renderToHTML({}, res, '/finish-response', {})
|
|
||||||
expect(html).toBeFalsy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/* global describe, test, expect */
|
|
||||||
import { pkg } from 'next-test-utils'
|
|
||||||
|
|
||||||
export default function ({ app }) {
|
|
||||||
describe('X-Powered-By header', () => {
|
|
||||||
test('set it by default', async () => {
|
|
||||||
const req = { url: '/stateless', headers: {} }
|
|
||||||
const headers = {}
|
|
||||||
const res = {
|
|
||||||
setHeader (key, value) {
|
|
||||||
headers[key] = value
|
|
||||||
},
|
|
||||||
end () {}
|
|
||||||
}
|
|
||||||
|
|
||||||
await app.render(req, res, req.url)
|
|
||||||
expect(headers['X-Powered-By']).toEqual(`Next.js ${pkg.version}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('do not set it when poweredByHeader==false', async () => {
|
|
||||||
const req = { url: '/stateless', headers: {} }
|
|
||||||
const originalConfigValue = app.config.poweredByHeader
|
|
||||||
app.config.poweredByHeader = false
|
|
||||||
const res = {
|
|
||||||
setHeader (key, value) {
|
|
||||||
if (key === 'X-Powered-By') {
|
|
||||||
throw new Error('Should not set the X-Powered-By header')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end () {}
|
|
||||||
}
|
|
||||||
|
|
||||||
await app.render(req, res, req.url)
|
|
||||||
app.config.poweredByHeader = originalConfigValue
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import {
|
import {
|
||||||
|
pkg,
|
||||||
nextServer,
|
nextServer,
|
||||||
nextBuild,
|
nextBuild,
|
||||||
startApp,
|
startApp,
|
||||||
|
@ -89,5 +90,51 @@ describe('Production Usage', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Misc', () => {
|
||||||
|
it('should handle already finished responses', async () => {
|
||||||
|
const res = {
|
||||||
|
finished: false,
|
||||||
|
end () {
|
||||||
|
this.finished = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const html = await app.renderToHTML({}, res, '/finish-response', {})
|
||||||
|
expect(html).toBeFalsy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('X-Powered-By header', () => {
|
||||||
|
it('should set it by default', async () => {
|
||||||
|
const req = { url: '/stateless', headers: {} }
|
||||||
|
const headers = {}
|
||||||
|
const res = {
|
||||||
|
setHeader (key, value) {
|
||||||
|
headers[key] = value
|
||||||
|
},
|
||||||
|
end () {}
|
||||||
|
}
|
||||||
|
|
||||||
|
await app.render(req, res, req.url)
|
||||||
|
expect(headers['X-Powered-By']).toEqual(`Next.js ${pkg.version}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not set it when poweredByHeader==false', async () => {
|
||||||
|
const req = { url: '/stateless', headers: {} }
|
||||||
|
const originalConfigValue = app.config.poweredByHeader
|
||||||
|
app.config.poweredByHeader = false
|
||||||
|
const res = {
|
||||||
|
setHeader (key, value) {
|
||||||
|
if (key === 'X-Powered-By') {
|
||||||
|
throw new Error('Should not set the X-Powered-By header')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
end () {}
|
||||||
|
}
|
||||||
|
|
||||||
|
await app.render(req, res, req.url)
|
||||||
|
app.config.poweredByHeader = originalConfigValue
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
dynamicImportTests(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
dynamicImportTests(context, (p, q) => renderViaHTTP(context.appPort, p, q))
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,10 @@ import fetch from 'node-fetch'
|
||||||
import qs from 'querystring'
|
import qs from 'querystring'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
|
import path from 'path'
|
||||||
|
import portfinder from 'portfinder'
|
||||||
|
import { spawn } from 'child_process'
|
||||||
|
import fkill from 'fkill'
|
||||||
|
|
||||||
import server from '../../dist/server/next'
|
import server from '../../dist/server/next'
|
||||||
import build from '../../dist/server/build'
|
import build from '../../dist/server/build'
|
||||||
|
@ -23,6 +27,48 @@ export function renderViaHTTP (appPort, pathname, query = {}) {
|
||||||
return fetch(url).then((res) => res.text())
|
return fetch(url).then((res) => res.text())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findPort () {
|
||||||
|
portfinder.basePort = 20000 + Math.ceil(Math.random() * 10000)
|
||||||
|
return portfinder.getPortPromise()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch the app in dev mode.
|
||||||
|
export function launchApp (dir, port) {
|
||||||
|
const cwd = path.resolve(__dirname, '../../')
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const instance = spawn('node', ['dist/bin/next', dir, '-p', port], { cwd })
|
||||||
|
|
||||||
|
function handleStdout (data) {
|
||||||
|
const message = data.toString()
|
||||||
|
if (/> Ready on/.test(message)) {
|
||||||
|
resolve(instance)
|
||||||
|
}
|
||||||
|
process.stdout.write(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStderr (data) {
|
||||||
|
process.stderr.write(data.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.stdout.on('data', handleStdout)
|
||||||
|
instance.stderr.on('data', handleStderr)
|
||||||
|
|
||||||
|
instance.on('close', () => {
|
||||||
|
instance.stdout.removeListener('data', handleStdout)
|
||||||
|
instance.stderr.removeListener('data', handleStderr)
|
||||||
|
})
|
||||||
|
|
||||||
|
instance.on('error', (err) => {
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill a launched app
|
||||||
|
export async function killApp (instance) {
|
||||||
|
await fkill(instance.pid)
|
||||||
|
}
|
||||||
|
|
||||||
export async function startApp (app) {
|
export async function startApp (app) {
|
||||||
await app.prepare()
|
await app.prepare()
|
||||||
const handler = app.getRequestHandler()
|
const handler = app.getRequestHandler()
|
||||||
|
|
74
yarn.lock
74
yarn.lock
|
@ -74,6 +74,13 @@ acorn@^5.0.0, acorn@^5.0.1:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.0.tgz#e468bf609b0672700e02f878ae2f1b360fe24b4f"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.0.tgz#e468bf609b0672700e02f878ae2f1b360fe24b4f"
|
||||||
|
|
||||||
|
aggregate-error@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-1.0.0.tgz#888344dad0220a72e3af50906117f48771925fac"
|
||||||
|
dependencies:
|
||||||
|
clean-stack "^1.0.0"
|
||||||
|
indent-string "^3.0.0"
|
||||||
|
|
||||||
ajv-keywords@^1.0.0:
|
ajv-keywords@^1.0.0:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
|
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
|
||||||
|
@ -290,7 +297,7 @@ async@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.8.0"
|
lodash "^4.8.0"
|
||||||
|
|
||||||
async@^1.4.0:
|
async@^1.4.0, async@^1.5.2:
|
||||||
version "1.5.2"
|
version "1.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||||
|
|
||||||
|
@ -1298,6 +1305,10 @@ circular-json@^0.3.1:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
|
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
|
||||||
|
|
||||||
|
clean-stack@^1.0.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31"
|
||||||
|
|
||||||
cli-cursor@^1.0.1, cli-cursor@^1.0.2:
|
cli-cursor@^1.0.1, cli-cursor@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
|
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
|
||||||
|
@ -1514,6 +1525,13 @@ cross-env@5.0.1:
|
||||||
cross-spawn "^5.1.0"
|
cross-spawn "^5.1.0"
|
||||||
is-windows "^1.0.0"
|
is-windows "^1.0.0"
|
||||||
|
|
||||||
|
cross-spawn-async@^2.1.1:
|
||||||
|
version "2.2.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc"
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^4.0.0"
|
||||||
|
which "^1.2.8"
|
||||||
|
|
||||||
cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0:
|
cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
|
||||||
|
@ -2096,6 +2114,14 @@ exec-sh@^0.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
merge "^1.1.3"
|
merge "^1.1.3"
|
||||||
|
|
||||||
|
execa@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/execa/-/execa-0.1.1.tgz#b09c2a9309bc0ef0501479472db3180f8d4c3edd"
|
||||||
|
dependencies:
|
||||||
|
cross-spawn-async "^2.1.1"
|
||||||
|
object-assign "^4.0.1"
|
||||||
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
execa@^0.5.0:
|
execa@^0.5.0:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-0.5.1.tgz#de3fb85cb8d6e91c85bcbceb164581785cb57b36"
|
||||||
|
@ -2108,6 +2134,18 @@ execa@^0.5.0:
|
||||||
signal-exit "^3.0.0"
|
signal-exit "^3.0.0"
|
||||||
strip-eof "^1.0.0"
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
|
execa@^0.6.3:
|
||||||
|
version "0.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/execa/-/execa-0.6.3.tgz#57b69a594f081759c69e5370f0d17b9cb11658fe"
|
||||||
|
dependencies:
|
||||||
|
cross-spawn "^5.0.1"
|
||||||
|
get-stream "^3.0.0"
|
||||||
|
is-stream "^1.1.0"
|
||||||
|
npm-run-path "^2.0.0"
|
||||||
|
p-finally "^1.0.0"
|
||||||
|
signal-exit "^3.0.0"
|
||||||
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
execa@^0.7.0:
|
execa@^0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
|
||||||
|
@ -2321,6 +2359,15 @@ find-up@^2.0.0, find-up@^2.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path "^2.0.0"
|
locate-path "^2.0.0"
|
||||||
|
|
||||||
|
fkill@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fkill/-/fkill-5.0.0.tgz#642f746aa631c965afc76c0d56dcc492a1ee504f"
|
||||||
|
dependencies:
|
||||||
|
aggregate-error "^1.0.0"
|
||||||
|
arrify "^1.0.0"
|
||||||
|
execa "^0.6.3"
|
||||||
|
taskkill "^2.0.0"
|
||||||
|
|
||||||
flat-cache@^1.2.1:
|
flat-cache@^1.2.1:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
|
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
|
||||||
|
@ -3599,7 +3646,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens "^3.0.0"
|
js-tokens "^3.0.0"
|
||||||
|
|
||||||
lru-cache@^4.0.1:
|
lru-cache@^4.0.0, lru-cache@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -3764,7 +3811,7 @@ mkdirp@0.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4291,6 +4338,14 @@ pluralize@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
|
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
|
||||||
|
|
||||||
|
portfinder@^1.0.13:
|
||||||
|
version "1.0.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
|
||||||
|
dependencies:
|
||||||
|
async "^1.5.2"
|
||||||
|
debug "^2.2.0"
|
||||||
|
mkdirp "0.5.x"
|
||||||
|
|
||||||
prelude-ls@~1.1.2:
|
prelude-ls@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||||
|
@ -4371,11 +4426,11 @@ public-encrypt@^4.0.0:
|
||||||
parse-asn1 "^5.0.0"
|
parse-asn1 "^5.0.0"
|
||||||
randombytes "^2.0.1"
|
randombytes "^2.0.1"
|
||||||
|
|
||||||
punycode@1.3.2:
|
punycode@1.3.2, punycode@^1.2.4:
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
||||||
|
|
||||||
punycode@^1.2.4, punycode@^1.4.1:
|
punycode@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||||
|
|
||||||
|
@ -5181,6 +5236,13 @@ tar@^2.2.1:
|
||||||
fstream "^1.0.2"
|
fstream "^1.0.2"
|
||||||
inherits "2"
|
inherits "2"
|
||||||
|
|
||||||
|
taskkill@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/taskkill/-/taskkill-2.0.0.tgz#a354305702a964357033027aa949eaed5331b784"
|
||||||
|
dependencies:
|
||||||
|
arrify "^1.0.0"
|
||||||
|
execa "^0.1.1"
|
||||||
|
|
||||||
taskr@1.0.6:
|
taskr@1.0.6:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/taskr/-/taskr-1.0.6.tgz#6ba5b671f51703f780fb6335d3dc792cfcf9c192"
|
resolved "https://registry.yarnpkg.com/taskr/-/taskr-1.0.6.tgz#6ba5b671f51703f780fb6335d3dc792cfcf9c192"
|
||||||
|
@ -5539,7 +5601,7 @@ which-module@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||||
|
|
||||||
which@^1.2.10, which@^1.2.12, which@^1.2.4, which@^1.2.9:
|
which@^1.2.10, which@^1.2.12, which@^1.2.4, which@^1.2.8, which@^1.2.9:
|
||||||
version "1.2.14"
|
version "1.2.14"
|
||||||
resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
|
resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue