mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Merge branch 'canary' into master
This commit is contained in:
commit
b75a88790a
|
@ -21,6 +21,7 @@ const {
|
||||||
__NEXT_DATA__: {
|
__NEXT_DATA__: {
|
||||||
props,
|
props,
|
||||||
err,
|
err,
|
||||||
|
page,
|
||||||
pathname,
|
pathname,
|
||||||
query,
|
query,
|
||||||
buildId,
|
buildId,
|
||||||
|
@ -76,7 +77,7 @@ export default async ({ ErrorDebugComponent: passedDebugComponent, stripAnsi: pa
|
||||||
ErrorComponent = await pageLoader.loadPage('/_error')
|
ErrorComponent = await pageLoader.loadPage('/_error')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Component = await pageLoader.loadPage(pathname)
|
Component = await pageLoader.loadPage(page)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(stripAnsi(`${err.message}\n${err.stack}`))
|
console.error(stripAnsi(`${err.message}\n${err.stack}`))
|
||||||
Component = ErrorComponent
|
Component = ErrorComponent
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
# The poweredByHeader has been removed
|
|
||||||
|
|
||||||
#### Why This Error Occurred
|
|
||||||
|
|
||||||
Starting at Next.js version 5.0.0 the `poweredByHeader` option has been removed.
|
|
||||||
|
|
||||||
#### Possible Ways to Fix It
|
|
||||||
|
|
||||||
If you still want to remove `x-powered-by` you can use one of the custom-server examples.
|
|
||||||
|
|
||||||
And then manually remove the header using `res.removeHeader('x-powered-by')`
|
|
||||||
|
|
||||||
### Useful Links
|
|
||||||
|
|
||||||
- [Custom Server documentation + examples](https://github.com/zeit/next.js#custom-server-and-routing)
|
|
|
@ -5,9 +5,9 @@ import React, { Children } from 'react'
|
||||||
const ActiveLink = ({ router, children, ...props }) => {
|
const ActiveLink = ({ router, children, ...props }) => {
|
||||||
const child = Children.only(children)
|
const child = Children.only(children)
|
||||||
|
|
||||||
let className = child.props.className || ''
|
let className = child.props.className || null
|
||||||
if (router.pathname === props.href && props.activeClassName) {
|
if (router.pathname === props.href && props.activeClassName) {
|
||||||
className = `${className} ${props.activeClassName}`.trim()
|
className = `${className !== null ? className : ''} ${props.activeClassName}`.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
delete props.activeClassName
|
delete props.activeClassName
|
||||||
|
|
14
examples/custom-charset/package.json
Normal file
14
examples/custom-charset/package.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "custom-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node server.js",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "NODE_ENV=production node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "latest",
|
||||||
|
"react": "^16.0.0",
|
||||||
|
"react-dom": "^16.0.0"
|
||||||
|
}
|
||||||
|
}
|
34
examples/custom-charset/pages/README.md
Normal file
34
examples/custom-charset/pages/README.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/custom-server)
|
||||||
|
|
||||||
|
# Custom server example
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### 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/custom-charset
|
||||||
|
cd custom-charset
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
The HTTP/1.1 specification says - if charset is not set in the http header then the browser defaults use ISO-8859-1.
|
||||||
|
For languages like Polish, Albanian, Hungarian, Czech, Slovak, Slovene, there will be broken characters encoding from SSR.
|
||||||
|
|
||||||
|
You can overwrite Content-Type in getInitialProps. But if you want to handle it as a server side concern, you can use this as an simple example.
|
3
examples/custom-charset/pages/index.js
Normal file
3
examples/custom-charset/pages/index.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default () => <div>áéíóöúü</div>
|
21
examples/custom-charset/server.js
Normal file
21
examples/custom-charset/server.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const { createServer } = require('http')
|
||||||
|
const { parse } = require('url')
|
||||||
|
const next = require('next')
|
||||||
|
|
||||||
|
const port = parseInt(process.env.PORT, 10) || 3000
|
||||||
|
const dev = process.env.NODE_ENV !== 'production'
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
.listen(port, (err) => {
|
||||||
|
if (err) throw err
|
||||||
|
console.log(`> Ready on http://localhost:${port}`)
|
||||||
|
})
|
||||||
|
})
|
|
@ -45,26 +45,32 @@ function getCacheKey (req) {
|
||||||
return `${req.url}`
|
return `${req.url}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAndCache (req, res, pagePath, queryParams) {
|
async function renderAndCache (req, res, pagePath, queryParams) {
|
||||||
const key = getCacheKey(req)
|
const key = getCacheKey(req)
|
||||||
|
|
||||||
// If we have a page in the cache, let's serve it
|
// If we have a page in the cache, let's serve it
|
||||||
if (ssrCache.has(key)) {
|
if (ssrCache.has(key)) {
|
||||||
console.log(`CACHE HIT: ${key}`)
|
res.setHeader('x-cache', 'HIT')
|
||||||
res.send(ssrCache.get(key))
|
res.send(ssrCache.get(key))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
// If not let's render the page into HTML
|
// If not let's render the page into HTML
|
||||||
app.renderToHTML(req, res, pagePath, queryParams)
|
const html = await app.renderToHTML(req, res, pagePath, queryParams)
|
||||||
.then((html) => {
|
|
||||||
|
// Something is wrong with the request, let's skip the cache
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
res.send(html)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Let's cache this page
|
// Let's cache this page
|
||||||
console.log(`CACHE MISS: ${key}`)
|
|
||||||
ssrCache.set(key, html)
|
ssrCache.set(key, html)
|
||||||
|
|
||||||
|
res.setHeader('x-cache', 'MISS')
|
||||||
res.send(html)
|
res.send(html)
|
||||||
})
|
} catch (err) {
|
||||||
.catch((err) => {
|
|
||||||
app.renderError(err, req, res, pagePath, queryParams)
|
app.renderError(err, req, res, pagePath, queryParams)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"react": "latest,
|
"react": "latest",
|
||||||
"react-dom": "latest"
|
"react-dom": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -25,7 +25,7 @@ class Index extends React.Component {
|
||||||
|
|
||||||
// Force a reload of all the current queries now that the user is
|
// Force a reload of all the current queries now that the user is
|
||||||
// logged in, so we don't accidentally leave any state around.
|
// logged in, so we don't accidentally leave any state around.
|
||||||
this.props.client.resetStore().then(() => {
|
this.props.client.cache.reset().then(() => {
|
||||||
// Redirect to a more useful page when signed out
|
// Redirect to a more useful page when signed out
|
||||||
redirect({}, '/signin')
|
redirect({}, '/signin')
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,7 +18,7 @@ const test = require('ava')
|
||||||
* Apollo that we can clear, etc
|
* Apollo that we can clear, etc
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const apolloFilePath = require.resolve('../lib/init-apollo')
|
const apolloFilePath = require.resolve('../lib/initApollo')
|
||||||
|
|
||||||
test.beforeEach(() => {
|
test.beforeEach(() => {
|
||||||
// Clean up the cache
|
// Clean up the cache
|
||||||
|
|
|
@ -47,5 +47,3 @@ 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.
|
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.
|
This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
|
||||||
|
|
||||||
*Note: Apollo uses Redux internally; if you're interested in integrating the client with your existing Redux store check out the [`with-apollo-and-redux`](https://github.com/zeit/next.js/tree/master/examples/with-apollo-and-redux) example.*
|
|
||||||
|
|
41
examples/with-draft-js/README.md
Normal file
41
examples/with-draft-js/README.md
Normal 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-draft-js)
|
||||||
|
# DraftJS Medium editor inspiration
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Using `create-next-app`
|
||||||
|
|
||||||
|
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -g create-next-app
|
||||||
|
create-next-app --example with-draft-js
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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-draft-js
|
||||||
|
cd with-draft-js
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
Have you ever wanted to have an editor like medium.com in your Next.js app? DraftJS is avalaible for SSR, but some plugins like the toolbar are using `window`, which does not work when doing SSR.
|
||||||
|
|
||||||
|
This example aims to provides a fully customizable example of the famous medium editor with DraftJS. The goal was to get it as customizable as possible, and fully working with Next.js without using the react-no-ssr package.
|
17
examples/with-draft-js/package.json
Normal file
17
examples/with-draft-js/package.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "with-draft-js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Asten Mies",
|
||||||
|
"license": "ISC",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"draft-js": "0.10.5",
|
||||||
|
"next": "5.0.0",
|
||||||
|
"react": "16.2.0",
|
||||||
|
"react-dom": "16.2.0"
|
||||||
|
}
|
||||||
|
}
|
270
examples/with-draft-js/pages/index.js
Normal file
270
examples/with-draft-js/pages/index.js
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {
|
||||||
|
Editor,
|
||||||
|
EditorState,
|
||||||
|
RichUtils,
|
||||||
|
convertToRaw,
|
||||||
|
convertFromRaw
|
||||||
|
} from 'draft-js'
|
||||||
|
|
||||||
|
export default class App extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
editorState: EditorState.createWithContent(convertFromRaw(initialData)),
|
||||||
|
showToolbar: false,
|
||||||
|
windowWidth: 0,
|
||||||
|
toolbarMeasures: {
|
||||||
|
w: 0,
|
||||||
|
h: 0
|
||||||
|
},
|
||||||
|
selectionMeasures: {
|
||||||
|
w: 0,
|
||||||
|
h: 0
|
||||||
|
},
|
||||||
|
selectionCoordinates: {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
},
|
||||||
|
toolbarCoordinates: {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
},
|
||||||
|
showRawData: false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focus = () => this.editor.focus()
|
||||||
|
this.onChange = (editorState) => this.setState({editorState})
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickEditor = () => {
|
||||||
|
this.focus()
|
||||||
|
this.checkSelectedText()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1- Check if some text is selected
|
||||||
|
checkSelectedText = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const text = window.getSelection().toString()
|
||||||
|
if (text !== '') {
|
||||||
|
// 1-a Define the selection coordinates
|
||||||
|
this.setSelectionXY()
|
||||||
|
} else {
|
||||||
|
// Hide the toolbar if nothing is selected
|
||||||
|
this.setState({
|
||||||
|
showToolbar: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2- Identify the selection coordinates
|
||||||
|
setSelectionXY = () => {
|
||||||
|
var r = window.getSelection().getRangeAt(0).getBoundingClientRect()
|
||||||
|
var relative = document.body.parentNode.getBoundingClientRect()
|
||||||
|
// 2-a Set the selection coordinates in the state
|
||||||
|
this.setState({
|
||||||
|
selectionCoordinates: r,
|
||||||
|
windowWidth: relative.width,
|
||||||
|
selectionMeasures: {
|
||||||
|
w: r.width,
|
||||||
|
h: r.height
|
||||||
|
}
|
||||||
|
}, () => this.showToolbar())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3- Show the toolbar
|
||||||
|
showToolbar = () => {
|
||||||
|
this.setState({
|
||||||
|
showToolbar: true
|
||||||
|
}, () => this.measureToolbar())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4- The toolbar was hidden until now
|
||||||
|
measureToolbar = () => {
|
||||||
|
// 4-a Define the toolbar width and height, as it is now visible
|
||||||
|
this.setState({
|
||||||
|
toolbarMeasures: {
|
||||||
|
w: this.elemWidth,
|
||||||
|
h: this.elemHeight
|
||||||
|
}
|
||||||
|
}, () => this.setToolbarXY())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5- Now that we have all measures, define toolbar coordinates
|
||||||
|
setToolbarXY = () => {
|
||||||
|
let coordinates = {}
|
||||||
|
|
||||||
|
const { selectionMeasures, selectionCoordinates, toolbarMeasures, windowWidth } = this.state
|
||||||
|
|
||||||
|
const hiddenTop = selectionCoordinates.y < toolbarMeasures.h
|
||||||
|
const hiddenRight = windowWidth - selectionCoordinates.x < toolbarMeasures.w / 2
|
||||||
|
const hiddenLeft = selectionCoordinates.x < toolbarMeasures.w / 2
|
||||||
|
|
||||||
|
const normalX = selectionCoordinates.x - (toolbarMeasures.w / 2) + (selectionMeasures.w / 2)
|
||||||
|
const normalY = selectionCoordinates.y - toolbarMeasures.h
|
||||||
|
|
||||||
|
const invertedY = selectionCoordinates.y + selectionMeasures.h
|
||||||
|
const moveXToLeft = windowWidth - toolbarMeasures.w
|
||||||
|
const moveXToRight = 0
|
||||||
|
|
||||||
|
coordinates = {
|
||||||
|
x: normalX,
|
||||||
|
y: normalY
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiddenTop) {
|
||||||
|
coordinates.y = invertedY
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiddenRight) {
|
||||||
|
coordinates.x = moveXToLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiddenLeft) {
|
||||||
|
coordinates.x = moveXToRight
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
toolbarCoordinates: coordinates
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyCommand = (command) => {
|
||||||
|
const {editorState} = this.state
|
||||||
|
const newState = RichUtils.handleKeyCommand(editorState, command)
|
||||||
|
if (newState) {
|
||||||
|
this.onChange(newState)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleToolbar = (inlineStyle) => {
|
||||||
|
this.onChange(
|
||||||
|
RichUtils.toggleInlineStyle(
|
||||||
|
this.state.editorState,
|
||||||
|
inlineStyle
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {editorState} = this.state
|
||||||
|
// Make sure we're not on the ssr
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
// Let's stick the toolbar to the selection
|
||||||
|
// when the window is resized
|
||||||
|
window.addEventListener('resize', this.checkSelectedText)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolbarStyle = {
|
||||||
|
display: this.state.showToolbar ? 'block' : 'none',
|
||||||
|
backgroundColor: 'black',
|
||||||
|
color: 'white',
|
||||||
|
position: 'absolute',
|
||||||
|
left: this.state.toolbarCoordinates.x,
|
||||||
|
top: this.state.toolbarCoordinates.y,
|
||||||
|
zIndex: 999,
|
||||||
|
padding: 10
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
ref={(elem) => {
|
||||||
|
this.elemWidth = elem ? elem.clientWidth : 0
|
||||||
|
this.elemHeight = elem ? elem.clientHeight : 0
|
||||||
|
}}
|
||||||
|
style={toolbarStyle}
|
||||||
|
>
|
||||||
|
<ToolBar
|
||||||
|
editorState={editorState}
|
||||||
|
onToggle={this.toggleToolbar}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div onClick={this.onClickEditor} onBlur={this.checkSelectedText}>
|
||||||
|
<Editor
|
||||||
|
customStyleMap={styleMap}
|
||||||
|
editorState={editorState}
|
||||||
|
handleKeyCommand={this.handleKeyCommand}
|
||||||
|
onChange={this.onChange}
|
||||||
|
placeholder='Tell a story...'
|
||||||
|
spellCheck={false}
|
||||||
|
ref={(element) => { this.editor = element }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 40 }}>
|
||||||
|
<button onClick={() => this.setState({showRawData: !this.state.showRawData})}>
|
||||||
|
{!this.state.showRawData ? 'Show' : 'Hide'} Raw Data
|
||||||
|
</button><br />
|
||||||
|
{this.state.showRawData && JSON.stringify(convertToRaw(editorState.getCurrentContent()))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom overrides for each style
|
||||||
|
const styleMap = {
|
||||||
|
CODE: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
fontFamily: '"Inconsolata", "Menlo", "Consolas", monospace',
|
||||||
|
fontSize: 16,
|
||||||
|
padding: 4
|
||||||
|
},
|
||||||
|
BOLD: {
|
||||||
|
color: '#395296',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
},
|
||||||
|
ANYCUSTOMSTYLE: {
|
||||||
|
color: '#00e400'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ToolbarButton extends React.Component {
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this.onToggle = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.props.onToggle(this.props.style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const buttonStyle = {
|
||||||
|
padding: 10
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span onMouseDown={this.onToggle} style={buttonStyle}>
|
||||||
|
{ this.props.label }
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolbarItems = [
|
||||||
|
{label: 'Bold', style: 'BOLD'},
|
||||||
|
{label: 'Italic', style: 'ITALIC'},
|
||||||
|
{label: 'Underline', style: 'UNDERLINE'},
|
||||||
|
{label: 'Code', style: 'CODE'},
|
||||||
|
{label: 'Surprise', style: 'ANYCUSTOMSTYLE'}
|
||||||
|
]
|
||||||
|
|
||||||
|
const ToolBar = (props) => {
|
||||||
|
var currentStyle = props.editorState.getCurrentInlineStyle()
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{toolbarItems.map(toolbarItem =>
|
||||||
|
<ToolbarButton
|
||||||
|
key={toolbarItem.label}
|
||||||
|
active={currentStyle.has(toolbarItem.style)}
|
||||||
|
label={toolbarItem.label}
|
||||||
|
onToggle={props.onToggle}
|
||||||
|
style={toolbarItem.style}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialData = {'blocks': [{'key': '16d0k', 'text': 'You can edit this text.', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [{'offset': 0, 'length': 23, 'style': 'BOLD'}], 'entityRanges': [], 'data': {}}, {'key': '98peq', 'text': '', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [], 'data': {}}, {'key': 'ecmnc', 'text': 'Luke Skywalker has vanished. In his absence, the sinister FIRST ORDER has risen from the ashes of the Empire and will not rest until Skywalker, the last Jedi, has been destroyed.', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [{'offset': 0, 'length': 14, 'style': 'BOLD'}, {'offset': 133, 'length': 9, 'style': 'BOLD'}], 'entityRanges': [], 'data': {}}, {'key': 'fe2gn', 'text': '', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [], 'entityRanges': [], 'data': {}}, {'key': '4481k', 'text': 'With the support of the REPUBLIC, General Leia Organa leads a brave RESISTANCE. She is desperate to find her brother Luke and gain his help in restoring peace and justice to the galaxy.', 'type': 'unstyled', 'depth': 0, 'inlineStyleRanges': [{'offset': 34, 'length': 19, 'style': 'BOLD'}, {'offset': 117, 'length': 4, 'style': 'BOLD'}, {'offset': 68, 'length': 10, 'style': 'ANYCUSTOMSTYLE'}], 'entityRanges': [], 'data': {}}], 'entityMap': {}}
|
|
@ -23,10 +23,11 @@ cd with-firebase-authentication
|
||||||
```
|
```
|
||||||
|
|
||||||
Set up firebase:
|
Set up firebase:
|
||||||
- create a project
|
- Create a project at the [Firebase console](https://console.firebase.google.com/).
|
||||||
- get your service account credentials and client credentials and set both in firebaseCredentials.js
|
- Get your account credentials from the Firebase console at *settings>service accounts*, where you can click on *generate new private key* and download the credentials as a json file. It will contain keys such as `project_id`, `client_email` and `client id`. Now copy them into your project in the `credentials/server.js` file.
|
||||||
- set your firebase database url in server.js
|
- Get your authentication credentials from the Firebase console under *authentication>users>web setup*. It will include keys like `apiKey`, `authDomain` and `databaseUrl` and it goes into your project in `credentials/client.js`.
|
||||||
- on the firebase Authentication console, select Google as your provider
|
- Copy the `databaseUrl` key you got in the last step into `server.js` in the corresponding line.
|
||||||
|
- Back at the Firebase web console, go to *authentication>signup method* and select *Google*.
|
||||||
|
|
||||||
Install it and run:
|
Install it and run:
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
"body-parser": "^1.17.1",
|
"body-parser": "^1.17.1",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"express-session": "^1.15.2",
|
"express-session": "^1.15.2",
|
||||||
"firebase": "^3.7.5",
|
"firebase": "^4.9.1",
|
||||||
"firebase-admin": "^4.2.0",
|
"firebase-admin": "^5.8.2",
|
||||||
"isomorphic-unfetch": "2.0.0",
|
"isomorphic-unfetch": "2.0.0",
|
||||||
"next": "latest",
|
"next": "latest",
|
||||||
"react": "^16.0.0",
|
"react": "^16.0.0",
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { renderStatic } from 'glamor/server'
|
||||||
export default class MyDocument extends Document {
|
export default class MyDocument extends Document {
|
||||||
static async getInitialProps ({ renderPage }) {
|
static async getInitialProps ({ renderPage }) {
|
||||||
const page = renderPage()
|
const page = renderPage()
|
||||||
const styles = renderStatic(() => page.html)
|
const styles = renderStatic(() => page.html || page.errorHtml)
|
||||||
return { ...page, ...styles }
|
return { ...page, ...styles }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { renderStatic } from 'glamor/server'
|
||||||
export default class MyDocument extends Document {
|
export default class MyDocument extends Document {
|
||||||
static async getInitialProps ({ renderPage }) {
|
static async getInitialProps ({ renderPage }) {
|
||||||
const page = renderPage()
|
const page = renderPage()
|
||||||
const styles = renderStatic(() => page.html)
|
const styles = renderStatic(() => page.html || page.errorHtml)
|
||||||
return { ...page, ...styles }
|
return { ...page, ...styles }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,5 @@
|
||||||
"react": "^16.0.0",
|
"react": "^16.0.0",
|
||||||
"react-dom": "^16.0.0",
|
"react-dom": "^16.0.0",
|
||||||
"sass-loader": "^6.0.6"
|
"sass-loader": "^6.0.6"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"now": "^8.3.10"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ const Store = types
|
||||||
// mobx-state-tree doesn't allow anonymous callbacks changing data
|
// mobx-state-tree doesn't allow anonymous callbacks changing data
|
||||||
// pass off to another action instead
|
// pass off to another action instead
|
||||||
self.update()
|
self.update()
|
||||||
})
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function update () {
|
function update () {
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Store {
|
||||||
this.timer = setInterval(() => {
|
this.timer = setInterval(() => {
|
||||||
this.lastUpdate = Date.now()
|
this.lastUpdate = Date.now()
|
||||||
this.light = true
|
this.light = true
|
||||||
})
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
stop = () => clearInterval(this.timer)
|
stop = () => clearInterval(this.timer)
|
||||||
|
|
40
examples/with-next-css/README.md
Normal file
40
examples/with-next-css/README.md
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
[![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-next-css)
|
||||||
|
|
||||||
|
# next-css example
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Using `create-next-app`
|
||||||
|
|
||||||
|
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -g create-next-app
|
||||||
|
create-next-app --example with-next-css with-next-css-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-next-css
|
||||||
|
cd with-next-css
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This example demonstrates how to use the [next-css plugin](https://github.com/zeit/next-plugins/tree/master/packages/next-css) It includes patterns for with and without CSS Modules, with PostCSS and with additional webpack configurations on top of the next-css plugin.
|
16
examples/with-next-css/next.config.js
Normal file
16
examples/with-next-css/next.config.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
const withCSS = require('@zeit/next-css')
|
||||||
|
/* Without CSS Modules, with PostCSS */
|
||||||
|
module.exports = withCSS()
|
||||||
|
|
||||||
|
/* With CSS Modules */
|
||||||
|
// module.exports = withCSS({ cssModules: true })
|
||||||
|
|
||||||
|
/* With additional configuration on top of CSS Modules */
|
||||||
|
/*
|
||||||
|
module.exports = withCSS({
|
||||||
|
cssModules: true,
|
||||||
|
webpack: function (config) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
16
examples/with-next-css/package.json
Normal file
16
examples/with-next-css/package.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "with-css-modules",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@zeit/next-css": "0.0.7",
|
||||||
|
"next": "5.0.0",
|
||||||
|
"react": "16.2.0",
|
||||||
|
"react-dom": "16.2.0"
|
||||||
|
}
|
||||||
|
}
|
23
examples/with-next-css/pages/_document.js
Normal file
23
examples/with-next-css/pages/_document.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
In production the stylesheet is compiled to .next/static/style.css and served from /_next/static/style.css
|
||||||
|
|
||||||
|
You have to include it into the page using either next/head or a custom _document.js, as is being done in this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Document, { Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default class MyDocument extends Document {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<Head>
|
||||||
|
<link rel='stylesheet' href='/_next/static/style.css' />
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
13
examples/with-next-css/pages/index.js
Normal file
13
examples/with-next-css/pages/index.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
/* Without CSS Modules, maybe with PostCSS */
|
||||||
|
|
||||||
|
import '../style.css'
|
||||||
|
|
||||||
|
export default () => <div className='example'>O Hai world!</div>
|
||||||
|
|
||||||
|
/* With CSS Modules */
|
||||||
|
/*
|
||||||
|
import css from "../style.css"
|
||||||
|
|
||||||
|
export default () => <div className={css.example}>Hello World, I am being styled using CSS Modules!</div>
|
||||||
|
*/
|
16
examples/with-next-css/style.css
Normal file
16
examples/with-next-css/style.css
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
.example {
|
||||||
|
font-size: 50px;
|
||||||
|
color: papayawhip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Post-CSS */
|
||||||
|
/*
|
||||||
|
:root {
|
||||||
|
--some-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example {
|
||||||
|
color: var(--some-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
2
examples/with-next-sass/next.config.js
Normal file
2
examples/with-next-sass/next.config.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
const withSass = require('@zeit/next-sass')
|
||||||
|
module.exports = withSass()
|
14
examples/with-next-sass/package.json
Normal file
14
examples/with-next-sass/package.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@zeit/next-sass": "0.0.9",
|
||||||
|
"next": "^5.0.0",
|
||||||
|
"node-sass": "^4.7.2",
|
||||||
|
"react": "^16.2.0",
|
||||||
|
"react-dom": "^16.2.0"
|
||||||
|
}
|
||||||
|
}
|
26
examples/with-next-sass/pages/_document.js
Normal file
26
examples/with-next-sass/pages/_document.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
In production the stylesheet is compiled to .next/static/style.css.
|
||||||
|
The file will be served from /_next/static/style.css
|
||||||
|
You could include it into the page using either next/head or a custom _document.js.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Document, { Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default class MyDocument extends Document {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<Head>
|
||||||
|
<link
|
||||||
|
rel='stylesheet'
|
||||||
|
href='/_next/static/style.css'
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
6
examples/with-next-sass/pages/index.js
Normal file
6
examples/with-next-sass/pages/index.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import '../styles/style.scss'
|
||||||
|
|
||||||
|
export default () =>
|
||||||
|
<div className='example'>
|
||||||
|
Hello World!
|
||||||
|
</div>
|
43
examples/with-next-sass/readme.md
Normal file
43
examples/with-next-sass/readme.md
Normal 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-jest)
|
||||||
|
|
||||||
|
# Example app with next-sass
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Using `create-next-app`
|
||||||
|
|
||||||
|
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i -g create-next-app
|
||||||
|
create-next-app --example with-next-sass with-next-sass-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-next-sass
|
||||||
|
cd with-next-sass
|
||||||
|
```
|
||||||
|
|
||||||
|
Install it and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
The dev mode is also support via `npm run dev`
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This example features:
|
||||||
|
|
||||||
|
* An app with next-sass
|
||||||
|
|
||||||
|
This example uses next-sass without css-modules. The config can be found in `next.config.js`, change `withSass()` to `withSass({cssModules: true})` if you use css-modules. Then in the code, you import the stylesheet as `import style '../styles/style.scss'` and use it like `<div className={style.example}>`.
|
||||||
|
|
||||||
|
[Learn more](https://github.com/zeit/next-plugins/tree/master/packages/next-sass)
|
4
examples/with-next-sass/styles/style.scss
Normal file
4
examples/with-next-sass/styles/style.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
$color: #2ecc71;
|
||||||
|
.example {
|
||||||
|
background-color: $color;
|
||||||
|
}
|
|
@ -3,7 +3,11 @@ module.exports = {
|
||||||
const originalEntry = cfg.entry
|
const originalEntry = cfg.entry
|
||||||
cfg.entry = async () => {
|
cfg.entry = async () => {
|
||||||
const entries = await originalEntry()
|
const entries = await originalEntry()
|
||||||
|
|
||||||
|
if (entries['main.js']) {
|
||||||
entries['main.js'].unshift('./client/polyfills.js')
|
entries['main.js'].unshift('./client/polyfills.js')
|
||||||
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,15 +11,15 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "4.15.3",
|
"express": "4.16.2",
|
||||||
"i18next": "8.4.2",
|
"i18next": "10.4.1",
|
||||||
"i18next-browser-languagedetector": "2.0.0",
|
"i18next-browser-languagedetector": "2.1.0",
|
||||||
"i18next-express-middleware": "1.0.5",
|
"i18next-express-middleware": "1.0.10",
|
||||||
"i18next-node-fs-backend": "1.0.0",
|
"i18next-node-fs-backend": "1.0.0",
|
||||||
"i18next-xhr-backend": "1.4.2",
|
"i18next-xhr-backend": "1.5.1",
|
||||||
"next": "latest",
|
"next": "5.0.0",
|
||||||
"react": "16.2.0",
|
"react": "16.2.0",
|
||||||
"react-dom": "16.2.0",
|
"react-dom": "16.2.0",
|
||||||
"react-i18next": "4.6.3"
|
"react-i18next": "7.3.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ const express = require('express')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const next = require('next')
|
const next = require('next')
|
||||||
|
|
||||||
const port = parseInt(process.env.PORT, 10) || 3000
|
|
||||||
const dev = process.env.NODE_ENV !== 'production'
|
const dev = process.env.NODE_ENV !== 'production'
|
||||||
const app = next({ dev })
|
const app = next({ dev })
|
||||||
const handle = app.getRequestHandler()
|
const handle = app.getRequestHandler()
|
||||||
|
@ -17,6 +16,7 @@ i18n
|
||||||
.use(Backend)
|
.use(Backend)
|
||||||
.use(i18nextMiddleware.LanguageDetector)
|
.use(i18nextMiddleware.LanguageDetector)
|
||||||
.init({
|
.init({
|
||||||
|
fallbackLng: 'en',
|
||||||
preload: ['en', 'de'], // preload all langages
|
preload: ['en', 'de'], // preload all langages
|
||||||
ns: ['common', 'home', 'page2'], // need to preload all the namespaces
|
ns: ['common', 'home', 'page2'], // need to preload all the namespaces
|
||||||
backend: {
|
backend: {
|
||||||
|
@ -41,9 +41,9 @@ i18n
|
||||||
// use next.js
|
// use next.js
|
||||||
server.get('*', (req, res) => handle(req, res))
|
server.get('*', (req, res) => handle(req, res))
|
||||||
|
|
||||||
server.listen(port, (err) => {
|
server.listen(3000, (err) => {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
console.log(`> Ready on http://localhost:${port}`)
|
console.log('> Ready on http://localhost:3000')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
4
examples/with-react-native-web/.babelrc
Normal file
4
examples/with-react-native-web/.babelrc
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"presets": ["next/babel"],
|
||||||
|
"plugins": ["react-native-web"]
|
||||||
|
}
|
42
examples/with-react-native-web/README.md
Normal file
42
examples/with-react-native-web/README.md
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
[![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-react-native-web)
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
### Using `create-next-app`
|
||||||
|
|
||||||
|
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -g create-next-app
|
||||||
|
create-next-app --example with-react-native-web
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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-react-native-web
|
||||||
|
cd with-react-native-web
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## The idea behind the example
|
||||||
|
|
||||||
|
This example features how to use [react-native-web](https://github.com/necolas/react-native-web) to bring the platform-agnostic Components and APIs of React Native to the web.
|
||||||
|
|
||||||
|
> **High-quality user interfaces**: React Native for Web makes it easy to create fast, adaptive web UIs in JavaScript. It provides native-like interactions, support for multiple input modes (touch, mouse, keyboard), optimized vendor-prefixed styles, built-in support for RTL layout, built-in accessibility, and integrates with React Dev Tools.
|
||||||
|
>
|
||||||
|
> **Write once, render anywhere**: React Native for Web interoperates with existing React DOM components and is compatible with the majority of the React Native API. You can develop new components for native and web without rewriting existing code. React Native for Web can also render to HTML and critical CSS on the server using Node.js.
|
17
examples/with-react-native-web/package.json
Normal file
17
examples/with-react-native-web/package.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "with-react-native-web",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "latest",
|
||||||
|
"react": "^16.0.0",
|
||||||
|
"react-dom": "^16.0.0",
|
||||||
|
"react-native-web": "^0.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-plugin-react-native-web": "^0.4.0"
|
||||||
|
}
|
||||||
|
}
|
45
examples/with-react-native-web/pages/_document.js
Normal file
45
examples/with-react-native-web/pages/_document.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import Document, { Head, Main, NextScript } from 'next/document'
|
||||||
|
import React from 'react'
|
||||||
|
import { AppRegistry } from 'react-native-web'
|
||||||
|
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
// Force Next-generated DOM elements to fill their parent's height.
|
||||||
|
// Not required for using of react-native-web, but helps normalize
|
||||||
|
// layout for top-level wrapping elements.
|
||||||
|
const normalizeNextElements = `
|
||||||
|
body > div:first-child,
|
||||||
|
#__next {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default class MyDocument extends Document {
|
||||||
|
static async getInitialProps ({ renderPage }) {
|
||||||
|
AppRegistry.registerComponent('Main', () => Main)
|
||||||
|
const { getStyleElement } = AppRegistry.getApplication('Main')
|
||||||
|
const page = renderPage()
|
||||||
|
const styles = [
|
||||||
|
<style
|
||||||
|
key={index++}
|
||||||
|
dangerouslySetInnerHTML={{ __html: normalizeNextElements }}
|
||||||
|
/>,
|
||||||
|
getStyleElement()
|
||||||
|
]
|
||||||
|
return { ...page, styles }
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<html style={{ height: '100%', width: '100%' }}>
|
||||||
|
<Head>
|
||||||
|
<title>react-native-web</title>
|
||||||
|
</Head>
|
||||||
|
<body style={{ height: '100%', width: '100%', overflowY: 'scroll' }}>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
20
examples/with-react-native-web/pages/index.js
Normal file
20
examples/with-react-native-web/pages/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
alignItems: 'center',
|
||||||
|
fontSize: 24
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default props => (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.text}>Welcome to Next.js!</Text>
|
||||||
|
</View>
|
||||||
|
)
|
|
@ -40,7 +40,7 @@ Run BuckleScript build system `bsb -w` and `next -w` separately. For the sake
|
||||||
of simple convention, `npm run dev` run both `bsb` and `next` concurrently.
|
of simple convention, `npm run dev` run both `bsb` and `next` concurrently.
|
||||||
However, this doesn't offer the full [colorful and very, very, veeeery nice
|
However, this doesn't offer the full [colorful and very, very, veeeery nice
|
||||||
error
|
error
|
||||||
output](https://reasonml.github.io/community/blog/#way-way-waaaay-nicer-error-messages)
|
output](https://reasonml.github.io/blog/2017/08/25/way-nicer-error-messages.html)
|
||||||
experience that ReasonML can offer, don't miss it!
|
experience that ReasonML can offer, don't miss it!
|
||||||
|
|
||||||
## The idea behind the example
|
## The idea behind the example
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { interval } from 'rxjs/observable/interval'
|
import { interval } from 'rxjs/observable/interval'
|
||||||
import { of } from 'rxjs/observable/of'
|
import { of } from 'rxjs/observable/of'
|
||||||
import { takeUntil, mergeMap, catchError } from 'rxjs/operators'
|
import { takeUntil, mergeMap, catchError, map } from 'rxjs/operators'
|
||||||
import { combineEpics, ofType } from 'redux-observable'
|
import { combineEpics, ofType } from 'redux-observable'
|
||||||
import ajax from 'universal-rx-request' // because standard AjaxObservable only works in browser
|
import ajax from 'universal-rx-request' // because standard AjaxObservable only works in browser
|
||||||
|
|
||||||
|
@ -13,11 +13,9 @@ export const fetchUserEpic = (action$, store) =>
|
||||||
mergeMap(action => {
|
mergeMap(action => {
|
||||||
return interval(3000).pipe(
|
return interval(3000).pipe(
|
||||||
mergeMap(x =>
|
mergeMap(x =>
|
||||||
of(
|
|
||||||
actions.fetchCharacter({
|
actions.fetchCharacter({
|
||||||
isServer: store.getState().isServer
|
isServer: store.getState().isServer
|
||||||
})
|
})
|
||||||
)
|
|
||||||
),
|
),
|
||||||
takeUntil(action$.ofType(types.STOP_FETCHING_CHARACTERS))
|
takeUntil(action$.ofType(types.STOP_FETCHING_CHARACTERS))
|
||||||
)
|
)
|
||||||
|
@ -31,13 +29,11 @@ export const fetchCharacterEpic = (action$, store) =>
|
||||||
ajax({
|
ajax({
|
||||||
url: `https://swapi.co/api/people/${store.getState().nextCharacterId}`
|
url: `https://swapi.co/api/people/${store.getState().nextCharacterId}`
|
||||||
}).pipe(
|
}).pipe(
|
||||||
mergeMap(response =>
|
map(response =>
|
||||||
of(
|
|
||||||
actions.fetchCharacterSuccess(
|
actions.fetchCharacterSuccess(
|
||||||
response.body,
|
response.body,
|
||||||
store.getState().isServer
|
store.getState().isServer
|
||||||
)
|
)
|
||||||
)
|
|
||||||
),
|
),
|
||||||
catchError(error =>
|
catchError(error =>
|
||||||
of(
|
of(
|
||||||
|
|
|
@ -61,7 +61,7 @@ The second example, under `components/add-count.js`, shows a simple add counter
|
||||||
|
|
||||||
## What changed with next-redux-saga
|
## What changed with next-redux-saga
|
||||||
|
|
||||||
The digital clock is updated every 800ms using the `runClockSaga` found in `saga.js`.
|
The digital clock is updated every second using the `runClockSaga` found in `saga.js`.
|
||||||
|
|
||||||
All pages are also being wrapped by `next-redux-saga` using a helper function from `store.js`:
|
All pages are also being wrapped by `next-redux-saga` using a helper function from `store.js`:
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {increment, loadData, startClock} from '../actions'
|
import {increment, loadData, startClock, tickClock} from '../actions'
|
||||||
import {withReduxSaga} from '../store'
|
import {withReduxSaga} from '../store'
|
||||||
import Page from '../components/page'
|
import Page from '../components/page'
|
||||||
|
|
||||||
class Counter extends React.Component {
|
class Counter extends React.Component {
|
||||||
static async getInitialProps ({store}) {
|
static async getInitialProps ({store, isServer}) {
|
||||||
|
store.dispatch(tickClock(isServer))
|
||||||
store.dispatch(increment())
|
store.dispatch(increment())
|
||||||
if (!store.getState().placeholderData) {
|
if (!store.getState().placeholderData) {
|
||||||
store.dispatch(loadData())
|
store.dispatch(loadData())
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import {increment, startClock} from '../actions'
|
import {increment, startClock, tickClock} from '../actions'
|
||||||
import {withReduxSaga} from '../store'
|
import {withReduxSaga} from '../store'
|
||||||
import Page from '../components/page'
|
import Page from '../components/page'
|
||||||
|
|
||||||
class Counter extends React.Component {
|
class Counter extends React.Component {
|
||||||
static async getInitialProps ({store}) {
|
static async getInitialProps ({store, isServer}) {
|
||||||
|
store.dispatch(tickClock(isServer))
|
||||||
store.dispatch(increment())
|
store.dispatch(increment())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ function * runClockSaga () {
|
||||||
yield take(actionTypes.START_CLOCK)
|
yield take(actionTypes.START_CLOCK)
|
||||||
while (true) {
|
while (true) {
|
||||||
yield put(tickClock(false))
|
yield put(tickClock(false))
|
||||||
yield call(delay, 800)
|
yield call(delay, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const serverRenderClock = (isServer) => dispatch => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const startClock = () => dispatch => {
|
export const startClock = () => dispatch => {
|
||||||
return setInterval(() => dispatch({ type: actionTypes.TICK, light: true, ts: Date.now() }), 800)
|
return setInterval(() => dispatch({ type: actionTypes.TICK, light: true, ts: Date.now() }), 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addCount = () => dispatch => {
|
export const addCount = () => dispatch => {
|
||||||
|
|
1
examples/with-typescript/.gitignore
vendored
1
examples/with-typescript/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
*.js
|
|
|
@ -31,4 +31,3 @@ npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Output JS files are aside the related TypeScript ones.
|
|
||||||
|
|
2
examples/with-typescript/next.config.js
Normal file
2
examples/with-typescript/next.config.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
const withTypescript = require('@zeit/next-typescript')
|
||||||
|
module.exports = withTypescript()
|
|
@ -1,22 +1,20 @@
|
||||||
{
|
{
|
||||||
"name": "with-typescript",
|
"name": "with-typescript-plugin",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"tsc --pretty --watch\" \"next\"",
|
"dev": "next",
|
||||||
"prebuild": "tsc",
|
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "latest",
|
"next": "^5.0.0",
|
||||||
"react": "^16.1.0",
|
"react": "^16.2.0",
|
||||||
"react-dom": "^16.1.0"
|
"react-dom": "^16.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/next": "^2.4.5",
|
"@types/next": "^2.4.7",
|
||||||
"@types/react": "^16.0.22",
|
"@types/react": "^16.0.36",
|
||||||
"concurrently": "^3.5.0",
|
"@zeit/next-typescript": "0.0.8",
|
||||||
"tslint": "^5.8.0",
|
"typescript": "^2.7.1"
|
||||||
"typescript": "^2.6.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,26 @@
|
||||||
{
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-native",
|
"target": "esnext",
|
||||||
"module": "commonjs",
|
"module": "esnext",
|
||||||
"strict": true,
|
"jsx": "preserve",
|
||||||
"target": "es2017"
|
"allowJs": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"preserveConstEnums": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es2015",
|
||||||
|
"es2016"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"defaultSeverity": "error",
|
|
||||||
"extends": ["tslint:recommended"],
|
|
||||||
"jsRules": {},
|
|
||||||
"rules": {
|
|
||||||
"quotemark": [true, "single", "jsx-double"],
|
|
||||||
"semicolon": [true, "never"]
|
|
||||||
},
|
|
||||||
"rulesDirectory": []
|
|
||||||
}
|
|
|
@ -2,11 +2,11 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
|
||||||
const { ANALYZE } = process.env
|
const { ANALYZE } = process.env
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
webpack: function (config) {
|
webpack: function (config, { isServer }) {
|
||||||
if (ANALYZE) {
|
if (ANALYZE) {
|
||||||
config.plugins.push(new BundleAnalyzerPlugin({
|
config.plugins.push(new BundleAnalyzerPlugin({
|
||||||
analyzerMode: 'server',
|
analyzerMode: 'server',
|
||||||
analyzerPort: 8888,
|
analyzerPort: isServer ? 8888 : 8889,
|
||||||
openAnalyzer: true
|
openAnalyzer: true
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"start": "next start -p 5000"
|
"start": "next start -p 5000"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "zones",
|
"next": "latest",
|
||||||
"react": "^16.0.0",
|
"react": "^16.0.0",
|
||||||
"react-dom": "^16.0.0"
|
"react-dom": "^16.0.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default function withRouter (ComposedComponent) {
|
||||||
router: PropTypes.object
|
router: PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
static displayName = `withRoute(${displayName})`
|
static displayName = `withRouter(${displayName})`
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const props = {
|
const props = {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "next",
|
"name": "next",
|
||||||
"version": "5.0.0",
|
"version": "5.0.1-canary.6",
|
||||||
"description": "Minimalistic framework for server-rendered React applications",
|
"description": "Minimalistic framework for server-rendered React applications",
|
||||||
"main": "./dist/server/next.js",
|
"main": "./dist/server/next.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -90,12 +90,12 @@
|
||||||
"pkg-up": "2.0.0",
|
"pkg-up": "2.0.0",
|
||||||
"prop-types": "15.6.0",
|
"prop-types": "15.6.0",
|
||||||
"prop-types-exact": "1.1.1",
|
"prop-types-exact": "1.1.1",
|
||||||
"react-hot-loader": "4.0.0-beta.18",
|
"react-hot-loader": "4.0.0-beta.23",
|
||||||
"recursive-copy": "2.0.6",
|
"recursive-copy": "2.0.6",
|
||||||
"resolve": "1.5.0",
|
"resolve": "1.5.0",
|
||||||
"send": "0.16.1",
|
"send": "0.16.1",
|
||||||
"strip-ansi": "3.0.1",
|
"strip-ansi": "3.0.1",
|
||||||
"styled-jsx": "2.2.3",
|
"styled-jsx": "2.2.5",
|
||||||
"touch": "3.1.0",
|
"touch": "3.1.0",
|
||||||
"uglifyjs-webpack-plugin": "1.1.6",
|
"uglifyjs-webpack-plugin": "1.1.6",
|
||||||
"unfetch": "3.0.0",
|
"unfetch": "3.0.0",
|
||||||
|
@ -105,6 +105,7 @@
|
||||||
"webpack": "3.10.0",
|
"webpack": "3.10.0",
|
||||||
"webpack-dev-middleware": "1.12.0",
|
"webpack-dev-middleware": "1.12.0",
|
||||||
"webpack-hot-middleware": "2.21.0",
|
"webpack-hot-middleware": "2.21.0",
|
||||||
|
"webpack-sources": "1.1.0",
|
||||||
"write-file-webpack-plugin": "4.2.0",
|
"write-file-webpack-plugin": "4.2.0",
|
||||||
"xss-filters": "1.2.7"
|
"xss-filters": "1.2.7"
|
||||||
},
|
},
|
||||||
|
|
36
readme.md
36
readme.md
|
@ -169,9 +169,9 @@ To use more sophisticated CSS-in-JS solutions, you typically have to implement s
|
||||||
|
|
||||||
To support importing `.css` `.scss` or `.less` files you can use these modules, which configure sensible defaults for server rendered applications.
|
To support importing `.css` `.scss` or `.less` files you can use these modules, which configure sensible defaults for server rendered applications.
|
||||||
|
|
||||||
- ![@zeit/next-css](https://github.com/zeit/next-plugins/tree/master/packages/next-css)
|
- [@zeit/next-css](https://github.com/zeit/next-plugins/tree/master/packages/next-css)
|
||||||
- ![@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass)
|
- [@zeit/next-sass](https://github.com/zeit/next-plugins/tree/master/packages/next-sass)
|
||||||
- ![@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less)
|
- [@zeit/next-less](https://github.com/zeit/next-plugins/tree/master/packages/next-less)
|
||||||
|
|
||||||
### Static file serving (e.g.: images)
|
### Static file serving (e.g.: images)
|
||||||
|
|
||||||
|
@ -1037,6 +1037,17 @@ module.exports = {
|
||||||
|
|
||||||
This is development-only feature. If you want to cache SSR pages in production, please see [SSR-caching](https://github.com/zeit/next.js/tree/canary/examples/ssr-caching) example.
|
This is development-only feature. If you want to cache SSR pages in production, please see [SSR-caching](https://github.com/zeit/next.js/tree/canary/examples/ssr-caching) example.
|
||||||
|
|
||||||
|
#### Configuring extensions looked for when resolving pages in `pages`
|
||||||
|
|
||||||
|
Aimed at modules like [`@zeit/next-typescript`](https://github.com/zeit/next-plugins/tree/master/packages/next-typescript), that add support for pages ending in `.ts`. `pageExtensions` allows you to configure the extensions looked for in the `pages` directory when resolving pages.
|
||||||
|
|
||||||
|
```js
|
||||||
|
// next.config.js
|
||||||
|
module.exports = {
|
||||||
|
pageExtensions: ['jsx', 'js']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Customizing webpack config
|
### Customizing webpack config
|
||||||
|
|
||||||
<p><details>
|
<p><details>
|
||||||
|
@ -1077,6 +1088,21 @@ Some commonly asked for features are available as modules:
|
||||||
|
|
||||||
*Warning: The `webpack` function is executed twice, once for the server and once for the client. This allows you to distinguish between client and server configuration using the `isServer` property*
|
*Warning: The `webpack` function is executed twice, once for the server and once for the client. This allows you to distinguish between client and server configuration using the `isServer` property*
|
||||||
|
|
||||||
|
Multiple configurations can be combined together with function composition. For example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const withTypescript = require('@zeit/next-typescript')
|
||||||
|
const withSass = require('@zeit/next-sass')
|
||||||
|
|
||||||
|
module.exports = withTypescript(withSass({
|
||||||
|
webpack(config, options) {
|
||||||
|
// Further custom configuration here
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Customizing babel config
|
### Customizing babel config
|
||||||
|
|
||||||
<p><details>
|
<p><details>
|
||||||
|
@ -1094,7 +1120,8 @@ Here's an example `.babelrc` file:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"presets": ["next/babel", "env"]
|
"presets": ["next/babel"],
|
||||||
|
"plugins": []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1267,6 +1294,7 @@ For the production deployment, you can use the [path alias](https://zeit.co/docs
|
||||||
|
|
||||||
- [Setting up 301 redirects](https://www.raygesualdo.com/posts/301-redirects-with-nextjs/)
|
- [Setting up 301 redirects](https://www.raygesualdo.com/posts/301-redirects-with-nextjs/)
|
||||||
- [Dealing with SSR and server only modules](https://arunoda.me/blog/ssr-and-server-only-modules)
|
- [Dealing with SSR and server only modules](https://arunoda.me/blog/ssr-and-server-only-modules)
|
||||||
|
- [Building with React-Material-UI-Next-Express-Mongoose-Mongodb](https://github.com/builderbook/builderbook)
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { resolve } from 'path'
|
|
||||||
import del from 'del'
|
|
||||||
import getConfig from '../config'
|
|
||||||
|
|
||||||
export default function clean (dir) {
|
|
||||||
const dist = getConfig(dir).distDir
|
|
||||||
return del(resolve(dir, dist), { force: true })
|
|
||||||
}
|
|
|
@ -1,22 +1,19 @@
|
||||||
import { join } from 'path'
|
import { join, resolve, relative, dirname } from 'path'
|
||||||
|
|
||||||
// This plugin modifies the require-ensure code generated by Webpack
|
// This plugin modifies the require-ensure code generated by Webpack
|
||||||
// to work with Next.js SSR
|
// to work with Next.js SSR
|
||||||
export default class NextJsSsrImportPlugin {
|
export default class NextJsSsrImportPlugin {
|
||||||
constructor ({ dir, dist }) {
|
|
||||||
this.dir = dir
|
|
||||||
this.dist = dist
|
|
||||||
}
|
|
||||||
|
|
||||||
apply (compiler) {
|
apply (compiler) {
|
||||||
compiler.plugin('compilation', (compilation) => {
|
compiler.plugin('compilation', (compilation) => {
|
||||||
compilation.mainTemplate.plugin('require-ensure', (code) => {
|
compilation.mainTemplate.plugin('require-ensure', (code, chunk) => {
|
||||||
// Update to load chunks from our custom chunks directory
|
// Update to load chunks from our custom chunks directory
|
||||||
const chunksDirPath = join(this.dir, this.dist, 'dist')
|
const outputPath = resolve('/')
|
||||||
|
const pagePath = join('/', dirname(chunk.name))
|
||||||
|
const relativePathToBaseDir = relative(pagePath, outputPath)
|
||||||
// Make sure even in windows, the path looks like in unix
|
// Make sure even in windows, the path looks like in unix
|
||||||
// Node.js require system will convert it accordingly
|
// Node.js require system will convert it accordingly
|
||||||
const chunksDirPathNormalized = chunksDirPath.replace(/\\/g, '/')
|
const relativePathToBaseDirNormalized = relativePathToBaseDir.replace(/\\/g, '/')
|
||||||
let updatedCode = code.replace('require("./"', `require("${chunksDirPathNormalized}/"`)
|
let updatedCode = code.replace('require("./"', `require("${relativePathToBaseDirNormalized}/"`)
|
||||||
|
|
||||||
// Replace a promise equivalent which runs in the same loop
|
// Replace a promise equivalent which runs in the same loop
|
||||||
// If we didn't do this webpack's module loading process block us from
|
// If we didn't do this webpack's module loading process block us from
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Next.js needs to use module.resolve to generate paths to modules it includes,
|
|
||||||
// but those paths need to be relative to something so that they're portable
|
|
||||||
// across directories and machines.
|
|
||||||
//
|
|
||||||
// This function returns paths relative to the top-level 'node_modules'
|
|
||||||
// directory found in the path. If none is found, returns the complete path.
|
|
||||||
|
|
||||||
import { sep } from 'path'
|
|
||||||
|
|
||||||
const RELATIVE_START = `node_modules${sep}`
|
|
||||||
|
|
||||||
// Pass in the module's `require` object since it's module-specific.
|
|
||||||
export default (moduleRequire) => (path) => {
|
|
||||||
// package.json removed because babel-runtime is resolved as
|
|
||||||
// "babel-runtime/package"
|
|
||||||
const absolutePath = moduleRequire.resolve(path)
|
|
||||||
.replace(/[\\/]package\.json$/, '')
|
|
||||||
|
|
||||||
const relativeStartIndex = absolutePath.indexOf(RELATIVE_START)
|
|
||||||
|
|
||||||
if (relativeStartIndex === -1) {
|
|
||||||
return absolutePath
|
|
||||||
}
|
|
||||||
|
|
||||||
return absolutePath.substring(relativeStartIndex + RELATIVE_START.length)
|
|
||||||
}
|
|
|
@ -71,7 +71,15 @@ function externalsConfig (dir, isServer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Webpack itself has to be compiled because it doesn't always use module relative paths
|
// Webpack itself has to be compiled because it doesn't always use module relative paths
|
||||||
if (res.match(/node_modules[/\\].*\.js/) && !res.match(/node_modules[/\\]webpack/)) {
|
if (res.match(/node_modules[/\\]next[/\\]dist[/\\]pages/)) {
|
||||||
|
return callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.match(/node_modules[/\\]webpack/)) {
|
||||||
|
return callback()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.match(/node_modules[/\\].*\.js/)) {
|
||||||
return callback(null, `commonjs ${request}`)
|
return callback(null, `commonjs ${request}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +115,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
externals: externalsConfig(dir, isServer),
|
externals: externalsConfig(dir, isServer),
|
||||||
context: dir,
|
context: dir,
|
||||||
entry: async () => {
|
entry: async () => {
|
||||||
const pages = await getPages(dir, {dev, isServer})
|
const pages = await getPages(dir, {dev, isServer, pageExtensions: config.pageExtensions.join('|')})
|
||||||
totalPages = Object.keys(pages).length
|
totalPages = Object.keys(pages).length
|
||||||
const mainJS = require.resolve(`../../client/next${dev ? '-dev' : ''}`)
|
const mainJS = require.resolve(`../../client/next${dev ? '-dev' : ''}`)
|
||||||
const clientConfig = !isServer ? {
|
const clientConfig = !isServer ? {
|
||||||
|
@ -141,10 +149,15 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
],
|
],
|
||||||
alias: {
|
alias: {
|
||||||
next: nextDir,
|
next: nextDir,
|
||||||
// This bypasses React's check for production mode. Since we know it is in production this way.
|
// React already does something similar to this.
|
||||||
// This allows us to exclude React from being uglified. Saving multiple seconds per build.
|
// But if the user has react-devtools, it'll throw an error showing that
|
||||||
react: dev ? 'react/cjs/react.development.js' : 'react/cjs/react.production.min.js',
|
// we haven't done dead code elimination (via uglifyjs).
|
||||||
'react-dom': dev ? 'react-dom/cjs/react-dom.development.js' : 'react-dom/cjs/react-dom.production.min.js'
|
// We purposly do not uglify React code to save the build time.
|
||||||
|
// (But it didn't increase the overall build size)
|
||||||
|
// Here we are doing an exact match with '$'
|
||||||
|
// So, you can still require nested modules like `react-dom/server`
|
||||||
|
react$: dev ? 'react/cjs/react.development.js' : 'react/cjs/react.production.min.js',
|
||||||
|
'react-dom$': dev ? 'react-dom/cjs/react-dom.development.js' : 'react-dom/cjs/react-dom.production.min.js'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolveLoader: {
|
resolveLoader: {
|
||||||
|
@ -242,7 +255,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
|
!dev && new webpack.optimize.ModuleConcatenationPlugin(),
|
||||||
!isServer && new PagesPlugin(),
|
!isServer && new PagesPlugin(),
|
||||||
!isServer && new DynamicChunksPlugin(),
|
!isServer && new DynamicChunksPlugin(),
|
||||||
isServer && new NextJsSsrImportPlugin({ dir, dist: config.distDir }),
|
isServer && new NextJsSsrImportPlugin(),
|
||||||
!isServer && new webpack.optimize.CommonsChunkPlugin({
|
!isServer && new webpack.optimize.CommonsChunkPlugin({
|
||||||
name: `commons`,
|
name: `commons`,
|
||||||
filename: `commons.js`,
|
filename: `commons.js`,
|
||||||
|
@ -250,11 +263,11 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
// We need to move react-dom explicitly into common chunks.
|
// We need to move react-dom explicitly into common chunks.
|
||||||
// Otherwise, if some other page or module uses it, it might
|
// Otherwise, if some other page or module uses it, it might
|
||||||
// included in that bundle too.
|
// included in that bundle too.
|
||||||
if (dev && module.context && module.context.indexOf(`${sep}react${sep}`) >= 0) {
|
if (module.context && module.context.indexOf(`${sep}react${sep}`) >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dev && module.context && module.context.indexOf(`${sep}react-dom${sep}`) >= 0) {
|
if (module.context && module.context.indexOf(`${sep}react-dom${sep}`) >= 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,7 +314,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof config.webpack === 'function') {
|
if (typeof config.webpack === 'function') {
|
||||||
webpackConfig = config.webpack(webpackConfig, {dir, dev, isServer, buildId, config, defaultLoaders})
|
webpackConfig = config.webpack(webpackConfig, {dir, dev, isServer, buildId, config, defaultLoaders, totalPages})
|
||||||
}
|
}
|
||||||
|
|
||||||
return webpackConfig
|
return webpackConfig
|
||||||
|
|
|
@ -3,26 +3,28 @@ import glob from 'glob-promise'
|
||||||
|
|
||||||
const nextPagesDir = path.join(__dirname, '..', '..', '..', 'pages')
|
const nextPagesDir = path.join(__dirname, '..', '..', '..', 'pages')
|
||||||
|
|
||||||
export async function getPages (dir, {dev, isServer}) {
|
export async function getPages (dir, {dev, isServer, pageExtensions}) {
|
||||||
const pageFiles = await getPagePaths(dir, {dev, isServer})
|
const pageFiles = await getPagePaths(dir, {dev, isServer, pageExtensions})
|
||||||
|
|
||||||
return getPageEntries(pageFiles, {isServer})
|
return getPageEntries(pageFiles, {isServer, pageExtensions})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPagePaths (dir, {dev, isServer}) {
|
async function getPagePaths (dir, {dev, isServer, pageExtensions}) {
|
||||||
let pages
|
let pages
|
||||||
|
|
||||||
if (dev) {
|
if (dev) {
|
||||||
pages = await glob(isServer ? 'pages/+(_document|_error).+(js|jsx|ts|tsx)' : 'pages/_error.+(js|jsx|ts|tsx)', { cwd: dir })
|
// In development we only compile _document.js and _error.js when starting, since they're always needed. All other pages are compiled with on demand entries
|
||||||
|
pages = await glob(isServer ? `pages/+(_document|_error).+(${pageExtensions})` : `pages/_error.+(${pageExtensions})`, { cwd: dir })
|
||||||
} else {
|
} else {
|
||||||
pages = await glob(isServer ? 'pages/**/*.+(js|jsx|ts|tsx)' : 'pages/**/!(_document)*.+(js|jsx|ts|tsx)', { cwd: dir })
|
// In production get all pages from the pages directory
|
||||||
|
pages = await glob(isServer ? `pages/**/*.+(${pageExtensions})` : `pages/**/!(_document)*.+(${pageExtensions})`, { cwd: dir })
|
||||||
}
|
}
|
||||||
|
|
||||||
return pages
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert page path into single entry
|
// Convert page path into single entry
|
||||||
export function createEntry (filePath, name) {
|
export function createEntry (filePath, {name, pageExtensions} = {}) {
|
||||||
const parsedPath = path.parse(filePath)
|
const parsedPath = path.parse(filePath)
|
||||||
let entryName = name || filePath
|
let entryName = name || filePath
|
||||||
|
|
||||||
|
@ -33,7 +35,9 @@ export function createEntry (filePath, name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Makes sure supported extensions are stripped off. The outputted file should always be `.js`
|
// Makes sure supported extensions are stripped off. The outputted file should always be `.js`
|
||||||
entryName = entryName.replace(/\.+(jsx|tsx|ts)/, '.js')
|
if (pageExtensions) {
|
||||||
|
entryName = entryName.replace(new RegExp(`\\.+(${pageExtensions})$`), '.js')
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: path.join('bundles', entryName),
|
name: path.join('bundles', entryName),
|
||||||
|
@ -42,23 +46,23 @@ export function createEntry (filePath, name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert page paths into entries
|
// Convert page paths into entries
|
||||||
export function getPageEntries (pagePaths, {isServer}) {
|
export function getPageEntries (pagePaths, {isServer = false, pageExtensions} = {}) {
|
||||||
const entries = {}
|
const entries = {}
|
||||||
|
|
||||||
for (const filePath of pagePaths) {
|
for (const filePath of pagePaths) {
|
||||||
const entry = createEntry(filePath)
|
const entry = createEntry(filePath, {pageExtensions})
|
||||||
entries[entry.name] = entry.files
|
entries[entry.name] = entry.files
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorPagePath = path.join(nextPagesDir, '_error.js')
|
const errorPagePath = path.join(nextPagesDir, '_error.js')
|
||||||
const errorPageEntry = createEntry(errorPagePath, 'pages/_error.js') // default error.js
|
const errorPageEntry = createEntry(errorPagePath, {name: 'pages/_error.js'}) // default error.js
|
||||||
if (!entries[errorPageEntry.name]) {
|
if (!entries[errorPageEntry.name]) {
|
||||||
entries[errorPageEntry.name] = errorPageEntry.files
|
entries[errorPageEntry.name] = errorPageEntry.files
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isServer) {
|
if (isServer) {
|
||||||
const documentPagePath = path.join(nextPagesDir, '_document.js')
|
const documentPagePath = path.join(nextPagesDir, '_document.js')
|
||||||
const documentPageEntry = createEntry(documentPagePath, 'pages/_document.js')
|
const documentPageEntry = createEntry(documentPagePath, {name: 'pages/_document.js'}) // default _document.js
|
||||||
if (!entries[documentPageEntry.name]) {
|
if (!entries[documentPageEntry.name]) {
|
||||||
entries[documentPageEntry.name] = documentPageEntry.files
|
entries[documentPageEntry.name] = documentPageEntry.files
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@ const cache = new Map()
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
webpack: null,
|
webpack: null,
|
||||||
webpackDevMiddleware: null,
|
webpackDevMiddleware: null,
|
||||||
|
poweredByHeader: true,
|
||||||
distDir: '.next',
|
distDir: '.next',
|
||||||
assetPrefix: '',
|
assetPrefix: '',
|
||||||
configOrigin: 'default',
|
configOrigin: 'default',
|
||||||
useFileSystemPublicRoutes: true
|
useFileSystemPublicRoutes: true,
|
||||||
|
pageExtensions: ['jsx', 'js'] // jsx before js because otherwise regex matching will match js first
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function getConfig (dir, customConfig) {
|
export default function getConfig (dir, customConfig) {
|
||||||
|
@ -32,9 +34,6 @@ function loadConfig (dir, customConfig) {
|
||||||
if (path && path.length) {
|
if (path && path.length) {
|
||||||
const userConfigModule = require(path)
|
const userConfigModule = require(path)
|
||||||
userConfig = userConfigModule.default || userConfigModule
|
userConfig = userConfigModule.default || userConfigModule
|
||||||
if (userConfig.poweredByHeader === true || userConfig.poweredByHeader === false) {
|
|
||||||
console.warn('> the `poweredByHeader` option has been removed https://err.sh/zeit/next.js/powered-by-header-option-removed')
|
|
||||||
}
|
|
||||||
userConfig.configOrigin = 'next.config.js'
|
userConfig.configOrigin = 'next.config.js'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,12 +84,12 @@ export class Head extends Component {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { head, styles, __NEXT_DATA__ } = this.context._documentProps
|
const { head, styles, __NEXT_DATA__ } = this.context._documentProps
|
||||||
const { pathname, buildId, assetPrefix } = __NEXT_DATA__
|
const { page, pathname, buildId, assetPrefix } = __NEXT_DATA__
|
||||||
const pagePathname = getPagePathname(pathname)
|
const pagePathname = getPagePathname(pathname)
|
||||||
|
|
||||||
return <head {...this.props}>
|
return <head {...this.props}>
|
||||||
{(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))}
|
{(head || []).map((h, i) => React.cloneElement(h, { key: h.key || i }))}
|
||||||
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} as='script' />
|
{page !== '/_error' && <link rel='preload' href={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} as='script' />}
|
||||||
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error.js`} as='script' />
|
<link rel='preload' href={`${assetPrefix}/_next/${buildId}/page/_error.js`} as='script' />
|
||||||
{this.getPreloadDynamicChunks()}
|
{this.getPreloadDynamicChunks()}
|
||||||
{this.getPreloadMainLinks()}
|
{this.getPreloadMainLinks()}
|
||||||
|
@ -173,7 +173,7 @@ export class NextScript extends Component {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { staticMarkup, __NEXT_DATA__, chunks } = this.context._documentProps
|
const { staticMarkup, __NEXT_DATA__, chunks } = this.context._documentProps
|
||||||
const { pathname, buildId, assetPrefix } = __NEXT_DATA__
|
const { page, pathname, buildId, assetPrefix } = __NEXT_DATA__
|
||||||
const pagePathname = getPagePathname(pathname)
|
const pagePathname = getPagePathname(pathname)
|
||||||
|
|
||||||
__NEXT_DATA__.chunks = chunks.names
|
__NEXT_DATA__.chunks = chunks.names
|
||||||
|
@ -193,9 +193,18 @@ export class NextScript extends Component {
|
||||||
__NEXT_REGISTER_CHUNK = function (chunkName, fn) {
|
__NEXT_REGISTER_CHUNK = function (chunkName, fn) {
|
||||||
__NEXT_LOADED_CHUNKS__.push({ chunkName: chunkName, fn: fn })
|
__NEXT_LOADED_CHUNKS__.push({ chunkName: chunkName, fn: fn })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${page === '_error' && `
|
||||||
|
__NEXT_REGISTER_PAGE(${htmlescape(pathname)}, function() {
|
||||||
|
var error = new Error('Page does not exist: ${htmlescape(pathname)}')
|
||||||
|
error.statusCode = 404
|
||||||
|
|
||||||
|
return { error: error }
|
||||||
|
})
|
||||||
|
`}
|
||||||
`
|
`
|
||||||
}} />}
|
}} />}
|
||||||
<script async id={`__NEXT_PAGE__${pathname}`} type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} />
|
{page !== '/_error' && <script async id={`__NEXT_PAGE__${pathname}`} type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page${pagePathname}`} />}
|
||||||
<script async id={`__NEXT_PAGE__/_error`} type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page/_error.js`} />
|
<script async id={`__NEXT_PAGE__/_error`} type='text/javascript' src={`${assetPrefix}/_next/${buildId}/page/_error.js`} />
|
||||||
{staticMarkup ? null : this.getDynamicChunks()}
|
{staticMarkup ? null : this.getDynamicChunks()}
|
||||||
{staticMarkup ? null : this.getScripts()}
|
{staticMarkup ? null : this.getScripts()}
|
||||||
|
|
|
@ -49,6 +49,15 @@ export default async function (dir, options, configuration) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy .next/static directory
|
||||||
|
if (existsSync(join(nextDir, 'static'))) {
|
||||||
|
log(' copying "static build" directory')
|
||||||
|
await cp(
|
||||||
|
join(nextDir, 'static'),
|
||||||
|
join(outDir, '_next', 'static')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Copy dynamic import chunks
|
// Copy dynamic import chunks
|
||||||
if (existsSync(join(nextDir, 'chunks'))) {
|
if (existsSync(join(nextDir, 'chunks'))) {
|
||||||
log(' copying dynamic import chunks')
|
log(' copying dynamic import chunks')
|
||||||
|
@ -76,6 +85,7 @@ export default async function (dir, options, configuration) {
|
||||||
// Start the rendering process
|
// Start the rendering process
|
||||||
const renderOpts = {
|
const renderOpts = {
|
||||||
dir,
|
dir,
|
||||||
|
dist: config.distDir,
|
||||||
buildStats,
|
buildStats,
|
||||||
buildId,
|
buildId,
|
||||||
nextExport: true,
|
nextExport: true,
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { join, relative, sep } from 'path'
|
import { join, relative, sep } from 'path'
|
||||||
import WebpackDevMiddleware from 'webpack-dev-middleware'
|
import WebpackDevMiddleware from 'webpack-dev-middleware'
|
||||||
import WebpackHotMiddleware from 'webpack-hot-middleware'
|
import WebpackHotMiddleware from 'webpack-hot-middleware'
|
||||||
|
import del from 'del'
|
||||||
import onDemandEntryHandler from './on-demand-entry-handler'
|
import onDemandEntryHandler from './on-demand-entry-handler'
|
||||||
import webpack from 'webpack'
|
import webpack from 'webpack'
|
||||||
import getBaseWebpackConfig from './build/webpack'
|
import getBaseWebpackConfig from './build/webpack'
|
||||||
import clean from './build/clean'
|
|
||||||
import getConfig from './config'
|
|
||||||
import UUID from 'uuid'
|
import UUID from 'uuid'
|
||||||
import {
|
import {
|
||||||
IS_BUNDLED_PAGE,
|
IS_BUNDLED_PAGE,
|
||||||
|
@ -13,7 +12,7 @@ import {
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
export default class HotReloader {
|
export default class HotReloader {
|
||||||
constructor (dir, { quiet, conf } = {}) {
|
constructor (dir, { quiet, config } = {}) {
|
||||||
this.dir = dir
|
this.dir = dir
|
||||||
this.quiet = quiet
|
this.quiet = quiet
|
||||||
this.middlewares = []
|
this.middlewares = []
|
||||||
|
@ -32,7 +31,7 @@ export default class HotReloader {
|
||||||
// it should be the same value.
|
// it should be the same value.
|
||||||
this.buildId = UUID.v4()
|
this.buildId = UUID.v4()
|
||||||
|
|
||||||
this.config = getConfig(dir, conf)
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
async run (req, res) {
|
async run (req, res) {
|
||||||
|
@ -55,8 +54,12 @@ export default class HotReloader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clean () {
|
||||||
|
return del(join(this.dir, this.config.distDir), { force: true })
|
||||||
|
}
|
||||||
|
|
||||||
async start () {
|
async start () {
|
||||||
await clean(this.dir)
|
await this.clean()
|
||||||
|
|
||||||
const configs = await Promise.all([
|
const configs = await Promise.all([
|
||||||
getBaseWebpackConfig(this.dir, { dev: true, isServer: false, config: this.config }),
|
getBaseWebpackConfig(this.dir, { dev: true, isServer: false, config: this.config }),
|
||||||
|
@ -86,7 +89,7 @@ export default class HotReloader {
|
||||||
async reload () {
|
async reload () {
|
||||||
this.stats = null
|
this.stats = null
|
||||||
|
|
||||||
await clean(this.dir)
|
await this.clean()
|
||||||
|
|
||||||
const configs = await Promise.all([
|
const configs = await Promise.all([
|
||||||
getBaseWebpackConfig(this.dir, { dev: true, isServer: false, config: this.config }),
|
getBaseWebpackConfig(this.dir, { dev: true, isServer: false, config: this.config }),
|
||||||
|
@ -225,6 +228,7 @@ export default class HotReloader {
|
||||||
dir: this.dir,
|
dir: this.dir,
|
||||||
dev: true,
|
dev: true,
|
||||||
reload: this.reload.bind(this),
|
reload: this.reload.bind(this),
|
||||||
|
pageExtensions: this.config.pageExtensions,
|
||||||
...this.config.onDemandEntries
|
...this.config.onDemandEntries
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -32,11 +32,12 @@ export default class Server {
|
||||||
this.dev = dev
|
this.dev = dev
|
||||||
this.quiet = quiet
|
this.quiet = quiet
|
||||||
this.router = new Router()
|
this.router = new Router()
|
||||||
this.hotReloader = dev ? this.getHotReloader(this.dir, { quiet, conf }) : null
|
|
||||||
this.http = null
|
this.http = null
|
||||||
this.config = getConfig(this.dir, conf)
|
this.config = getConfig(this.dir, conf)
|
||||||
this.dist = this.config.distDir
|
this.dist = this.config.distDir
|
||||||
|
|
||||||
|
this.hotReloader = dev ? this.getHotReloader(this.dir, { quiet, config: this.config }) : null
|
||||||
|
|
||||||
if (dev) {
|
if (dev) {
|
||||||
updateNotifier(pkg, 'next')
|
updateNotifier(pkg, 'next')
|
||||||
}
|
}
|
||||||
|
@ -51,6 +52,7 @@ export default class Server {
|
||||||
dev,
|
dev,
|
||||||
staticMarkup,
|
staticMarkup,
|
||||||
dir: this.dir,
|
dir: this.dir,
|
||||||
|
dist: this.dist,
|
||||||
hotReloader: this.hotReloader,
|
hotReloader: this.hotReloader,
|
||||||
buildStats: this.buildStats,
|
buildStats: this.buildStats,
|
||||||
buildId: this.buildId,
|
buildId: this.buildId,
|
||||||
|
@ -183,8 +185,7 @@ export default class Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dist = getConfig(this.dir).distDir
|
const path = join(this.dir, this.dist, 'bundles', 'pages', `${page}.js.map`)
|
||||||
const path = join(this.dir, dist, 'bundles', 'pages', `${page}.js.map`)
|
|
||||||
await serveStatic(req, res, path)
|
await serveStatic(req, res, path)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -241,7 +242,7 @@ export default class Server {
|
||||||
},
|
},
|
||||||
|
|
||||||
'/_next/static/:path*': async (req, res, params) => {
|
'/_next/static/:path*': async (req, res, params) => {
|
||||||
const p = join(this.dist, 'static', ...(params.path || []))
|
const p = join(this.dir, this.dist, 'static', ...(params.path || []))
|
||||||
await this.serveStatic(req, res, p)
|
await this.serveStatic(req, res, p)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -322,7 +323,9 @@ export default class Server {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.config.poweredByHeader) {
|
||||||
res.setHeader('X-Powered-By', `Next.js ${pkg.version}`)
|
res.setHeader('X-Powered-By', `Next.js ${pkg.version}`)
|
||||||
|
}
|
||||||
return sendHTML(req, res, html, req.method, this.renderOpts)
|
return sendHTML(req, res, html, req.method, this.renderOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin'
|
import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin'
|
||||||
import { EventEmitter } from 'events'
|
import { EventEmitter } from 'events'
|
||||||
import { join, relative } from 'path'
|
import { join } from 'path'
|
||||||
import { parse } from 'url'
|
import { parse } from 'url'
|
||||||
import touch from 'touch'
|
import touch from 'touch'
|
||||||
import resolvePath from './resolve'
|
import glob from 'glob-promise'
|
||||||
|
import {normalizePagePath, pageNotFoundError} from './require'
|
||||||
import {createEntry} from './build/webpack/utils'
|
import {createEntry} from './build/webpack/utils'
|
||||||
import { MATCH_ROUTE_NAME, IS_BUNDLED_PAGE } from './utils'
|
import { MATCH_ROUTE_NAME, IS_BUNDLED_PAGE } from './utils'
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
|
||||||
dir,
|
dir,
|
||||||
dev,
|
dev,
|
||||||
reload,
|
reload,
|
||||||
|
pageExtensions,
|
||||||
maxInactiveAge = 1000 * 60,
|
maxInactiveAge = 1000 * 60,
|
||||||
pagesBufferLength = 2
|
pagesBufferLength = 2
|
||||||
}) {
|
}) {
|
||||||
|
@ -37,7 +39,7 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
|
||||||
const allEntries = Object.keys(entries).map((page) => {
|
const allEntries = Object.keys(entries).map((page) => {
|
||||||
const { name, entry } = entries[page]
|
const { name, entry } = entries[page]
|
||||||
entries[page].status = BUILDING
|
entries[page].status = BUILDING
|
||||||
return addEntry(compilation, this.context, name, entry)
|
return addEntry(compilation, compiler.context, name, entry)
|
||||||
})
|
})
|
||||||
|
|
||||||
Promise.all(allEntries)
|
Promise.all(allEntries)
|
||||||
|
@ -139,10 +141,26 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
|
||||||
async ensurePage (page) {
|
async ensurePage (page) {
|
||||||
await this.waitUntilReloaded()
|
await this.waitUntilReloaded()
|
||||||
page = normalizePage(page)
|
page = normalizePage(page)
|
||||||
|
let normalizedPagePath
|
||||||
|
try {
|
||||||
|
normalizedPagePath = normalizePagePath(page)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
throw pageNotFoundError(normalizedPagePath)
|
||||||
|
}
|
||||||
|
|
||||||
const pagePath = join(dir, 'pages', page)
|
const extensions = pageExtensions.join('|')
|
||||||
const pathname = await resolvePath(pagePath)
|
const paths = await glob(`pages/{${normalizedPagePath}/index,${normalizedPagePath}}.+(${extensions})`, {cwd: dir})
|
||||||
const {name, files} = createEntry(relative(dir, pathname))
|
|
||||||
|
if (paths.length === 0) {
|
||||||
|
throw pageNotFoundError(normalizedPagePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativePathToPage = paths[0]
|
||||||
|
|
||||||
|
const pathname = join(dir, relativePathToPage)
|
||||||
|
|
||||||
|
const {name, files} = createEntry(relativePathToPage, {pageExtensions: extensions})
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
const entryInfo = entries[page]
|
const entryInfo = entries[page]
|
||||||
|
|
|
@ -4,8 +4,7 @@ import { renderToString, renderToStaticMarkup } from 'react-dom/server'
|
||||||
import send from 'send'
|
import send from 'send'
|
||||||
import generateETag from 'etag'
|
import generateETag from 'etag'
|
||||||
import fresh from 'fresh'
|
import fresh from 'fresh'
|
||||||
import requireModule from './require'
|
import requirePage from './require'
|
||||||
import getConfig from './config'
|
|
||||||
import { Router } from '../lib/router'
|
import { Router } from '../lib/router'
|
||||||
import { loadGetInitialProps, isResSent } from '../lib/utils'
|
import { loadGetInitialProps, isResSent } from '../lib/utils'
|
||||||
import { getAvailableChunks } from './utils'
|
import { getAvailableChunks } from './utils'
|
||||||
|
@ -30,7 +29,7 @@ export async function renderError (err, req, res, pathname, query, opts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderErrorToHTML (err, req, res, pathname, query, opts = {}) {
|
export function renderErrorToHTML (err, req, res, pathname, query, opts = {}) {
|
||||||
return doRender(req, res, pathname, query, { ...opts, err, page: '_error' })
|
return doRender(req, res, pathname, query, { ...opts, err, page: '/_error' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doRender (req, res, pathname, query, {
|
async function doRender (req, res, pathname, query, {
|
||||||
|
@ -41,6 +40,7 @@ async function doRender (req, res, pathname, query, {
|
||||||
hotReloader,
|
hotReloader,
|
||||||
assetPrefix,
|
assetPrefix,
|
||||||
availableChunks,
|
availableChunks,
|
||||||
|
dist,
|
||||||
dir = process.cwd(),
|
dir = process.cwd(),
|
||||||
dev = false,
|
dev = false,
|
||||||
staticMarkup = false,
|
staticMarkup = false,
|
||||||
|
@ -48,17 +48,14 @@ async function doRender (req, res, pathname, query, {
|
||||||
} = {}) {
|
} = {}) {
|
||||||
page = page || pathname
|
page = page || pathname
|
||||||
|
|
||||||
|
if (hotReloader) { // In dev mode we use on demand entries to compile the page before rendering
|
||||||
await ensurePage(page, { dir, hotReloader })
|
await ensurePage(page, { dir, hotReloader })
|
||||||
|
}
|
||||||
|
|
||||||
const dist = getConfig(dir).distDir
|
|
||||||
|
|
||||||
const pagePath = join(dir, dist, 'dist', 'bundles', 'pages', page)
|
|
||||||
const documentPath = join(dir, dist, 'dist', 'bundles', 'pages', '_document')
|
const documentPath = join(dir, dist, 'dist', 'bundles', 'pages', '_document')
|
||||||
|
|
||||||
let [Component, Document] = await Promise.all([
|
let Component = requirePage(page, {dir, dist})
|
||||||
requireModule(pagePath),
|
let Document = require(documentPath)
|
||||||
requireModule(documentPath)
|
|
||||||
])
|
|
||||||
Component = Component.default || Component
|
Component = Component.default || Component
|
||||||
Document = Document.default || Document
|
Document = Document.default || Document
|
||||||
const asPath = req.url
|
const asPath = req.url
|
||||||
|
@ -105,7 +102,8 @@ async function doRender (req, res, pathname, query, {
|
||||||
const doc = createElement(Document, {
|
const doc = createElement(Document, {
|
||||||
__NEXT_DATA__: {
|
__NEXT_DATA__: {
|
||||||
props,
|
props,
|
||||||
pathname,
|
page, // the rendered page
|
||||||
|
pathname, // the requested path
|
||||||
query,
|
query,
|
||||||
buildId,
|
buildId,
|
||||||
buildStats,
|
buildStats,
|
||||||
|
@ -224,8 +222,7 @@ export function serveStatic (req, res, path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensurePage (page, { dir, hotReloader }) {
|
async function ensurePage (page, { dir, hotReloader }) {
|
||||||
if (!hotReloader) return
|
if (page === '/_error') return
|
||||||
if (page === '_error' || page === '_document') return
|
|
||||||
|
|
||||||
await hotReloader.ensurePage(page)
|
await hotReloader.ensurePage(page)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,63 @@
|
||||||
import resolve from './resolve'
|
import {join, parse, normalize, sep} from 'path'
|
||||||
|
|
||||||
export default async function requireModule (path) {
|
export function pageNotFoundError (page) {
|
||||||
const f = await resolve(path)
|
const err = new Error(`Cannot find module for page: ${page}`)
|
||||||
return require(f)
|
err.code = 'ENOENT'
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizePagePath (page) {
|
||||||
|
// If the page is `/` we need to append `/index`, otherwise the returned directory root will be bundles instead of pages
|
||||||
|
if (page === '/') {
|
||||||
|
page = '/index'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve on anything that doesn't start with `/`
|
||||||
|
if (page[0] !== '/') {
|
||||||
|
page = `/${page}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows compatibility
|
||||||
|
if (sep !== '/') {
|
||||||
|
page = page.replace(/\//g, sep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw when using ../ etc in the pathname
|
||||||
|
const resolvedPage = normalize(page)
|
||||||
|
if (page !== resolvedPage) {
|
||||||
|
throw new Error('Requested and resolved page mismatch')
|
||||||
|
}
|
||||||
|
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPagePath (page, {dir, dist}) {
|
||||||
|
const pageBundlesPath = join(dir, dist, 'dist', 'bundles', 'pages')
|
||||||
|
|
||||||
|
try {
|
||||||
|
page = normalizePagePath(page)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
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')
|
||||||
|
throw pageNotFoundError(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pagePath
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function requirePage (page, {dir, dist}) {
|
||||||
|
const pagePath = getPagePath(page, {dir, dist})
|
||||||
|
try {
|
||||||
|
return require(pagePath)
|
||||||
|
} catch (err) {
|
||||||
|
throw pageNotFoundError(page)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
import { join, sep, parse } from 'path'
|
|
||||||
import fs from 'mz/fs'
|
|
||||||
import glob from 'glob-promise'
|
|
||||||
|
|
||||||
export default async function resolve (id) {
|
|
||||||
const paths = getPaths(id)
|
|
||||||
for (const p of paths) {
|
|
||||||
if (await isFile(p)) {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const err = new Error(`Cannot find module ${id}`)
|
|
||||||
err.code = 'ENOENT'
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveFromList (id, files) {
|
|
||||||
const paths = getPaths(id)
|
|
||||||
const set = new Set(files)
|
|
||||||
for (const p of paths) {
|
|
||||||
if (set.has(p)) return p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPaths (id) {
|
|
||||||
const i = sep === '/' ? id : id.replace(/\//g, sep)
|
|
||||||
|
|
||||||
if (i.slice(-3) === '.js') return [i]
|
|
||||||
if (i.slice(-4) === '.jsx') return [i]
|
|
||||||
if (i.slice(-4) === '.tsx') return [i]
|
|
||||||
if (i.slice(-3) === '.ts') return [i]
|
|
||||||
if (i.slice(-5) === '.json') return [i]
|
|
||||||
|
|
||||||
if (i[i.length - 1] === sep) {
|
|
||||||
return [
|
|
||||||
i + 'index.js',
|
|
||||||
i + 'index.jsx',
|
|
||||||
i + 'index.ts',
|
|
||||||
i + 'index.tsx',
|
|
||||||
i + 'index.json'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
i + '.js',
|
|
||||||
join(i, 'index.js'),
|
|
||||||
i + '.jsx',
|
|
||||||
join(i, 'index.jsx'),
|
|
||||||
i + '.tsx',
|
|
||||||
join(i, 'index.tsx'),
|
|
||||||
i + '.ts',
|
|
||||||
join(i, 'index.ts'),
|
|
||||||
i + '.json',
|
|
||||||
join(i, 'index.json')
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function isFile (p) {
|
|
||||||
let stat
|
|
||||||
try {
|
|
||||||
stat = await fs.stat(p)
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code === 'ENOENT') return false
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need the path to be case sensitive
|
|
||||||
const realpath = await getTrueFilePath(p)
|
|
||||||
if (p !== realpath) return false
|
|
||||||
|
|
||||||
return stat.isFile() || stat.isFIFO()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is based on the stackoverflow answer: http://stackoverflow.com/a/33139702/457224
|
|
||||||
// We assume we'll get properly normalized path names as p
|
|
||||||
async function getTrueFilePath (p) {
|
|
||||||
let fsPathNormalized = p
|
|
||||||
// OSX: HFS+ stores filenames in NFD (decomposed normal form) Unicode format,
|
|
||||||
// so we must ensure that the input path is in that format first.
|
|
||||||
if (process.platform === 'darwin') fsPathNormalized = fsPathNormalized.normalize('NFD')
|
|
||||||
|
|
||||||
// !! Windows: Curiously, the drive component mustn't be part of a glob,
|
|
||||||
// !! otherwise glob.sync() will invariably match nothing.
|
|
||||||
// !! Thus, we remove the drive component and instead pass it in as the 'cwd'
|
|
||||||
// !! (working dir.) property below.
|
|
||||||
var pathRoot = parse(fsPathNormalized).root
|
|
||||||
var noDrivePath = fsPathNormalized.slice(Math.max(pathRoot.length - 1, 0))
|
|
||||||
|
|
||||||
// Perform case-insensitive globbing (on Windows, relative to the drive /
|
|
||||||
// network share) and return the 1st match, if any.
|
|
||||||
// Fortunately, glob() with nocase case-corrects the input even if it is
|
|
||||||
// a *literal* path.
|
|
||||||
const result = await glob(noDrivePath, { nocase: true, cwd: pathRoot })
|
|
||||||
return result[0]
|
|
||||||
}
|
|
|
@ -370,16 +370,8 @@ export default (context, render) => {
|
||||||
browser.close()
|
browser.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should work with dir/index page ', async () => {
|
|
||||||
const browser = await webdriver(context.appPort, '/nested-cdm/index')
|
|
||||||
const text = await browser.elementByCss('p').text()
|
|
||||||
|
|
||||||
expect(text).toBe('ComponentDidMount executed on client.')
|
|
||||||
browser.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should work with dir/ page ', async () => {
|
it('should work with dir/ page ', async () => {
|
||||||
const browser = await webdriver(context.appPort, '/nested-cdm/')
|
const browser = await webdriver(context.appPort, '/nested-cdm')
|
||||||
const text = await browser.elementByCss('p').text()
|
const text = await browser.elementByCss('p').text()
|
||||||
|
|
||||||
expect(text).toBe('ComponentDidMount executed on client.')
|
expect(text).toBe('ComponentDidMount executed on client.')
|
||||||
|
@ -426,7 +418,7 @@ export default (context, render) => {
|
||||||
describe('with asPath', () => {
|
describe('with asPath', () => {
|
||||||
describe('inside getInitialProps', () => {
|
describe('inside getInitialProps', () => {
|
||||||
it('should show the correct asPath with a Link with as prop', async () => {
|
it('should show the correct asPath with a Link with as prop', async () => {
|
||||||
const browser = await webdriver(context.appPort, '/nav/')
|
const browser = await webdriver(context.appPort, '/nav')
|
||||||
const asPath = await browser
|
const asPath = await browser
|
||||||
.elementByCss('#as-path-link').click()
|
.elementByCss('#as-path-link').click()
|
||||||
.waitForElementByCss('.as-path-content')
|
.waitForElementByCss('.as-path-content')
|
||||||
|
@ -437,7 +429,7 @@ export default (context, render) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show the correct asPath with a Link without the as prop', async () => {
|
it('should show the correct asPath with a Link without the as prop', async () => {
|
||||||
const browser = await webdriver(context.appPort, '/nav/')
|
const browser = await webdriver(context.appPort, '/nav')
|
||||||
const asPath = await browser
|
const asPath = await browser
|
||||||
.elementByCss('#as-path-link-no-as').click()
|
.elementByCss('#as-path-link-no-as').click()
|
||||||
.waitForElementByCss('.as-path-content')
|
.waitForElementByCss('.as-path-content')
|
||||||
|
@ -450,7 +442,7 @@ export default (context, render) => {
|
||||||
|
|
||||||
describe('with next/router', () => {
|
describe('with next/router', () => {
|
||||||
it('should show the correct asPath', async () => {
|
it('should show the correct asPath', async () => {
|
||||||
const browser = await webdriver(context.appPort, '/nav/')
|
const browser = await webdriver(context.appPort, '/nav')
|
||||||
const asPath = await browser
|
const asPath = await browser
|
||||||
.elementByCss('#as-path-using-router-link').click()
|
.elementByCss('#as-path-using-router-link').click()
|
||||||
.waitForElementByCss('.as-path-content')
|
.waitForElementByCss('.as-path-content')
|
||||||
|
@ -461,5 +453,31 @@ export default (context, render) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('with 404 pages', () => {
|
||||||
|
it('should 404 on not existent page', async () => {
|
||||||
|
const browser = await webdriver(context.appPort, '/non-existent')
|
||||||
|
expect(await browser.elementByCss('h1').text()).toBe('404')
|
||||||
|
expect(await browser.elementByCss('h2').text()).toBe('This page could not be found.')
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should 404 for <page>/', async () => {
|
||||||
|
const browser = await webdriver(context.appPort, '/nav/about/')
|
||||||
|
expect(await browser.elementByCss('h1').text()).toBe('404')
|
||||||
|
expect(await browser.elementByCss('h2').text()).toBe('This page could not be found.')
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should should not contain a page script in a 404 page', async () => {
|
||||||
|
const browser = await webdriver(context.appPort, '/non-existent')
|
||||||
|
const scripts = await browser.elementsByCss('script[src]')
|
||||||
|
for (const script of scripts) {
|
||||||
|
const src = await script.getAttribute('src')
|
||||||
|
expect(src.includes('/non-existent')).toBeFalsy()
|
||||||
|
}
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,12 +106,30 @@ export default function ({ app }, suiteName, render, fetch) {
|
||||||
expect($('.as-path-content').text()).toBe('/nav/as-path?aa=10')
|
expect($('.as-path-content').text()).toBe('/nav/as-path?aa=10')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('error 404', async () => {
|
describe('404', () => {
|
||||||
|
it('should 404 on not existent page', async () => {
|
||||||
const $ = await get$('/non-existent')
|
const $ = await get$('/non-existent')
|
||||||
expect($('h1').text()).toBe('404')
|
expect($('h1').text()).toBe('404')
|
||||||
expect($('h2').text()).toBe('This page could not be found.')
|
expect($('h2').text()).toBe('This page could not be found.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should 404 for <page>/', async () => {
|
||||||
|
const $ = await get$('/nav/about/')
|
||||||
|
expect($('h1').text()).toBe('404')
|
||||||
|
expect($('h2').text()).toBe('This page could not be found.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should should not contain a page script in a 404 page', async () => {
|
||||||
|
const $ = await get$('/non-existent')
|
||||||
|
$('script[src]').each((index, element) => {
|
||||||
|
const src = $(element).attr('src')
|
||||||
|
if (src.includes('/non-existent')) {
|
||||||
|
throw new Error('Page includes page script')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('with the HOC based router', () => {
|
describe('with the HOC based router', () => {
|
||||||
it('should navigate as expected', async () => {
|
it('should navigate as expected', async () => {
|
||||||
const $ = await get$('/nav/with-hoc')
|
const $ = await get$('/nav/with-hoc')
|
||||||
|
|
33
test/integration/production/pages/counter.js
Normal file
33
test/integration/production/pages/counter.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { Component } from 'react'
|
||||||
|
import Router from 'next/router'
|
||||||
|
|
||||||
|
let counter = 0
|
||||||
|
|
||||||
|
export default class extends Component {
|
||||||
|
increase () {
|
||||||
|
counter++
|
||||||
|
this.forceUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
visitQueryStringPage () {
|
||||||
|
const href = { pathname: '/nav/querystring', query: { id: 10 } }
|
||||||
|
const as = { pathname: '/nav/querystring/10', hash: '10' }
|
||||||
|
Router.push(href, as)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div id='counter-page'>
|
||||||
|
<Link href='/no-such-page'><a id='no-such-page'>No Such Page</a></Link>
|
||||||
|
<br />
|
||||||
|
<Link href='/no-such-page' prefetch><a id='no-such-page-prefetch'>No Such Page (with prefetch)</a></Link>
|
||||||
|
<p>This is the home.</p>
|
||||||
|
<div id='counter'>
|
||||||
|
Counter: {counter}
|
||||||
|
</div>
|
||||||
|
<button id='increase' onClick={() => this.increase()}>Increase</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,8 @@ import {
|
||||||
nextBuild,
|
nextBuild,
|
||||||
startApp,
|
startApp,
|
||||||
stopApp,
|
stopApp,
|
||||||
renderViaHTTP
|
renderViaHTTP,
|
||||||
|
waitFor
|
||||||
} from 'next-test-utils'
|
} from 'next-test-utils'
|
||||||
import webdriver from 'next-webdriver'
|
import webdriver from 'next-webdriver'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
|
@ -93,6 +94,55 @@ describe('Production Usage', () => {
|
||||||
const data = await renderViaHTTP(appPort, '/static/data/item.txt')
|
const data = await renderViaHTTP(appPort, '/static/data/item.txt')
|
||||||
expect(data).toBe('item')
|
expect(data).toBe('item')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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()
|
||||||
|
expect(counter).toBe('Counter: 2')
|
||||||
|
|
||||||
|
// When we go to the 404 page, it'll do a hard reload.
|
||||||
|
// So, it's possible for the front proxy to load a page from another zone.
|
||||||
|
// Since the page is reloaded, when we go back to the counter page again,
|
||||||
|
// previous counter value should be gone.
|
||||||
|
const counterAfter404Page = await browser
|
||||||
|
.elementByCss('#no-such-page').click()
|
||||||
|
.waitForElementByCss('h1')
|
||||||
|
.back()
|
||||||
|
.waitForElementByCss('#counter-page')
|
||||||
|
.elementByCss('#counter').text()
|
||||||
|
expect(counterAfter404Page).toBe('Counter: 0')
|
||||||
|
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
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()
|
||||||
|
expect(counter).toBe('Counter: 2')
|
||||||
|
|
||||||
|
// Let the browser to prefetch the page and error it on the console.
|
||||||
|
await waitFor(3000)
|
||||||
|
const browserLogs = await browser.log('browser')
|
||||||
|
expect(browserLogs[0].message).toMatch(/Page does not exist: \/no-such-page/)
|
||||||
|
|
||||||
|
// When we go to the 404 page, it'll do a hard reload.
|
||||||
|
// So, it's possible for the front proxy to load a page from another zone.
|
||||||
|
// Since the page is reloaded, when we go back to the counter page again,
|
||||||
|
// previous counter value should be gone.
|
||||||
|
const counterAfter404Page = await browser
|
||||||
|
.elementByCss('#no-such-page-prefetch').click()
|
||||||
|
.waitForElementByCss('h1')
|
||||||
|
.back()
|
||||||
|
.waitForElementByCss('#counter-page')
|
||||||
|
.elementByCss('#counter').text()
|
||||||
|
expect(counterAfter404Page).toBe('Counter: 0')
|
||||||
|
|
||||||
|
browser.close()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('X-Powered-By header', () => {
|
describe('X-Powered-By header', () => {
|
||||||
|
@ -112,6 +162,26 @@ describe('Production Usage', () => {
|
||||||
await app.render(req, res, req.url)
|
await app.render(req, res, req.url)
|
||||||
expect(headers['X-Powered-By']).toEqual(`Next.js ${pkg.version}`)
|
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 = {
|
||||||
|
getHeader () {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
setHeader (key, value) {
|
||||||
|
if (key === 'XPoweredBy') {
|
||||||
|
throw new Error('Should not set the XPoweredBy 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))
|
||||||
|
|
1
test/isolated/_resolvedata/.gitignore
vendored
Normal file
1
test/isolated/_resolvedata/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
!dist
|
3
test/isolated/_resolvedata/dist/bundles/pages/index.js
vendored
Normal file
3
test/isolated/_resolvedata/dist/bundles/pages/index.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
test: 'hello'
|
||||||
|
}
|
3
test/isolated/_resolvedata/dist/bundles/pages/world.js
vendored
Normal file
3
test/isolated/_resolvedata/dist/bundles/pages/world.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
test: 'world'
|
||||||
|
}
|
82
test/isolated/require-page.test.js
Normal file
82
test/isolated/require-page.test.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/* global describe, it, expect */
|
||||||
|
|
||||||
|
import { join, sep } 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')
|
||||||
|
|
||||||
|
describe('pageNotFoundError', () => {
|
||||||
|
it('Should throw error with ENOENT code', () => {
|
||||||
|
try {
|
||||||
|
pageNotFoundError('test')
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.code).toBe('ENOENT')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('normalizePagePath', () => {
|
||||||
|
it('Should turn / into /index', () => {
|
||||||
|
expect(normalizePagePath('/')).toBe(`${sep}index`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should turn _error into /_error', () => {
|
||||||
|
expect(normalizePagePath('_error')).toBe(`${sep}_error`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should turn /abc into /abc', () => {
|
||||||
|
expect(normalizePagePath('/abc')).toBe(`${sep}abc`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should turn /abc/def into /abc/def', () => {
|
||||||
|
expect(normalizePagePath('/abc/def')).toBe(`${sep}abc${sep}def`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should throw on /../../test.js', () => {
|
||||||
|
expect(() => normalizePagePath('/../../test.js')).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getPagePath', () => {
|
||||||
|
it('Should append /index to the / page', () => {
|
||||||
|
const pagePath = getPagePath('/', {dir, dist})
|
||||||
|
expect(pagePath).toBe(join(pathToBundles, `${sep}index`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should prepend / when a page does not have it', () => {
|
||||||
|
const pagePath = getPagePath('_error', {dir, dist})
|
||||||
|
expect(pagePath).toBe(join(pathToBundles, `${sep}_error`))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should throw with paths containing ../', () => {
|
||||||
|
expect(() => getPagePath('/../../package.json', {dir, dist})).toThrow()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('requirePage', () => {
|
||||||
|
it('Should require /index.js when using /', () => {
|
||||||
|
const page = requirePage('/', {dir: __dirname, dist: '_resolvedata'})
|
||||||
|
expect(page.test).toBe('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should require /index.js when using /index', () => {
|
||||||
|
const page = requirePage('/index', {dir: __dirname, dist: '_resolvedata'})
|
||||||
|
expect(page.test).toBe('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should require /world.js when using /world', () => {
|
||||||
|
const page = requirePage('/world', {dir: __dirname, dist: '_resolvedata'})
|
||||||
|
expect(page.test).toBe('world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should throw when using /../../test.js', () => {
|
||||||
|
expect(() => requirePage('/../../test.js', {dir: __dirname, dist: '_resolvedata'})).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should throw when using non existent pages like /non-existent.js', () => {
|
||||||
|
expect(() => requirePage('/non-existent.js', {dir: __dirname, dist: '_resolvedata'})).toThrow()
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,52 +0,0 @@
|
||||||
/* global describe, it, expect */
|
|
||||||
|
|
||||||
import { join } from 'path'
|
|
||||||
import resolve from '../../dist/server/resolve'
|
|
||||||
|
|
||||||
const dataPath = join(__dirname, '_resolvedata')
|
|
||||||
|
|
||||||
describe('Resolve', () => {
|
|
||||||
it('should resolve a .js path', async () => {
|
|
||||||
const p = await resolve(join(dataPath, 'one.js'))
|
|
||||||
expect(p).toBe(join(dataPath, 'one.js'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resolve a .json path', async () => {
|
|
||||||
const p = await resolve(join(dataPath, 'two.json'))
|
|
||||||
expect(p).toBe(join(dataPath, 'two.json'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resolve a module without an extension', async () => {
|
|
||||||
const p = await resolve(join(dataPath, 'one'))
|
|
||||||
expect(p).toBe(join(dataPath, 'one.js'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resolve a .js module in a directory without /', async () => {
|
|
||||||
const p = await resolve(join(dataPath, 'aa'))
|
|
||||||
expect(p).toBe(join(dataPath, 'aa', 'index.js'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resolve a .js module in a directory with /', async () => {
|
|
||||||
const p = await resolve(join(dataPath, 'aa/'))
|
|
||||||
expect(p).toBe(join(dataPath, 'aa', 'index.js'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resolve a .json module in a directory', async () => {
|
|
||||||
const p = await resolve(join(dataPath, 'bb'))
|
|
||||||
expect(p).toBe(join(dataPath, 'bb', 'index.json'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resolve give priority to index.js over index.json', async () => {
|
|
||||||
const p = await resolve(join(dataPath, 'cc'))
|
|
||||||
expect(p).toBe(join(dataPath, 'cc', 'index.js'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error for non existing paths', async () => {
|
|
||||||
try {
|
|
||||||
await resolve(join(dataPath, 'aaa.js'))
|
|
||||||
throw new Error('Should not run this line.')
|
|
||||||
} catch (ex) {
|
|
||||||
expect(ex.message).toMatch(/Cannot find module/)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
80
test/isolated/webpack-utils.test.js
Normal file
80
test/isolated/webpack-utils.test.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
/* global describe, it, expect */
|
||||||
|
|
||||||
|
import {normalize} from 'path'
|
||||||
|
import {getPageEntries, createEntry} from '../../dist/server/build/webpack/utils'
|
||||||
|
|
||||||
|
describe('createEntry', () => {
|
||||||
|
it('Should turn a path into a page entry', () => {
|
||||||
|
const entry = createEntry('pages/index.js')
|
||||||
|
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
|
||||||
|
expect(entry.files[0]).toBe('./pages/index.js')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have a custom name', () => {
|
||||||
|
const entry = createEntry('pages/index.js', {name: 'something-else.js'})
|
||||||
|
expect(entry.name).toBe(normalize('bundles/something-else.js'))
|
||||||
|
expect(entry.files[0]).toBe('./pages/index.js')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should allow custom extension like .ts to be turned into .js', () => {
|
||||||
|
const entry = createEntry('pages/index.ts', {pageExtensions: ['js', 'ts'].join('|')})
|
||||||
|
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
|
||||||
|
expect(entry.files[0]).toBe('./pages/index.ts')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should allow custom extension like .jsx to be turned into .js', () => {
|
||||||
|
const entry = createEntry('pages/index.jsx', {pageExtensions: ['jsx', 'js'].join('|')})
|
||||||
|
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
|
||||||
|
expect(entry.files[0]).toBe('./pages/index.jsx')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should allow custom extension like .tsx to be turned into .js', () => {
|
||||||
|
const entry = createEntry('pages/index.tsx', {pageExtensions: ['tsx', 'ts'].join('|')})
|
||||||
|
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
|
||||||
|
expect(entry.files[0]).toBe('./pages/index.tsx')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should allow custom extension like .tsx to be turned into .js with another order', () => {
|
||||||
|
const entry = createEntry('pages/index.tsx', {pageExtensions: ['ts', 'tsx'].join('|')})
|
||||||
|
expect(entry.name).toBe(normalize('bundles/pages/index.js'))
|
||||||
|
expect(entry.files[0]).toBe('./pages/index.tsx')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should turn pages/blog/index.js into pages/blog.js', () => {
|
||||||
|
const entry = createEntry('pages/blog/index.js')
|
||||||
|
expect(entry.name).toBe(normalize('bundles/pages/blog.js'))
|
||||||
|
expect(entry.files[0]).toBe('./pages/blog/index.js')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getPageEntries', () => {
|
||||||
|
it('Should return paths', () => {
|
||||||
|
const pagePaths = ['pages/index.js']
|
||||||
|
const pageEntries = getPageEntries(pagePaths)
|
||||||
|
expect(pageEntries[normalize('bundles/pages/index.js')][0]).toBe('./pages/index.js')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should include default _error', () => {
|
||||||
|
const pagePaths = ['pages/index.js']
|
||||||
|
const pageEntries = getPageEntries(pagePaths)
|
||||||
|
expect(pageEntries[normalize('bundles/pages/_error.js')][0]).toMatch(/dist[/\\]pages[/\\]_error\.js/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not include default _error when _error.js is inside the pages directory', () => {
|
||||||
|
const pagePaths = ['pages/index.js', 'pages/_error.js']
|
||||||
|
const pageEntries = getPageEntries(pagePaths)
|
||||||
|
expect(pageEntries[normalize('bundles/pages/_error.js')][0]).toBe('./pages/_error.js')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should include default _document when isServer is true', () => {
|
||||||
|
const pagePaths = ['pages/index.js']
|
||||||
|
const pageEntries = getPageEntries(pagePaths, {isServer: true})
|
||||||
|
expect(pageEntries[normalize('bundles/pages/_document.js')][0]).toMatch(/dist[/\\]pages[/\\]_document\.js/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not include default _document when _document.js is inside the pages directory', () => {
|
||||||
|
const pagePaths = ['pages/index.js', 'pages/_document.js']
|
||||||
|
const pageEntries = getPageEntries(pagePaths, {isServer: true})
|
||||||
|
expect(pageEntries[normalize('bundles/pages/_document.js')][0]).toBe('./pages/_document.js')
|
||||||
|
})
|
||||||
|
})
|
45
yarn.lock
45
yarn.lock
|
@ -3158,10 +3158,14 @@ hoek@4.x.x:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
|
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
|
||||||
|
|
||||||
hoist-non-react-statics@2.3.1, hoist-non-react-statics@^2.3.1:
|
hoist-non-react-statics@2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
|
||||||
|
|
||||||
|
hoist-non-react-statics@^2.5.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
|
||||||
|
|
||||||
home-or-tmp@^2.0.0:
|
home-or-tmp@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
|
||||||
|
@ -5563,19 +5567,14 @@ react-dom@16.2.0:
|
||||||
object-assign "^4.1.1"
|
object-assign "^4.1.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react-hot-loader@4.0.0-beta.18:
|
react-hot-loader@4.0.0-beta.23:
|
||||||
version "4.0.0-beta.18"
|
version "4.0.0-beta.23"
|
||||||
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.0.0-beta.18.tgz#5a3d1b5bd813633380b88c0c660019dbf638975d"
|
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.0.0-beta.23.tgz#dced27e8e2400adb1690eb5b3a0a39b4e0b90615"
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-levenshtein "^2.0.6"
|
fast-levenshtein "^2.0.6"
|
||||||
global "^4.3.0"
|
global "^4.3.0"
|
||||||
hoist-non-react-statics "^2.3.1"
|
hoist-non-react-statics "^2.5.0"
|
||||||
react-stand-in "^4.0.0-beta.18"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react-stand-in@^4.0.0-beta.18:
|
|
||||||
version "4.0.0-beta.21"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-stand-in/-/react-stand-in-4.0.0-beta.21.tgz#fb694e465cb20fab7f36d3284f82b68bbd7a657e"
|
|
||||||
dependencies:
|
|
||||||
shallowequal "^1.0.2"
|
shallowequal "^1.0.2"
|
||||||
|
|
||||||
react@16.2.0:
|
react@16.2.0:
|
||||||
|
@ -6361,25 +6360,25 @@ style-loader@^0.19.1:
|
||||||
loader-utils "^1.0.2"
|
loader-utils "^1.0.2"
|
||||||
schema-utils "^0.3.0"
|
schema-utils "^0.3.0"
|
||||||
|
|
||||||
styled-jsx@2.2.3:
|
styled-jsx@2.2.5:
|
||||||
version "2.2.3"
|
version "2.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-2.2.3.tgz#9fe3df55c852388019c3bf4c026bcbf8b3e003de"
|
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-2.2.5.tgz#3eb98418720655421bfc1a680e2add581726a5e6"
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-plugin-syntax-jsx "6.18.0"
|
babel-plugin-syntax-jsx "6.18.0"
|
||||||
babel-types "6.26.0"
|
babel-types "6.26.0"
|
||||||
convert-source-map "1.5.1"
|
convert-source-map "1.5.1"
|
||||||
source-map "0.6.1"
|
source-map "0.6.1"
|
||||||
string-hash "1.1.3"
|
string-hash "1.1.3"
|
||||||
stylis "3.4.5"
|
stylis "3.4.10"
|
||||||
stylis-rule-sheet "0.0.7"
|
stylis-rule-sheet "0.0.8"
|
||||||
|
|
||||||
stylis-rule-sheet@0.0.7:
|
stylis-rule-sheet@0.0.8:
|
||||||
version "0.0.7"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.7.tgz#5c51dc879141a61821c2094ba91d2cbcf2469c6c"
|
resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.8.tgz#b0d0a126c945b1f3047447a3aae0647013e8d166"
|
||||||
|
|
||||||
stylis@3.4.5:
|
stylis@3.4.10:
|
||||||
version "3.4.5"
|
version "3.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.5.tgz#d7b9595fc18e7b9c8775eca8270a9a1d3e59806e"
|
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.10.tgz#a135cab4b9ff208e327fbb5a6fde3fa991c638ee"
|
||||||
|
|
||||||
supports-color@^2.0.0:
|
supports-color@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -6881,7 +6880,7 @@ webpack-hot-middleware@2.21.0:
|
||||||
querystring "^0.2.0"
|
querystring "^0.2.0"
|
||||||
strip-ansi "^3.0.0"
|
strip-ansi "^3.0.0"
|
||||||
|
|
||||||
webpack-sources@^1.0.1, webpack-sources@^1.1.0:
|
webpack-sources@1.1.0, webpack-sources@^1.0.1, webpack-sources@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
|
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in a new issue