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

Merge branch 'canary' of github.com:zeit/next.js into canary

This commit is contained in:
Tim Neutkens 2018-03-30 16:09:40 +02:00
commit b6b88e5f81
101 changed files with 2125 additions and 1013 deletions

View file

@ -1,7 +1,8 @@
{
"presets": [
"env",
"react"
"react",
"flow"
],
"plugins": [
"transform-object-rest-spread",

2
.flowconfig Normal file
View file

@ -0,0 +1,2 @@
[ignore]
<PROJECT_ROOT>/examples/.*

View file

@ -43,7 +43,7 @@ if (!existsSync(join(dir, 'pages'))) {
}
build(dir)
.catch((err) => {
console.error(err)
process.exit(1)
})
.catch((err) => {
console.error(err)
process.exit(1)
})

View file

@ -54,23 +54,23 @@ if (!existsSync(join(dir, 'pages'))) {
const srv = new Server({ dir, dev: true })
srv.start(argv.port, argv.hostname)
.then(async () => {
if (!process.env.NOW) {
console.log(`> Ready on http://${argv.hostname ? argv.hostname : 'localhost'}:${argv.port}`)
}
})
.catch((err) => {
if (err.code === 'EADDRINUSE') {
let errorMessage = `Port ${argv.port} is already in use.`
const pkgAppPath = require('find-up').sync('package.json', {
cwd: dir
})
const appPackage = JSON.parse(readFileSync(pkgAppPath, 'utf8'))
const nextScript = Object.entries(appPackage.scripts).find(scriptLine => scriptLine[1] === 'next')
if (nextScript) errorMessage += `\nUse \`npm run ${nextScript[0]} -- -p <some other port>\`.`
console.error(errorMessage)
} else {
console.error(err)
}
process.nextTick(() => process.exit(1))
})
.then(async () => {
if (!process.env.NOW) {
console.log(`> Ready on http://${argv.hostname ? argv.hostname : 'localhost'}:${argv.port}`)
}
})
.catch((err) => {
if (err.code === 'EADDRINUSE') {
let errorMessage = `Port ${argv.port} is already in use.`
const pkgAppPath = require('find-up').sync('package.json', {
cwd: dir
})
const appPackage = JSON.parse(readFileSync(pkgAppPath, 'utf8'))
const nextScript = Object.entries(appPackage.scripts).find(scriptLine => scriptLine[1] === 'next')
if (nextScript) errorMessage += `\nUse \`npm run ${nextScript[0]} -- -p <some other port>\`.`
console.error(errorMessage)
} else {
console.error(err)
}
process.nextTick(() => process.exit(1))
})

View file

@ -46,12 +46,12 @@ const dir = resolve(argv._[0] || '.')
const srv = new Server({ dir })
srv.start(argv.port, argv.hostname)
.then(() => {
if (!process.env.NOW) {
console.log(`> Ready on http://${argv.hostname ? argv.hostname : 'localhost'}:${argv.port}`)
}
})
.catch((err) => {
console.error(err)
process.exit(1)
})
.then(() => {
if (!process.env.NOW) {
console.log(`> Ready on http://${argv.hostname ? argv.hostname : 'localhost'}:${argv.port}`)
}
})
.catch((err) => {
console.error(err)
process.exit(1)
})

View file

@ -8,14 +8,14 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
res.setHeader('Content-Type', 'text/html; charset=iso-8859-2')
handle(req, res, parsedUrl)
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
res.setHeader('Content-Type', 'text/html; charset=iso-8859-2')
handle(req, res, parsedUrl)
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -8,7 +8,7 @@ exports['default'] = {
// Logging levels of task workers
workerLogging: {
failure: 'error', // task failure
success: 'info', // task success
success: 'info', // task success
start: 'info',
end: 'info',
cleaning_worker: 'info',

View file

@ -7,27 +7,27 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
.then(() => {
const server = express()
server.get('/a', (req, res) => {
return app.render(req, res, '/b', req.query)
})
server.get('/a', (req, res) => {
return app.render(req, res, '/b', req.query)
})
server.get('/b', (req, res) => {
return app.render(req, res, '/a', req.query)
})
server.get('/b', (req, res) => {
return app.render(req, res, '/a', req.query)
})
server.get('/posts/:id', (req, res) => {
return app.render(req, res, '/posts', { id: req.params.id })
})
server.get('/posts/:id', (req, res) => {
return app.render(req, res, '/posts', { id: req.params.id })
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
})

View file

@ -7,23 +7,23 @@ const app = Next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = fastify()
.then(() => {
const server = fastify()
server.get('/a', (req, res) => {
return app.render(req.req, res.res, '/a', req.query)
})
server.get('/a', (req, res) => {
return app.render(req.req, res.res, '/a', req.query)
})
server.get('/b', (req, res) => {
return app.render(req.req, res.res, '/b', req.query)
})
server.get('/b', (req, res) => {
return app.render(req.req, res.res, '/b', req.query)
})
server.get('/*', (req, res) => {
return handle(req.req, res.res)
})
server.get('/*', (req, res) => {
return handle(req.req, res.res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
})

View file

@ -10,37 +10,37 @@ const server = new Hapi.Server({
})
app
.prepare()
.then(async () => {
server.route({
method: 'GET',
path: '/a',
handler: pathWrapper(app, '/a')
})
.prepare()
.then(async () => {
server.route({
method: 'GET',
path: '/a',
handler: pathWrapper(app, '/a')
})
server.route({
method: 'GET',
path: '/b',
handler: pathWrapper(app, '/b')
})
server.route({
method: 'GET',
path: '/b',
handler: pathWrapper(app, '/b')
})
server.route({
method: 'GET',
path: '/_next/{p*}', /* next specific routes */
handler: nextHandlerWrapper(app)
})
server.route({
method: 'GET',
path: '/_next/{p*}', /* next specific routes */
handler: nextHandlerWrapper(app)
})
server.route({
method: 'GET',
path: '/{p*}', /* catch all route */
handler: defaultHandlerWrapper(app)
})
server.route({
method: 'GET',
path: '/{p*}', /* catch all route */
handler: defaultHandlerWrapper(app)
})
try {
await server.start()
console.log(`> Ready on http://localhost:${port}`)
} catch (error) {
console.log('Error starting server')
console.log(error)
}
})
try {
await server.start()
console.log(`> Ready on http://localhost:${port}`)
} catch (error) {
console.log('Error starting server')
console.log(error)
}
})

View file

@ -8,32 +8,32 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = new Koa()
const router = new Router()
.then(() => {
const server = new Koa()
const router = new Router()
router.get('/a', async ctx => {
await app.render(ctx.req, ctx.res, '/b', ctx.query)
ctx.respond = false
})
router.get('/a', async ctx => {
await app.render(ctx.req, ctx.res, '/b', ctx.query)
ctx.respond = false
})
router.get('/b', async ctx => {
await app.render(ctx.req, ctx.res, '/a', ctx.query)
ctx.respond = false
})
router.get('/b', async ctx => {
await app.render(ctx.req, ctx.res, '/a', ctx.query)
ctx.respond = false
})
router.get('*', async ctx => {
await handle(ctx.req, ctx.res)
ctx.respond = false
})
router.get('*', async ctx => {
await handle(ctx.req, ctx.res)
ctx.respond = false
})
server.use(async (ctx, next) => {
ctx.res.statusCode = 200
await next()
})
server.use(async (ctx, next) => {
ctx.res.statusCode = 200
await next()
})
server.use(router.routes())
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`)
server.use(router.routes())
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`)
})
})
})

View file

@ -8,21 +8,21 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -7,14 +7,14 @@
"dependencies": {
"next": "latest",
"react": "^16.2.0",
"react-dom": "^16.2.0"
"react-dom": "^16.2.0",
"@zeit/next-typescript": "0.0.11",
"typescript": "^2.7.1"
},
"devDependencies": {
"@types/next": "^2.4.7",
"@types/react": "^16.0.36",
"@zeit/next-typescript": "^0.0.8",
"nodemon": "^1.12.1",
"ts-node": "^4.1.0",
"typescript": "^2.7.1"
"ts-node": "^4.1.0"
}
}

View file

@ -8,21 +8,21 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
if (pathname === '/a') {
app.render(req, res, '/b', query)
} else if (pathname === '/b') {
app.render(req, res, '/a', query)
} else {
handle(req, res, parsedUrl)
}
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -6,23 +6,23 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
.then(() => {
const server = express()
server.get('/', (req, res) => {
return handle(req, res)
})
server.get('/', (req, res) => {
return handle(req, res)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
server.listen(3000, (err) => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
})
})
.catch((ex) => {
console.error(ex.stack)
process.exit(1)
})

View file

@ -11,21 +11,21 @@ const route = pathMatch()
const match = route('/blog/:id')
app.prepare()
.then(() => {
createServer((req, res) => {
const { pathname, query } = parse(req.url, true)
const params = match(pathname)
if (params === false) {
handle(req, res)
return
}
// assigning `query` into the params means that we still
// get the query string passed to our application
// i.e. /blog/foo?show-comments=true
app.render(req, res, '/blog', Object.assign(params, query))
.then(() => {
createServer((req, res) => {
const { pathname, query } = parse(req.url, true)
const params = match(pathname)
if (params === false) {
handle(req, res)
return
}
// assigning `query` into the params means that we still
// get the query string passed to our application
// i.e. /blog/foo?show-comments=true
app.render(req, res, '/blog', Object.assign(params, query))
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -9,23 +9,23 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const rootStaticFiles = [
'/robots.txt',
'/sitemap.xml',
'/favicon.ico'
]
if (rootStaticFiles.indexOf(parsedUrl.pathname) > -1) {
const path = join(__dirname, 'static', parsedUrl.pathname)
app.serveStatic(req, res, path)
} else {
handle(req, res, parsedUrl)
}
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const rootStaticFiles = [
'/robots.txt',
'/sitemap.xml',
'/favicon.ico'
]
if (rootStaticFiles.indexOf(parsedUrl.pathname) > -1) {
const path = join(__dirname, 'static', parsedUrl.pathname)
app.serveStatic(req, res, path)
} else {
handle(req, res, parsedUrl)
}
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -14,28 +14,28 @@ const ssrCache = new LRUCache({
})
app.prepare()
.then(() => {
const server = express()
.then(() => {
const server = express()
// Use the `renderAndCache` utility defined below to serve pages
server.get('/', (req, res) => {
renderAndCache(req, res, '/')
})
// Use the `renderAndCache` utility defined below to serve pages
server.get('/', (req, res) => {
renderAndCache(req, res, '/')
})
server.get('/blog/:id', (req, res) => {
const queryParams = { id: req.params.id }
renderAndCache(req, res, '/blog', queryParams)
})
server.get('/blog/:id', (req, res) => {
const queryParams = { id: req.params.id }
renderAndCache(req, res, '/blog', queryParams)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
})
/*
* NB: make sure to modify this to take into account anything that should trigger

View file

@ -18,13 +18,13 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -17,13 +17,13 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -11,13 +11,13 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
handle(req, res, parsedUrl)
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -1,3 +1,4 @@
/* eslint-disable */
const withCss = require('@zeit/next-css')
// fix: prevents error when .css files are required by node

View file

@ -49,3 +49,6 @@ export default withReduxSaga(connect(mapStateToProps, null)(Index));
```
`connect` must go inside `withReduxSaga` otherwise `connect` will not be able to find the store.
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -49,3 +49,6 @@ const mapStateToProps = state => ({
export default withRedux(connect(mapStateToProps, null)(Index));
```
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -66,3 +66,7 @@ It is important to note the use of Apollo's `resetStore()` method after signing
To get this example running locally, you will need to create a graph.cool
account, and provide [the `project.graphcool` schema](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/project.graphcool).
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -48,3 +48,6 @@ In this simple example, we integrate Apollo seamlessly with Next by wrapping our
On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
### Note:
In these *with-apollo* examples, the ```withData()``` HOC must wrap a top-level component from within the ```pages``` directory. Wrapping a child component with the HOC will result in a ```Warning: Failed prop type: The prop 'serverState' is marked as required in 'WithData(Apollo(Component))', but its value is 'undefined'``` error. Down-tree child components will have access to Apollo, and can be wrapped with any other sort of ```graphql()```, ```compose()```, etc HOC's.

View file

@ -32,9 +32,16 @@ export default ComposedComponent => {
// and extract the resulting data
const apollo = initApollo()
try {
// create the url prop which is passed to every page
const url = {
query: ctx.query,
asPath: ctx.asPath,
pathname: ctx.pathname,
};
// Run all GraphQL queries
await getDataFromTree(
<ComposedComponent ctx={ctx} {...composedInitialProps} />,
<ComposedComponent ctx={ctx} url={url} {...composedInitialProps} />,
{
router: {
asPath: ctx.asPath,

View file

@ -10,24 +10,24 @@ export default connect({
mounted: signal`clock.mounted`,
unMounted: signal`clock.unMounted`
},
class Page extends React.Component {
componentDidMount () {
this.props.mounted()
}
componentWillUnmount () {
this.props.unMounted()
}
render () {
return (
<div>
<h1>{this.props.title}</h1>
<Clock lastUpdate={this.props.lastUpdate} light={this.props.light} />
<nav>
<Link href={this.props.linkTo}><a>Navigate</a></Link>
</nav>
</div>
)
}
class Page extends React.Component {
componentDidMount () {
this.props.mounted()
}
componentWillUnmount () {
this.props.unMounted()
}
render () {
return (
<div>
<h1>{this.props.title}</h1>
<Clock lastUpdate={this.props.lastUpdate} light={this.props.light} />
<nav>
<Link href={this.props.linkTo}><a>Navigate</a></Link>
</nav>
</div>
)
}
}
)

View file

@ -5,7 +5,7 @@ import {
RichUtils,
convertToRaw,
convertFromRaw
} from 'draft-js'
} from 'draft-js'
export default class App extends React.Component {
constructor (props) {
@ -62,7 +62,7 @@ export default class App extends React.Component {
setSelectionXY = () => {
var r = window.getSelection().getRangeAt(0).getBoundingClientRect()
var relative = document.body.parentNode.getBoundingClientRect()
// 2-a Set the selection coordinates in the state
// 2-a Set the selection coordinates in the state
this.setState({
selectionCoordinates: r,
windowWidth: relative.width,
@ -176,7 +176,7 @@ export default class App extends React.Component {
this.elemHeight = elem ? elem.clientHeight : 0
}}
style={toolbarStyle}
>
>
<ToolBar
editorState={editorState}
onToggle={this.toggleToolbar}
@ -261,8 +261,8 @@ const ToolBar = (props) => {
label={toolbarItem.label}
onToggle={props.onToggle}
style={toolbarItem.style}
/>
)}
/>
)}
</div>
)
}

View file

@ -1,3 +1,3 @@
module.exports = {
// TODO firebase client config
// TODO firebase client config
}

View file

@ -1,3 +1,3 @@
module.exports = {
// TODO firebase server config
// TODO firebase server config
}

View file

@ -90,8 +90,8 @@ export default class Index extends Component {
return <div>
{
user
? <button onClick={this.handleLogout}>Logout</button>
: <button onClick={this.handleLogin}>Login</button>
? <button onClick={this.handleLogout}>Logout</button>
: <button onClick={this.handleLogin}>Login</button>
}
{
user &&

View file

@ -16,49 +16,49 @@ const firebase = admin.initializeApp({
}, 'server')
app.prepare()
.then(() => {
const server = express()
.then(() => {
const server = express()
server.use(bodyParser.json())
server.use(session({
secret: 'geheimnis',
saveUninitialized: true,
store: new FileStore({path: '/tmp/sessions', secret: 'geheimnis'}),
resave: false,
rolling: true,
httpOnly: true,
cookie: { maxAge: 604800000 } // week
}))
server.use(bodyParser.json())
server.use(session({
secret: 'geheimnis',
saveUninitialized: true,
store: new FileStore({path: '/tmp/sessions', secret: 'geheimnis'}),
resave: false,
rolling: true,
httpOnly: true,
cookie: { maxAge: 604800000 } // week
}))
server.use((req, res, next) => {
req.firebaseServer = firebase
next()
server.use((req, res, next) => {
req.firebaseServer = firebase
next()
})
server.post('/api/login', (req, res) => {
if (!req.body) return res.sendStatus(400)
const token = req.body.token
firebase.auth().verifyIdToken(token)
.then((decodedToken) => {
req.session.decodedToken = decodedToken
return decodedToken
})
.then((decodedToken) => res.json({ status: true, decodedToken }))
.catch((error) => res.json({ error }))
})
server.post('/api/logout', (req, res) => {
req.session.decodedToken = null
res.json({ status: true })
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
server.post('/api/login', (req, res) => {
if (!req.body) return res.sendStatus(400)
const token = req.body.token
firebase.auth().verifyIdToken(token)
.then((decodedToken) => {
req.session.decodedToken = decodedToken
return decodedToken
})
.then((decodedToken) => res.json({ status: true, decodedToken }))
.catch((error) => res.json({ error }))
})
server.post('/api/logout', (req, res) => {
req.session.decodedToken = null
res.json({ status: true })
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -11,12 +11,12 @@ module.exports = {
name: 'dist/[path][name].[ext]'
}
}
,
,
{
test: /\.css$/,
use: ['babel-loader', 'raw-loader', 'postcss-loader']
}
,
,
{
test: /\.s(a|c)ss$/,
use: ['babel-loader', 'raw-loader', 'postcss-loader',

View file

@ -0,0 +1,41 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-google-analytics)
# Example app with analytics
## How to use
### Using `create-next-app`
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
```bash
npx create-next-app --example with-google-analytics with-google-analytics-app
# or
yarn create next-app --example with-google-analytics with-google-analytics-app
```
### Download manually
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-google-analytics
cd with-google-analytics
```
Install it and run:
```bash
yarn
yarn dev
```
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
```bash
now
```
## The idea behind the example
This example shows how to use [Next.js](https://github.com/zeit/next.js) along with [Google Analytics](https://developers.google.com/analytics/devguides/collection/gtagjs/). A custom [\_document](https://github.com/zeit/next.js/#custom-document) is used to inject [tracking snippet](https://developers.google.com/analytics/devguides/collection/gtagjs/) and track [pageviews](https://developers.google.com/analytics/devguides/collection/gtagjs/pages) and [event](https://developers.google.com/analytics/devguides/collection/gtagjs/events).

View file

@ -0,0 +1,26 @@
import React from 'react'
import Link from 'next/link'
export default () => (
<header>
<nav>
<ul>
<li>
<Link href='/'>
<a>Home</a>
</Link>
</li>
<li>
<Link href='/about'>
<a>About</a>
</Link>
</li>
<li>
<Link href='/contact'>
<a>Contact</a>
</Link>
</li>
</ul>
</nav>
</header>
)

View file

@ -0,0 +1,16 @@
import React from 'react'
import Router from 'next/router'
import Header from './Header'
import * as gtag from '../lib/gtag'
Router.onRouteChangeComplete = url => {
gtag.pageview(url)
}
export default ({ children }) => (
<div>
<Header />
{children}
</div>
)

View file

@ -0,0 +1,17 @@
export const GA_TRACKING_ID = '<YOUR_GA_TRACKING_ID>'
// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
export const pageview = url => {
window.gtag('config', GA_TRACKING_ID, {
page_location: url,
})
}
// https://developers.google.com/analytics/devguides/collection/gtagjs/events
export const event = ({ action, category, label, value }) => {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
})
}

View file

@ -0,0 +1,13 @@
{
"name": "with-google-analytics",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^16.2.0",
"react-dom": "^16.2.0"
}
}

View file

@ -0,0 +1,41 @@
import React from 'react'
import Document, { Head, Main, NextScript } from 'next/document'
import flush from 'styled-jsx/server'
import { GA_TRACKING_ID } from '../lib/gtag'
export default class extends Document {
static getInitialProps ({ renderPage }) {
const { html, head, errorHtml, chunks } = renderPage()
const styles = flush()
return { html, head, errorHtml, chunks, styles }
}
render () {
return (
<html>
<Head>
{/* Global Site Tag (gtag.js) - Google Analytics */}
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}');
`}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}

View file

@ -0,0 +1,8 @@
import React from 'react'
import Page from '../components/Page'
export default () => (
<Page>
<h1>This is the About page</h1>
</Page>
)

View file

@ -0,0 +1,39 @@
import React, { Component } from 'react'
import Page from '../components/Page'
import * as gtag from '../lib/gtag'
export default class extends Component {
state = { message: '' }
handleInput = e => {
this.setState({ message: e.target.value })
}
handleSubmit = e => {
e.preventDefault()
gtag.event({
action: 'submit_form',
category: 'Contact',
label: this.state.message
})
this.setState({ message: '' })
}
render () {
return (
<Page>
<h1>This is the Contact page</h1>
<form onSubmit={this.handleSubmit}>
<label>
<span>Message:</span>
<textarea onInput={this.handleInput} value={this.state.message} />
</label>
<button type='submit'>submit</button>
</form>
</Page>
)
}
}

View file

@ -0,0 +1,8 @@
import React from 'react'
import Page from '../components/Page'
export default () => (
<Page>
<h1>This is the Home page</h1>
</Page>
)

View file

@ -8,10 +8,10 @@ const app = next({ dev })
const handler = routes.getRequestHandler(app)
app.prepare()
.then(() => {
createServer(handler)
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
.then(() => {
createServer(handler)
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
})

View file

@ -8,10 +8,10 @@ const app = next({ dev })
const handler = routes.getRequestHandler(app)
app.prepare()
.then(() => {
createServer(handler)
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
.then(() => {
createServer(handler)
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
})

View file

@ -0,0 +1,43 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-now-env)
# Now-env example
## How to use
### Using `create-next-app`
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
```bash
npx create-next-app --example with-now-env with-now-env-app
# or
yarn create next-app --example with-now-env with-now-env-app
```
### Download manually
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-now-env
cd with-now-env
```
Install it and run:
```bash
npm install
npm run dev
```
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
```bash
now
```
keep in mind that in order to deploy the app to `now` the env [secrets](https://zeit.co/docs/getting-started/secrets) defined in `now.json` should be listed in your account
## The idea behind the example
This example shows the usage of [now-env](https://github.com/zeit/now-env), it allows to use secrets in development that will be replaced in production by the secrets defined with [now](https://zeit.co/docs/getting-started/secrets)

View file

@ -0,0 +1,27 @@
/**
* After the next require you can use process.env to get your secrets
*/
require('now-env')
console.log({
SECRET: process.env.SECRET,
ANOTHER_SECRET: process.env.ANOTHER_SECRET,
SECRET_FAIL: process.env.SECRET_FAIL
})
/**
* If some of the envs are public, like a google maps key, but you still
* want to keep them secret from the repo, the following code will allow you
* to share some variables with the client, configured at compile time.
*/
module.exports = {
webpack: config => {
config.plugins.forEach(plugin => {
if (plugin.constructor.name === 'DefinePlugin') {
plugin.definitions['process.env.SECRET'] = JSON.stringify(process.env.SECRET)
}
})
return config
}
}

View file

@ -0,0 +1,4 @@
{
"@my-secret-key": "keep-it-secret",
"@my-other-secret-key": "keep-it-secret-too"
}

View file

@ -0,0 +1,7 @@
{
"env": {
"SECRET": "@my-secret-key",
"ANOTHER_SECRET": "@my-other-secret-key",
"SECRET_FAIL": "@this-is-not-defined"
}
}

View file

@ -0,0 +1,17 @@
{
"name": "with-now-env",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^16.2.0",
"react-dom": "^16.2.0"
},
"devDependencies": {
"now-env": "^3.0.4"
}
}

View file

@ -0,0 +1,22 @@
export default () => (
<div className='hello'>
<p>
Hello World! Here's a secret shared with the client using webpack
DefinePlugin: <strong>{process.env.SECRET}</strong>, the secret is shared
at compile time, which means every reference to the secret is replaced
with its value
</p>
<style jsx>{`
.hello {
font: 15px Helvetica, Arial, sans-serif;
background: #eee;
padding: 100px;
text-align: center;
transition: 100ms ease-in background;
}
.hello:hover {
background: #ccc;
}
`}</style>
</div>
)

View file

@ -8,10 +8,10 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => handle(req, res, parse(req.url, true).pathname))
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
.then(() => {
createServer((req, res) => handle(req, res, parse(req.url, true).pathname))
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
})

View file

@ -1,13 +1,11 @@
/* eslint no-extend-native: 0 */
// Add your polyfills
// This files runs at the very beginning (even before React and Next.js core)
console.log('Load your polyfills')
// core-js comes with Next.js. So, you can import it like below
import includes from 'core-js/library/fn/string/virtual/includes'
import repeat from 'core-js/library/fn/string/virtual/repeat'
// Add your polyfills
// This files runs at the very beginning (even before React and Next.js core)
console.log('Load your polyfills')
String.prototype.includes = includes
String.prototype.repeat = repeat

View file

@ -8,13 +8,13 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
.then(() => {
const server = express()
Router.forEachPattern((page, pattern, defaultParams) => server.get(pattern, (req, res) =>
app.render(req, res, `/${page}`, Object.assign({}, defaultParams, req.query, req.params))
))
Router.forEachPattern((page, pattern, defaultParams) => server.get(pattern, (req, res) =>
app.render(req, res, `/${page}`, Object.assign({}, defaultParams, req.query, req.params))
))
server.get('*', (req, res) => handle(req, res))
server.listen(port)
})
server.get('*', (req, res) => handle(req, res))
server.listen(port)
})

View file

@ -22,8 +22,8 @@ export default class extends Document {
// should render on <head>
get helmetHeadComponents () {
return Object.keys(this.props.helmet)
.filter(el => el !== 'htmlAttributes' && el !== 'bodyAttributes')
.map(el => this.props.helmet[el].toComponent())
.filter(el => el !== 'htmlAttributes' && el !== 'bodyAttributes')
.map(el => this.props.helmet[el].toComponent())
}
get helmetJsx () {

View file

@ -39,5 +39,5 @@ export default connect(
character: state.character,
error: state.error,
isFetchedOnServer: state.isFetchedOnServer
}),
})
)(CharacterInfo)

View file

@ -46,5 +46,5 @@ export default withRedux(
{
startFetchingCharacters: actions.startFetchingCharacters,
stopFetchingCharacters: actions.stopFetchingCharacters
},
}
)(Counter)

View file

@ -7,7 +7,7 @@ import { rootEpic } from './epics'
export default function initStore (initialState) {
const epicMiddleware = createEpicMiddleware(rootEpic)
const logger = createLogger({ collapsed: true }) // log every action to see what's happening behind the scenes.
const logger = createLogger({ collapsed: true }) // log every action to see what's happening behind the scenes.
const reduxMiddleware = applyMiddleware(thunkMiddleware, epicMiddleware, logger)
return createStore(reducer, initialState, reduxMiddleware)

View file

@ -1,4 +1,4 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-analytics)
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-segment-analytics)
# Example app with analytics
@ -9,9 +9,9 @@
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
```bash
npx create-next-app --example with-analytics with-analytics-app
npx create-next-app --example with-segment-analytics with-segment-analytics-app
# or
yarn create next-app --example with-analytics with-analytics-app
yarn create next-app --example with-segment-analytics with-segment-analytics-app
```
### Download manually
@ -19,8 +19,8 @@ yarn create next-app --example with-analytics with-analytics-app
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-analytics
cd with-analytics
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-segment-analytics
cd with-segment-analytics
```
Install it and run:

View file

@ -1,5 +1,5 @@
{
"name": "with-analytics",
"name": "with-segment-analytics",
"scripts": {
"dev": "next",
"build": "next build",

View file

@ -15,7 +15,7 @@ module.exports = {
query: { id: post.id }
}
}),
{},
{}
)
// combine the map of post pages with the home

View file

@ -7,22 +7,22 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
.then(() => {
const server = express()
// custom route for posts
server.get('/post/:id', (req, res) => {
return app.render(req, res, '/post', {
id: req.params.id
// custom route for posts
server.get('/post/:id', (req, res) => {
return app.render(req, res, '/post', {
id: req.params.id
})
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -9,20 +9,20 @@ const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname } = parsedUrl
.then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname } = parsedUrl
if (pathname === '/service-worker.js') {
const filePath = join(__dirname, '.next', pathname)
app.serveStatic(req, res, filePath)
} else {
handle(req, res, parsedUrl)
}
if (pathname === '/service-worker.js') {
const filePath = join(__dirname, '.next', pathname)
app.serveStatic(req, res, filePath)
} else {
handle(req, res, parsedUrl)
}
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -244,15 +244,15 @@ module.exports = {
*/
textSizes: {
'xs': '.75rem', // 12px
'sm': '.875rem', // 14px
'base': '1rem', // 16px
'lg': '1.125rem', // 18px
'xl': '1.25rem', // 20px
'2xl': '1.5rem', // 24px
'3xl': '1.875rem', // 30px
'4xl': '2.25rem', // 36px
'5xl': '3rem' // 48px
'xs': '.75rem', // 12px
'sm': '.875rem', // 14px
'base': '1rem', // 16px
'lg': '1.125rem', // 18px
'xl': '1.25rem', // 20px
'2xl': '1.5rem', // 24px
'3xl': '1.875rem', // 30px
'4xl': '2.25rem', // 36px
'5xl': '3rem' // 48px
},
/*

View file

@ -14,7 +14,7 @@
"devDependencies": {
"@types/next": "^2.4.7",
"@types/react": "^16.0.36",
"@zeit/next-typescript": "0.0.8",
"@zeit/next-typescript": "0.0.11",
"typescript": "^2.7.1"
}
}

View file

@ -5,7 +5,7 @@ const {API_URL} = env
export default class extends React.Component {
static async getInitialProps () {
// fetch(`${API_URL}/some-path`)
// fetch(`${API_URL}/some-path`)
return {}
}

View file

@ -11,19 +11,19 @@ const route = pathMatch()
const match = route('/about/:name')
app.prepare()
.then(() => {
createServer((req, res) => {
const { pathname } = parse(req.url)
const params = match(pathname)
if (params === false) {
handle(req, res)
return
}
.then(() => {
createServer((req, res) => {
const { pathname } = parse(req.url)
const params = match(pathname)
if (params === false) {
handle(req, res)
return
}
app.render(req, res, '/about', params)
app.render(req, res, '/about', params)
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})

View file

@ -15,7 +15,7 @@ export default class EventEmitter {
emit (event, ...data) {
if (!this.listeners[event]) return
this.listeners[event].forEach(cb => cb(...data))
this.listeners[event].forEach(cb => cb(...data)) // eslint-disable-line standard/no-callback-literal
}
off (event, cb) {

View file

@ -2,3 +2,4 @@ export const PHASE_EXPORT = 'phase-export'
export const PHASE_PRODUCTION_BUILD = 'phase-production-build'
export const PHASE_PRODUCTION_SERVER = 'phase-production-server'
export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server'
export const PAGES_MANIFEST = 'pages-manifest.json'

View file

@ -10,8 +10,8 @@ export default ({ error, error: { name, message, module } }) => (
{module ? <h1 style={styles.heading}>Error in {module.rawRequest}</h1> : null}
{
name === 'ModuleBuildError'
? <pre style={styles.stack} dangerouslySetInnerHTML={{ __html: ansiHTML(encodeHtml(message)) }} />
: <StackTrace error={error} />
? <pre style={styles.stack} dangerouslySetInnerHTML={{ __html: ansiHTML(encodeHtml(message)) }} />
: <StackTrace error={error} />
}
</div>
)

View file

@ -18,24 +18,24 @@ export function defaultHead () {
function reduceComponents (components) {
return components
.map((c) => c.props.children)
.map((children) => React.Children.toArray(children))
.reduce((a, b) => a.concat(b), [])
.reduce((a, b) => {
if (React.Fragment && b.type === React.Fragment) {
return a.concat(React.Children.toArray(b.props.children))
}
return a.concat(b)
}, [])
.reverse()
.concat(...defaultHead())
.filter((c) => !!c)
.filter(unique())
.reverse()
.map((c) => {
const className = (c.props && c.props.className ? c.props.className + ' ' : '') + 'next-head'
return React.cloneElement(c, { className })
})
.map((c) => c.props.children)
.map((children) => React.Children.toArray(children))
.reduce((a, b) => a.concat(b), [])
.reduce((a, b) => {
if (React.Fragment && b.type === React.Fragment) {
return a.concat(React.Children.toArray(b.props.children))
}
return a.concat(b)
}, [])
.reverse()
.concat(...defaultHead())
.filter((c) => !!c)
.filter(unique())
.reverse()
.map((c) => {
const className = (c.props && c.props.className ? c.props.className + ' ' : '') + 'next-head'
return React.cloneElement(c, { className })
})
}
function mapOnServer (head) {

View file

@ -355,7 +355,7 @@ export default class Router {
}
async fetchRoute (route) {
return await this.pageLoader.loadPage(route)
return this.pageLoader.loadPage(route)
}
abortComponentLoad (as) {

View file

@ -113,11 +113,12 @@
"@taskr/esnext": "1.1.0",
"@taskr/watch": "1.1.0",
"@zeit/next-css": "0.0.7",
"babel-eslint": "8.0.1",
"babel-eslint": "8.2.2",
"babel-jest": "21.2.0",
"babel-plugin-istanbul": "4.1.5",
"babel-plugin-transform-remove-strict-mode": "0.0.2",
"babel-preset-es2015": "6.24.1",
"babel-preset-flow": "6.23.0",
"benchmark": "2.1.4",
"cheerio": "0.22.0",
"chromedriver": "2.32.3",
@ -139,7 +140,7 @@
"react": "16.2.0",
"react-dom": "16.2.0",
"rimraf": "2.6.2",
"standard": "9.0.2",
"standard": "11.0.1",
"taskr": "1.1.0",
"wd": "1.4.1"
},

View file

@ -1012,7 +1012,7 @@ module.exports = {
Or use a function:
```js
module.exports = (phase, {defaultConfig}){
module.exports = (phase, {defaultConfig}) => {
//
// https://github.com/zeit/
return {
@ -1185,6 +1185,7 @@ module.exports = {
```js
// pages/index.js
import getConfig from 'next/config'
// Only holds serverRuntimeConfig and publicRuntimeConfig from next.config.js nothing else.
const {serverRuntimeConfig, publicRuntimeConfig} = getConfig()
console.log(serverRuntimeConfig.mySecret) // Will only be available on the server side
@ -1258,7 +1259,7 @@ Simply develop your app as you normally do with Next.js. Then create a custom Ne
```js
// next.config.js
module.exports = {
exportPathMap: function() {
exportPathMap: function(defaultPathMap) {
return {
'/': { page: '/' },
'/about': { page: '/about' },

View file

@ -34,7 +34,7 @@ function getRoute (loaderContext) {
const pagesDir = resolve(loaderContext.options.context, 'pages')
const { resourcePath } = loaderContext
const dir = [pagesDir, nextPagesDir]
.find((d) => resourcePath.indexOf(d) === 0)
.find((d) => resourcePath.indexOf(d) === 0)
const path = relative(dir, resourcePath)
return '/' + path.replace(/((^|\/)index)?\.js$/, '')
}

View file

@ -0,0 +1,30 @@
// @flow
import { RawSource } from 'webpack-sources'
import { MATCH_ROUTE_NAME } from '../../utils'
import {PAGES_MANIFEST} from '../../../lib/constants'
// This plugin creates a pages-manifest.json from page entrypoints.
// This is used for mapping paths like `/` to `.next/dist/bundles/pages/index.js` when doing SSR
// It's also used by next export to provide defaultPathMap
export default class PagesManifestPlugin {
apply (compiler: any) {
compiler.plugin('emit', (compilation, callback) => {
const {entries} = compilation
const pages = {}
for (const entry of entries) {
const pagePath = MATCH_ROUTE_NAME.exec(entry.name)[1]
if (!pagePath) {
continue
}
const {name} = entry
pages[`/${pagePath.replace(/\\/g, '/')}`] = name
}
compilation.assets[PAGES_MANIFEST] = new RawSource(JSON.stringify(pages))
callback()
})
}
}

View file

@ -13,12 +13,12 @@ class PageChunkTemplatePlugin {
let routeName = MATCH_ROUTE_NAME.exec(chunk.name)[1]
// We need to convert \ into / when we are in windows
// to get the proper route name
// Here we need to do windows check because it's possible
// to have "\" in the filename in unix.
// Anyway if someone did that, he'll be having issues here.
// But that's something we cannot avoid.
// We need to convert \ into / when we are in windows
// to get the proper route name
// Here we need to do windows check because it's possible
// to have "\" in the filename in unix.
// Anyway if someone did that, he'll be having issues here.
// But that's something we cannot avoid.
if (/^win/.test(process.platform)) {
routeName = routeName.replace(/\\/g, '/')
}

View file

@ -10,7 +10,7 @@ export default class UnlinkFilePlugin {
apply (compiler) {
compiler.plugin('after-emit', (compilation, callback) => {
const removed = Object.keys(this.prevAssets)
.filter((a) => IS_BUNDLED_PAGE.test(a) && !compilation.assets[a])
.filter((a) => IS_BUNDLED_PAGE.test(a) && !compilation.assets[a])
this.prevAssets = compilation.assets
@ -23,7 +23,7 @@ export default class UnlinkFilePlugin {
throw err
}
}))
.then(() => callback(), callback)
.then(() => callback(), callback)
})
}
}

View file

@ -10,6 +10,7 @@ import PagesPlugin from './plugins/pages-plugin'
import NextJsSsrImportPlugin from './plugins/nextjs-ssr-import'
import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin'
import UnlinkFilePlugin from './plugins/unlink-file-plugin'
import PagesManifestPlugin from './plugins/pages-manifest-plugin'
import findBabelConfig from './babel/find-config'
const nextDir = path.join(__dirname, '..', '..', '..')
@ -254,6 +255,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production')
}),
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
isServer && new PagesManifestPlugin(),
!isServer && new PagesPlugin(),
!isServer && new DynamicChunksPlugin(),
isServer && new NextJsSsrImportPlugin(),

View file

@ -9,7 +9,7 @@ export async function getPages (dir, {dev, isServer, pageExtensions}) {
return getPageEntries(pageFiles, {isServer, pageExtensions})
}
async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
export async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
let pages
if (dev) {

View file

@ -1,3 +1,4 @@
// @flow
import findUp from 'find-up'
const cache = new Map()
@ -11,28 +12,29 @@ const defaultConfig = {
configOrigin: 'default',
useFileSystemPublicRoutes: true,
generateEtags: true,
pageExtensions: ['jsx', 'js'] // jsx before js because otherwise regex matching will match js first
pageExtensions: ['jsx', 'js']
}
export default function getConfig (phase, dir, customConfig) {
export default function getConfig (phase: string, dir: string, customConfig?: ?Object) {
if (!cache.has(dir)) {
cache.set(dir, loadConfig(phase, dir, customConfig))
}
return cache.get(dir)
}
export function loadConfig (phase, dir, customConfig) {
export function loadConfig (phase: string, dir: string, customConfig?: ?Object) {
if (customConfig && typeof customConfig === 'object') {
customConfig.configOrigin = 'server'
return withDefaults(customConfig)
}
const path = findUp.sync('next.config.js', {
const path: string = findUp.sync('next.config.js', {
cwd: dir
})
let userConfig = {}
if (path && path.length) {
// $FlowFixMe
const userConfigModule = require(path)
userConfig = userConfigModule.default || userConfigModule
if (typeof userConfigModule === 'function') {
@ -44,6 +46,6 @@ export function loadConfig (phase, dir, customConfig) {
return withDefaults(userConfig)
}
function withDefaults (config) {
function withDefaults (config: Object) {
return Object.assign({}, defaultConfig, config)
}

View file

@ -5,10 +5,9 @@ import walk from 'walk'
import { extname, resolve, join, dirname, sep } from 'path'
import { existsSync, readFileSync, writeFileSync } from 'fs'
import getConfig from './config'
import {PHASE_EXPORT} from '../lib/constants'
import {PHASE_EXPORT, PAGES_MANIFEST} from '../lib/constants'
import { renderToHTML } from './render'
import { getAvailableChunks } from './utils'
import { printAndExit } from '../lib/utils'
import { setAssetPrefix } from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
@ -17,7 +16,7 @@ export default async function (dir, options, configuration) {
const nextConfig = configuration || getConfig(PHASE_EXPORT, dir)
const nextDir = join(dir, nextConfig.distDir)
log(` using build directory: ${nextDir}`)
log(`> using build directory: ${nextDir}`)
if (!existsSync(nextDir)) {
console.error(
@ -27,6 +26,17 @@ export default async function (dir, options, configuration) {
}
const buildId = readFileSync(join(nextDir, 'BUILD_ID'), 'utf8')
const pagesManifest = require(join(nextDir, 'dist', PAGES_MANIFEST))
const pages = Object.keys(pagesManifest)
const defaultPathMap = {}
for (const page of pages) {
if (page === '/_document') {
continue
}
defaultPathMap[page] = { page }
}
// Initialize the output directory
const outDir = options.outdir
@ -73,13 +83,13 @@ export default async function (dir, options, configuration) {
// Get the exportPathMap from the `next.config.js`
if (typeof nextConfig.exportPathMap !== 'function') {
printAndExit(
'> Could not find "exportPathMap" function inside "next.config.js"\n' +
'> "next export" uses that function to build html pages.'
)
console.log('> No "exportPathMap" found in "next.config.js". Generating map from "./pages"')
nextConfig.exportPathMap = async (defaultMap) => {
return defaultMap
}
}
const exportPathMap = await nextConfig.exportPathMap()
const exportPathMap = await nextConfig.exportPathMap(defaultPathMap)
const exportPaths = Object.keys(exportPathMap)
// Start the rendering process
@ -115,7 +125,7 @@ export default async function (dir, options, configuration) {
}
for (const path of exportPaths) {
log(` exporting path: ${path}`)
log(`> exporting path: ${path}`)
if (!path.startsWith('/')) {
throw new Error(`path "${path}" doesn't start with a backslash`)
}

View file

@ -149,11 +149,11 @@ export default class HotReloader {
)
const failedChunkNames = new Set(compilation.errors
.map((e) => e.module.reasons)
.reduce((a, b) => a.concat(b), [])
.map((r) => r.module.chunks)
.reduce((a, b) => a.concat(b), [])
.map((c) => c.name))
.map((e) => e.module.reasons)
.reduce((a, b) => a.concat(b), [])
.map((r) => r.module.chunks)
.reduce((a, b) => a.concat(b), [])
.map((c) => c.name))
const chunkHashes = new Map(
compilation.chunks

View file

@ -1,3 +1,4 @@
/* eslint-disable import/first, no-return-await */
require('@zeit/source-map-support').install()
import { resolve, join, sep } from 'path'
import { parse as parseUrl } from 'url'

View file

@ -198,15 +198,15 @@ function serializeError (dev, err) {
export function serveStatic (req, res, path) {
return new Promise((resolve, reject) => {
send(req, path)
.on('directory', () => {
.on('directory', () => {
// We don't allow directories to be read.
const err = new Error('No directory access')
err.code = 'ENOENT'
reject(err)
})
.on('error', reject)
.pipe(res)
.on('finish', resolve)
const err = new Error('No directory access')
err.code = 'ENOENT'
reject(err)
})
.on('error', reject)
.pipe(res)
.on('finish', resolve)
})
}

View file

@ -1,5 +1,5 @@
import {join, parse, normalize, sep} from 'path'
import fs from 'mz/fs'
import {join, posix} from 'path'
import {PAGES_MANIFEST} from '../lib/constants'
export function pageNotFoundError (page) {
const err = new Error(`Cannot find module for page: ${page}`)
@ -18,13 +18,8 @@ export function normalizePagePath (page) {
page = `/${page}`
}
// Windows compatibility
if (sep !== '/') {
page = page.replace(/\//g, sep)
}
// Throw when using ../ etc in the pathname
const resolvedPage = normalize(page)
const resolvedPage = posix.normalize(page)
if (page !== resolvedPage) {
throw new Error('Requested and resolved page mismatch')
}
@ -33,7 +28,8 @@ export function normalizePagePath (page) {
}
export function getPagePath (page, {dir, dist}) {
const pageBundlesPath = join(dir, dist, 'dist', 'bundles', 'pages')
const serverBuildPath = join(dir, dist, 'dist')
const pagesManifest = require(join(serverBuildPath, PAGES_MANIFEST))
try {
page = normalizePagePath(page)
@ -42,24 +38,14 @@ export function getPagePath (page, {dir, dist}) {
throw pageNotFoundError(page)
}
const pagePath = join(pageBundlesPath, page) // Path to the page that is to be loaded
// Don't allow wandering outside of the bundles directory
const pathDir = parse(pagePath).dir
if (pathDir.indexOf(pageBundlesPath) !== 0) {
console.error('Resolved page path goes outside of bundles path')
if (!pagesManifest[page]) {
throw pageNotFoundError(page)
}
return pagePath
return join(serverBuildPath, pagesManifest[page])
}
export default async function requirePage (page, {dir, dist}) {
const pagePath = getPagePath(page, {dir, dist}) + '.js'
const fileExists = await fs.exists(pagePath)
if (!fileExists) {
throw pageNotFoundError(page)
}
const pagePath = getPagePath(page, {dir, dist})
return require(pagePath)
}

View file

@ -2,7 +2,7 @@ import React from 'react'
export default class AsyncProps extends React.Component {
static async getInitialProps () {
return await fetchData()
return fetchData()
}
render () {

View file

@ -29,7 +29,7 @@ export default (context, render) => {
browser.close()
})
it('should not show the default HMR error overlay', async() => {
it('should not show the default HMR error overlay', async () => {
const browser = await webdriver(context.appPort, '/hmr/about')
const text = await browser
.elementByCss('p').text()

View file

@ -70,7 +70,7 @@ describe('Custom Server', () => {
expect($dynamic('img').attr('src')).toBe(`http://127.0.0.1:${context.appPort}/static/myimage.png`)
})
it('should support next/asset in client side', async() => {
it('should support next/asset in client side', async () => {
const browser = await webdriver(context.appPort, '/')
await browser
.elementByCss('#go-asset').click()

View file

@ -65,9 +65,9 @@ describe('Production Usage', () => {
it('should navigate via client side', async () => {
const browser = await webdriver(appPort, '/')
const text = await browser
.elementByCss('a').click()
.waitForElementByCss('.about-page')
.elementByCss('div').text()
.elementByCss('a').click()
.waitForElementByCss('.about-page')
.elementByCss('div').text()
expect(text).toBe('About Page')
browser.close()
@ -98,8 +98,8 @@ describe('Production Usage', () => {
it('should reload the page on page script error', async () => {
const browser = await webdriver(appPort, '/counter')
const counter = await browser
.elementByCss('#increase').click().click()
.elementByCss('#counter').text()
.elementByCss('#increase').click().click()
.elementByCss('#counter').text()
expect(counter).toBe('Counter: 2')
// When we go to the 404 page, it'll do a hard reload.
@ -120,8 +120,8 @@ describe('Production Usage', () => {
it('should reload the page on page script error with prefetch', async () => {
const browser = await webdriver(appPort, '/counter')
const counter = await browser
.elementByCss('#increase').click().click()
.elementByCss('#counter').text()
.elementByCss('#increase').click().click()
.elementByCss('#counter').text()
expect(counter).toBe('Counter: 2')
// Let the browser to prefetch the page and error it on the console.

View file

@ -7,7 +7,7 @@ export default function (context) {
it('should render the home page', async () => {
const browser = await webdriver(context.port, '/')
const text = await browser
.elementByCss('#home-page p').text()
.elementByCss('#home-page p').text()
expect(text).toBe('This is the home page')
browser.close()
@ -16,9 +16,9 @@ export default function (context) {
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()
.elementByCss('#about-via-link').click()
.waitForElementByCss('#about-page')
.elementByCss('#about-page p').text()
expect(text).toBe('This is the About page')
browser.close()
@ -27,9 +27,9 @@ export default function (context) {
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()
.elementByCss('#about-via-router').click()
.waitForElementByCss('#about-page')
.elementByCss('#about-page p').text()
expect(text).toBe('This is the About page')
browser.close()
@ -38,11 +38,11 @@ export default function (context) {
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()
.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()
@ -51,9 +51,9 @@ export default function (context) {
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()
.elementByCss('#get-initial-props').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
expect(text).toBe('cool dynamic text')
browser.close()
@ -62,9 +62,9 @@ export default function (context) {
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()
.elementByCss('#dynamic-1').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
expect(text).toBe('next export is nice')
browser.close()
@ -73,11 +73,11 @@ export default function (context) {
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()
.elementByCss('#counter').click()
.waitForElementByCss('#counter-page')
.elementByCss('#counter-increase').click()
.elementByCss('#counter-increase').click()
.elementByCss('#counter-page p').text()
expect(text).toBe('Counter: 2')
@ -97,15 +97,15 @@ export default function (context) {
it('should render dynamic import components in the client', async () => {
const browser = await webdriver(context.port, '/')
await browser
.elementByCss('#dynamic-imports-page').click()
.waitForElementByCss('#dynamic-imports-page')
.elementByCss('#dynamic-imports-page').click()
.waitForElementByCss('#dynamic-imports-page')
// Wait until browser loads the dynamic import chunk
// TODO: We may need to find a better way to do this
await waitFor(5000)
const text = await browser
.elementByCss('#dynamic-imports-page p').text()
.elementByCss('#dynamic-imports-page p').text()
expect(text).toBe('Welcome to dynamic imports.')
browser.close()
@ -116,9 +116,9 @@ export default function (context) {
// Check for the query string content
const text = await browser
.elementByCss('#with-hash').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
.elementByCss('#with-hash').click()
.waitForElementByCss('#dynamic-page')
.elementByCss('#dynamic-page p').text()
expect(text).toBe('zeit is awesome')
@ -139,9 +139,9 @@ export default function (context) {
const browser = await webdriver(context.port, '/button-link')
const text = await browser
.elementByCss('button').click()
.waitForElementByCss('#home-page')
.elementByCss('#home-page p').text()
.elementByCss('button').click()
.waitForElementByCss('#home-page')
.elementByCss('#home-page p').text()
expect(text).toBe('This is the home page')
browser.close()
@ -151,9 +151,9 @@ export default function (context) {
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()
.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()
@ -162,9 +162,9 @@ export default function (context) {
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()
.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

@ -18,27 +18,27 @@ export default function (context) {
expect(filePathLink).toEqual('/file-name.md')
})
it('should render a page with getInitialProps', async() => {
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() => {
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/)
})
it('should render pages with dynamic imports', async() => {
it('should render pages with dynamic imports', async () => {
const html = await renderViaHTTP(context.port, '/dynamic-imports')
expect(html).toMatch(/Welcome to dynamic imports/)
})
it('should render paths with extensions', async() => {
it('should render paths with extensions', async () => {
const html = await renderViaHTTP(context.port, '/file-name.md')
expect(html).toMatch(/this file has an extension/)
})
it('should give empty object for query if there is no query', async() => {
it('should give empty object for query if there is no query', async () => {
const html = await renderViaHTTP(context.port, '/get-initial-props-with-no-query')
expect(html).toMatch(/Query is: {}/)
})

View file

@ -0,0 +1,3 @@
module.exports = {
test: 'error'
}

View file

@ -0,0 +1,6 @@
{
"/index": "bundles/pages/index.js",
"/world": "bundles/pages/world.js",
"/_error": "bundles/pages/_error.js",
"/non-existent-child": "bundles/pages/non-existent-child.js"
}

View file

@ -1,12 +1,10 @@
/* global describe, it, expect */
import { join, sep } from 'path'
import { join } from 'path'
import requirePage, {getPagePath, normalizePagePath, pageNotFoundError} from '../../dist/server/require'
const dir = '/path/to/some/project'
const dist = '.next'
const pathToBundles = join(dir, dist, 'dist', 'bundles', 'pages')
const sep = '/'
const pathToBundles = join(__dirname, '_resolvedata', 'dist', 'bundles', 'pages')
describe('pageNotFoundError', () => {
it('Should throw error with ENOENT code', () => {
@ -42,17 +40,17 @@ describe('normalizePagePath', () => {
describe('getPagePath', () => {
it('Should append /index to the / page', () => {
const pagePath = getPagePath('/', {dir, dist})
expect(pagePath).toBe(join(pathToBundles, `${sep}index`))
const pagePath = getPagePath('/', {dir: __dirname, dist: '_resolvedata'})
expect(pagePath).toBe(join(pathToBundles, `${sep}index.js`))
})
it('Should prepend / when a page does not have it', () => {
const pagePath = getPagePath('_error', {dir, dist})
expect(pagePath).toBe(join(pathToBundles, `${sep}_error`))
const pagePath = getPagePath('_error', {dir: __dirname, dist: '_resolvedata'})
expect(pagePath).toBe(join(pathToBundles, `${sep}_error.js`))
})
it('Should throw with paths containing ../', () => {
expect(() => getPagePath('/../../package.json', {dir, dist})).toThrow()
expect(() => getPagePath('/../../package.json', {dir: __dirname, dist: '_resolvedata'})).toThrow()
})
})

Some files were not shown because too many files have changed in this diff Show more