mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Merge branch 'master' into add/hot-reload
This commit is contained in:
commit
1be8447a26
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
|||
node_modules
|
||||
dist
|
||||
.next
|
||||
yarn.lock
|
||||
|
|
45
Readme.md
45
Readme.md
|
@ -30,9 +30,9 @@ Every `import` you declare gets bundled and served with each page
|
|||
|
||||
```jsx
|
||||
import React from 'react'
|
||||
import cowsay from 'cowsay'
|
||||
import cowsay from 'cowsay-browser'
|
||||
export default () => (
|
||||
<pre>{ cowsay('hi there!') }</pre>
|
||||
<pre>{ cowsay({ text: 'hi there!' }) }</pre>
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -46,11 +46,11 @@ We use [Aphrodite](https://github.com/Khan/aphrodite) to provide a great built-i
|
|||
import React from 'react'
|
||||
import { css, StyleSheet } from 'next/css'
|
||||
|
||||
export default () => {
|
||||
export default () => (
|
||||
<div className={ css(styles.main) }>
|
||||
Hello world
|
||||
</div>
|
||||
})
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
|
@ -70,11 +70,13 @@ We expose a built-in component for appending elements to the `<head>` of the pag
|
|||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
export default () => (
|
||||
<Head>
|
||||
<title>My page title</title>
|
||||
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
|
||||
</Head>
|
||||
<p>Hello world!</p>
|
||||
<div>
|
||||
<Head>
|
||||
<title>My page title</title>
|
||||
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
|
||||
</Head>
|
||||
<p>Hello world!</p>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -143,9 +145,19 @@ Each top-level component receives a `url` property with the following API:
|
|||
|
||||
```jsx
|
||||
import React from 'react'
|
||||
export default ({ statusCode }) => (
|
||||
<p>An error { statusCode } occurred</p>
|
||||
)
|
||||
|
||||
export default class Error extends React.Component {
|
||||
static getInitialProps ({ res, xhr }) {
|
||||
const statusCode = res ? res.statusCode : xhr.status
|
||||
return { statusCode }
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<p>An error { this.props.statusCode } occurred</p>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Production deployment
|
||||
|
@ -172,3 +184,12 @@ For example, to deploy with `now` a `package.json` like follows is recommended:
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
### In progress
|
||||
|
||||
The following tasks are planned and part of our roadmap
|
||||
|
||||
- [ ] Add option to supply a `req`, `res` handling function for custom routing
|
||||
- [ ] Add option to extend or replace custom babel configuration
|
||||
- [ ] Add option to extend or replace custom webpack configuration
|
||||
- [ ] Investigate pluggable component-oriented rendering backends (Inferno, Preact, etc)
|
||||
|
|
23
bench/fixtures/basic/pages/css.js
Normal file
23
bench/fixtures/basic/pages/css.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React, { Component } from 'react'
|
||||
import { StyleSheet, css } from 'next/css'
|
||||
|
||||
export default class CrazyCSS extends Component {
|
||||
spans () {
|
||||
const out = []
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
out.push(<span key={i} class={css(styles[`padding-${i}`])}>This is ${i}</span>)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div>{this.spans()}</div>
|
||||
}
|
||||
}
|
||||
|
||||
const spanStyles = {}
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
spanStyles[`padding-${i}`] = { padding: i }
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create(spanStyles)
|
17
bench/fixtures/basic/pages/stateless-big.js
Normal file
17
bench/fixtures/basic/pages/stateless-big.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
import React from 'react'
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<ul>
|
||||
{items()}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const items = () => {
|
||||
var out = new Array(10000)
|
||||
for (let i = 0; i < out.length; i++) {
|
||||
out[i] = <li key={i}>This is row {i + 1}</li>
|
||||
}
|
||||
return out
|
||||
}
|
3
bench/fixtures/basic/pages/stateless.js
Normal file
3
bench/fixtures/basic/pages/stateless.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import React from 'react'
|
||||
|
||||
export default () => <h1>My component!</h1>
|
32
bench/index.js
Normal file
32
bench/index.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
|
||||
import { resolve } from 'path'
|
||||
import build from '../server/build'
|
||||
import { render as _render } from '../server/render'
|
||||
import Benchmark from 'benchmark'
|
||||
|
||||
const dir = resolve(__dirname, 'fixtures', 'basic')
|
||||
const suite = new Benchmark.Suite('Next.js')
|
||||
|
||||
suite
|
||||
.on('start', async () => build(dir))
|
||||
|
||||
.add('Tiny stateless component', async p => {
|
||||
await render('/stateless')
|
||||
p.resolve()
|
||||
}, { defer: true })
|
||||
|
||||
.add('Big stateless component', async p => {
|
||||
await render('/stateless-big')
|
||||
p.resolve()
|
||||
}, { defer: true })
|
||||
|
||||
.add('Stateful component with a loooot of css', async p => {
|
||||
await render('/css')
|
||||
p.resolve()
|
||||
}, { defer: true })
|
||||
|
||||
module.exports = suite
|
||||
|
||||
function render (url, ctx) {
|
||||
return _render(url, ctx, { dir, staticMarkup: true })
|
||||
}
|
4
bin/next
4
bin/next
|
@ -1,12 +1,12 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { resolve } from 'path'
|
||||
import parseArgs from 'minimist'
|
||||
import { spawn } from 'cross-spawn';
|
||||
import { spawn } from 'cross-spawn'
|
||||
|
||||
const defaultCommand = 'dev'
|
||||
const commands = new Set([
|
||||
defaultCommand,
|
||||
'init',
|
||||
'build',
|
||||
'start'
|
||||
])
|
||||
|
|
|
@ -6,7 +6,7 @@ import build from '../server/build'
|
|||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help',
|
||||
h: 'help'
|
||||
},
|
||||
boolean: ['h']
|
||||
})
|
||||
|
|
26
bin/next-dev
26
bin/next-dev
|
@ -1,10 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { resolve } from 'path'
|
||||
import { exec } from 'child_process'
|
||||
import { resolve, join } from 'path'
|
||||
import parseArgs from 'minimist'
|
||||
import Server from '../server'
|
||||
import HotReloader from '../server/hot-reloader'
|
||||
import webpack from '../server/build/webpack'
|
||||
import { exists } from 'mz/fs'
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
|
@ -17,6 +18,12 @@ const argv = parseArgs(process.argv.slice(2), {
|
|||
}
|
||||
})
|
||||
|
||||
const open = url => {
|
||||
const openers = { darwin: 'open', win32: 'start' }
|
||||
const cmdName = openers[process.platform] || 'xdg-open'
|
||||
exec(`${cmdName} ${url}`)
|
||||
}
|
||||
|
||||
const dir = resolve(argv._[0] || '.')
|
||||
|
||||
webpack(dir, { hotReload: true })
|
||||
|
@ -24,7 +31,20 @@ webpack(dir, { hotReload: true })
|
|||
const hotReloader = new HotReloader(compiler)
|
||||
const srv = new Server({ dir, dev: true, hotReloader })
|
||||
await srv.start(argv.port)
|
||||
console.log('> Ready on http://localhost:%d', argv.port);
|
||||
console.log('> Ready on http://localhost:%d', argv.port)
|
||||
|
||||
// Check if pages dir exists and warn if not
|
||||
if (!(await exists(join(dir, 'pages')))) {
|
||||
if (await exists(join(dir, '..', 'pages'))) {
|
||||
console.warn('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?')
|
||||
} else {
|
||||
console.warn('> Couldn\'t find a `pages` directory. Please create one under the project root')
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^(false|0)$/i.test(process.env.NEXT_OPEN_BROWSER)) {
|
||||
open(`http://localhost:${argv.port}`)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
|
|
56
bin/next-init
Executable file
56
bin/next-init
Executable file
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env node
|
||||
import { resolve, join, basename } from 'path'
|
||||
import parseArgs from 'minimist'
|
||||
import { exists, writeFile, mkdir } from 'mz/fs'
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
alias: {
|
||||
h: 'help'
|
||||
},
|
||||
boolean: ['h']
|
||||
})
|
||||
|
||||
const dir = resolve(argv._[0] || '.')
|
||||
|
||||
exists(join(dir, 'package.json'))
|
||||
.then(async present => {
|
||||
if (basename(dir) === 'pages') {
|
||||
console.warn('Your root directory is named "pages". This looks suspicious. You probably want to go one directory up.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!present) {
|
||||
await writeFile(join(dir, 'package.json'), basePackage)
|
||||
}
|
||||
|
||||
if (!await exists(join(dir, 'static'))) {
|
||||
await mkdir(join(dir, 'static'))
|
||||
}
|
||||
|
||||
if (!await exists(join(dir, 'pages'))) {
|
||||
await mkdir(join(dir, 'pages'))
|
||||
await writeFile(join(dir, 'pages', 'index.js'), basePage)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
exit(1)
|
||||
})
|
||||
|
||||
const basePackage = `{
|
||||
"name": "my-app",
|
||||
"description": "my app",
|
||||
"dependencies": {
|
||||
"next": "latest"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
}
|
||||
}`
|
||||
|
||||
const basePage =`
|
||||
import React from 'react'
|
||||
export default () => <p>Hello, world</p>
|
||||
`
|
|
@ -20,7 +20,7 @@ const dir = resolve(argv._[0] || '.')
|
|||
const srv = new Server({ dir })
|
||||
srv.start(argv.port)
|
||||
.then(() => {
|
||||
console.log('> Ready on http://localhost:%d', argv.port);
|
||||
console.log('> Ready on http://localhost:%d', argv.port)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class HeadManager {
|
|||
let title
|
||||
if (component) {
|
||||
const { children } = component.props
|
||||
title = 'string' === typeof children ? children : children.join('')
|
||||
title = typeof children === 'string' ? children : children.join('')
|
||||
} else {
|
||||
title = DEFAULT_TITLE
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ function reactElementToDOM ({ type, props }) {
|
|||
const el = document.createElement(type)
|
||||
for (const p in props) {
|
||||
if (!props.hasOwnProperty(p)) continue
|
||||
if ('children' === p || 'dangerouslySetInnerHTML' === p) continue
|
||||
if (p === 'children' || p === 'dangerouslySetInnerHTML') continue
|
||||
|
||||
const attr = HTMLDOMPropertyConfig.DOMAttributeNames[p] || p.toLowerCase()
|
||||
el.setAttribute(attr, props[p])
|
||||
|
@ -63,7 +63,7 @@ function reactElementToDOM ({ type, props }) {
|
|||
if (dangerouslySetInnerHTML) {
|
||||
el.innerHTML = dangerouslySetInnerHTML.__html || ''
|
||||
} else if (children) {
|
||||
el.textContent = 'string' === typeof children ? children : children.join('')
|
||||
el.textContent = typeof children === 'string' ? children : children.join('')
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ const {
|
|||
const App = app ? evalScript(app).default : DefaultApp
|
||||
const Component = evalScript(component).default
|
||||
|
||||
export const router = new Router(location.href, { Component })
|
||||
export const router = new Router(window.location.href, { Component })
|
||||
|
||||
const headManager = new HeadManager()
|
||||
const container = document.getElementById('__next')
|
||||
|
|
21
examples/basic-css/pages/index.js
Normal file
21
examples/basic-css/pages/index.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from 'react'
|
||||
import { css, StyleSheet } from 'next/css'
|
||||
|
||||
export default () => (
|
||||
<div className={css(styles.main)}>
|
||||
<p>Hello World</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
font: '15px Helvetica, Arial, sans-serif',
|
||||
background: '#eee',
|
||||
padding: '100px',
|
||||
textAlign: 'center',
|
||||
transition: '100ms ease-in background',
|
||||
':hover': {
|
||||
background: '#ccc'
|
||||
}
|
||||
}
|
||||
})
|
14
examples/head-elements/pages/index.js
Normal file
14
examples/head-elements/pages/index.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Head>
|
||||
<title>This page has a title 🤔</title>
|
||||
<meta charSet='utf-8' />
|
||||
<meta name='viewport' content='initial-scale=1.0, width=device-width' />
|
||||
</Head>
|
||||
|
||||
<h1>This page has a title 🤔</h1>
|
||||
</div>
|
||||
)
|
4
examples/hello-world/pages/about.js
Normal file
4
examples/hello-world/pages/about.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
import React from 'react'
|
||||
export default () => (
|
||||
<div>About us</div>
|
||||
)
|
5
examples/hello-world/pages/index.js
Normal file
5
examples/hello-world/pages/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
export default () => (
|
||||
<div>Hello World. <Link href='/about'>About</Link></div>
|
||||
)
|
13
examples/nested-components/components/paragraph.js
Normal file
13
examples/nested-components/components/paragraph.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import React from 'react'
|
||||
import { css, StyleSheet } from 'next/css'
|
||||
|
||||
export default ({ children }) => (
|
||||
<p className={css(styles.main)}>{children}</p>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
font: '13px Helvetica, Arial',
|
||||
margin: '10px 0'
|
||||
}
|
||||
})
|
23
examples/nested-components/components/post.js
Normal file
23
examples/nested-components/components/post.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from 'react'
|
||||
import { css, StyleSheet } from 'next/css'
|
||||
|
||||
export default ({ title, children }) => (
|
||||
<div className={css(styles.main)}>
|
||||
<h1 className={css(styles.title)}>{ title }</h1>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
font: '15px Helvetica, Arial',
|
||||
border: '1px solid #eee',
|
||||
padding: '0 10px'
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
margin: '10px 0'
|
||||
}
|
||||
})
|
48
examples/nested-components/pages/index.js
Normal file
48
examples/nested-components/pages/index.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import React from 'react'
|
||||
import P from '../components/paragraph'
|
||||
import Post from '../components/post'
|
||||
import { css, StyleSheet } from 'next/css'
|
||||
|
||||
export default () => (
|
||||
<div className={css(styles.main)}>
|
||||
<Post title='My first blog post'>
|
||||
<P>Hello there</P>
|
||||
<P>This is an example of a componentized blog post</P>
|
||||
</Post>
|
||||
|
||||
<Hr />
|
||||
|
||||
<Post title='My second blog post'>
|
||||
<P>Hello there</P>
|
||||
<P>This is another example.</P>
|
||||
<P>Wa-hoo!</P>
|
||||
</Post>
|
||||
|
||||
<Hr />
|
||||
|
||||
<Post title='The final blog post'>
|
||||
<P>C'est fin</P>
|
||||
</Post>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Hr = () => <hr className={css(styles.hr)} />
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
main: {
|
||||
margin: 'auto',
|
||||
maxWidth: '420px',
|
||||
padding: '10px'
|
||||
},
|
||||
|
||||
hr: {
|
||||
width: '100px',
|
||||
borderWidth: 0,
|
||||
margin: '20px auto',
|
||||
textAlign: 'center',
|
||||
':before': {
|
||||
content: '"***"',
|
||||
color: '#ccc'
|
||||
}
|
||||
}
|
||||
})
|
32
gulpfile.js
32
gulpfile.js
|
@ -3,6 +3,7 @@ const babel = require('gulp-babel')
|
|||
const cache = require('gulp-cached')
|
||||
const notify_ = require('gulp-notify')
|
||||
const ava = require('gulp-ava')
|
||||
const benchmark = require('gulp-benchmark')
|
||||
const sequence = require('run-sequence')
|
||||
const webpack = require('webpack-stream')
|
||||
const del = require('del')
|
||||
|
@ -22,7 +23,8 @@ gulp.task('compile', [
|
|||
'compile-lib',
|
||||
'compile-server',
|
||||
'compile-client',
|
||||
'compile-test'
|
||||
'compile-test',
|
||||
'compile-bench'
|
||||
])
|
||||
|
||||
gulp.task('compile-bin', () => {
|
||||
|
@ -68,7 +70,7 @@ gulp.task('compile-test', () => {
|
|||
gulp.task('copy', [
|
||||
'copy-pages',
|
||||
'copy-test-fixtures'
|
||||
]);
|
||||
])
|
||||
|
||||
gulp.task('copy-pages', () => {
|
||||
return gulp.src('pages/**/*.js')
|
||||
|
@ -78,10 +80,23 @@ gulp.task('copy-pages', () => {
|
|||
gulp.task('copy-test-fixtures', () => {
|
||||
return gulp.src('test/fixtures/**/*')
|
||||
.pipe(gulp.dest('dist/test/fixtures'))
|
||||
});
|
||||
})
|
||||
|
||||
gulp.task('compile-bench', () => {
|
||||
return gulp.src('bench/*.js')
|
||||
.pipe(cache('bench'))
|
||||
.pipe(babel(babelOptions))
|
||||
.pipe(gulp.dest('dist/bench'))
|
||||
.pipe(notify('Compiled bench files'))
|
||||
})
|
||||
|
||||
gulp.task('copy-bench-fixtures', () => {
|
||||
return gulp.src('bench/fixtures/**/*')
|
||||
.pipe(gulp.dest('dist/bench/fixtures'))
|
||||
})
|
||||
|
||||
gulp.task('build', [
|
||||
'build-dev-client',
|
||||
'build-dev-client'
|
||||
])
|
||||
|
||||
gulp.task('build-release', [
|
||||
|
@ -107,7 +122,7 @@ gulp.task('build-dev-client', ['compile-lib', 'compile-client'], () => {
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
}))
|
||||
.pipe(gulp.dest('dist/client'))
|
||||
.pipe(notify('Built dev client'))
|
||||
|
@ -151,6 +166,13 @@ gulp.task('test', ['compile', 'copy-test-fixtures'], () => {
|
|||
.pipe(ava())
|
||||
})
|
||||
|
||||
gulp.task('bench', ['compile', 'copy-bench-fixtures'], () => {
|
||||
return gulp.src('dist/bench/*.js', {read: false})
|
||||
.pipe(benchmark({
|
||||
reporters: benchmark.reporters.etalon('RegExp#test')
|
||||
}))
|
||||
})
|
||||
|
||||
gulp.task('watch', [
|
||||
'watch-bin',
|
||||
'watch-lib',
|
||||
|
|
|
@ -53,13 +53,13 @@ export default class App extends Component {
|
|||
render () {
|
||||
const { Component, props } = this.state
|
||||
|
||||
if ('undefined' === typeof window) {
|
||||
if (typeof window === 'undefined') {
|
||||
// workaround for https://github.com/gaearon/react-hot-loader/issues/283
|
||||
return <Component { ...props }/>
|
||||
return <Component {...props} />
|
||||
}
|
||||
|
||||
return <AppContainer>
|
||||
<Component { ...props }/>
|
||||
<Component {...props} />
|
||||
</AppContainer>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,11 +14,11 @@ export default class Document extends Component {
|
|||
|
||||
render () {
|
||||
return <html>
|
||||
<Head/>
|
||||
<Head />
|
||||
<body>
|
||||
<Main/>
|
||||
<DevTools/>
|
||||
<NextScript/>
|
||||
<Main />
|
||||
<DevTools />
|
||||
<NextScript />
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
@ -30,14 +30,14 @@ export function Head (props, context) {
|
|||
.map((h, i) => React.cloneElement(h, { key: '_next' + i }))
|
||||
return <head>
|
||||
{h}
|
||||
<style data-aphrodite="" dangerouslySetInnerHTML={{ __html: css.content }} />
|
||||
<style data-aphrodite='' dangerouslySetInnerHTML={{ __html: css.content }} />
|
||||
</head>
|
||||
}
|
||||
|
||||
Head.contextTypes = { _documentProps: PropTypes.any }
|
||||
|
||||
export function Main (props, context) {
|
||||
const { html, data, staticMarkup } = context._documentProps;
|
||||
const { html, data, staticMarkup } = context._documentProps
|
||||
return <div>
|
||||
<div id='__next' dangerouslySetInnerHTML={{ __html: html }} />
|
||||
{staticMarkup ? null : <script dangerouslySetInnerHTML={{ __html: '__NEXT_DATA__ = ' + htmlescape(data) }} />}
|
||||
|
@ -48,16 +48,16 @@ Main.contextTypes = { _documentProps: PropTypes.any }
|
|||
|
||||
export function DevTools (props, context) {
|
||||
const { hotReload } = context._documentProps
|
||||
return hotReload ? <div id='__next-hot-code-reloading-indicator'/> : null
|
||||
return hotReload ? <div id='__next-hot-code-reloading-indicator' /> : null
|
||||
}
|
||||
|
||||
DevTools.contextTypes = { _documentProps: PropTypes.any }
|
||||
|
||||
export function NextScript (props, context) {
|
||||
const { dev, staticMarkup } = context._documentProps;
|
||||
const { dev, staticMarkup } = context._documentProps
|
||||
if (staticMarkup) return null
|
||||
const src = !dev ? '/_next/next.bundle.js' : '/_next/next-dev.bundle.js'
|
||||
return <script type='text/javascript' src={src}/>
|
||||
return <script type='text/javascript' src={src} />
|
||||
}
|
||||
|
||||
NextScript.contextTypes = { _documentProps: PropTypes.any }
|
||||
|
|
|
@ -62,7 +62,7 @@ function unique () {
|
|||
const metatype = METATYPES[i]
|
||||
if (!h.props.hasOwnProperty(metatype)) continue
|
||||
|
||||
if ('charSet' === metatype) {
|
||||
if (metatype === 'charSet') {
|
||||
if (metaTypes.has(metatype)) return false
|
||||
metaTypes.add(metatype)
|
||||
} else {
|
||||
|
|
10
lib/link.js
10
lib/link.js
|
@ -11,8 +11,8 @@ export default class Link extends Component {
|
|||
}
|
||||
|
||||
linkClicked (e) {
|
||||
if ('A' === e.target.nodeName &&
|
||||
(e.metaKey || e.ctrlKey || e.shiftKey || 2 === e.nativeEvent.which)) {
|
||||
if (e.target.nodeName === 'A' &&
|
||||
(e.metaKey || e.ctrlKey || e.shiftKey || e.nativeEvent.which === 2)) {
|
||||
// ignore click for new tab / new window behavior
|
||||
return
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export default class Link extends Component {
|
|||
this.context.router.push(null, href)
|
||||
.then((success) => {
|
||||
if (!success) return
|
||||
if (false !== scroll) window.scrollTo(0, 0)
|
||||
if (scroll !== false) window.scrollTo(0, 0)
|
||||
})
|
||||
.catch((err) => {
|
||||
if (this.props.onError) this.props.onError(err)
|
||||
|
@ -43,7 +43,7 @@ export default class Link extends Component {
|
|||
onClick: this.linkClicked
|
||||
}
|
||||
|
||||
const isAnchor = child && 'a' === child.type
|
||||
const isAnchor = child && child.type === 'a'
|
||||
|
||||
// if child does not specify a href, specify it
|
||||
// so that repetition is not needed by the user
|
||||
|
@ -63,7 +63,7 @@ export default class Link extends Component {
|
|||
}
|
||||
|
||||
function isLocal (href) {
|
||||
const origin = location.origin
|
||||
const origin = window.location.origin
|
||||
return !/^https?:\/\//.test(href) ||
|
||||
origin === href.substr(0, origin.length)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import shallowEquals from './shallow-equals'
|
|||
|
||||
export default class Router {
|
||||
constructor (url, initialData) {
|
||||
const parsed = parse(url, true);
|
||||
const parsed = parse(url, true)
|
||||
|
||||
// represents the current component key
|
||||
this.route = toRoute(parsed.pathname)
|
||||
|
@ -18,7 +18,7 @@ export default class Router {
|
|||
this.componentLoadCancel = null
|
||||
this.onPopState = this.onPopState.bind(this)
|
||||
|
||||
if ('undefined' !== typeof window) {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('popstate', this.onPopState)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export default class Router {
|
|||
onPopState (e) {
|
||||
this.abortComponentLoad()
|
||||
|
||||
const route = (e.state || {}).route || toRoute(location.pathname)
|
||||
const route = (e.state || {}).route || toRoute(window.location.pathname)
|
||||
|
||||
Promise.resolve()
|
||||
.then(async () => {
|
||||
|
@ -45,7 +45,7 @@ export default class Router {
|
|||
// the only way we can appropriately handle
|
||||
// this failure is deferring to the browser
|
||||
// since the URL has already changed
|
||||
location.reload()
|
||||
window.location.reload()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ export default class Router {
|
|||
}
|
||||
|
||||
back () {
|
||||
history.back()
|
||||
window.history.back()
|
||||
}
|
||||
|
||||
push (route, url) {
|
||||
|
@ -96,7 +96,7 @@ export default class Router {
|
|||
throw err
|
||||
}
|
||||
|
||||
history[method]({ route }, null, url)
|
||||
window.history[method]({ route }, null, url)
|
||||
this.route = route
|
||||
this.set(url, { ...data, props })
|
||||
return true
|
||||
|
@ -182,7 +182,7 @@ export default class Router {
|
|||
}
|
||||
|
||||
function getURL () {
|
||||
return location.pathname + (location.search || '') + (location.hash || '')
|
||||
return window.location.pathname + (window.location.search || '') + (window.location.hash || '')
|
||||
}
|
||||
|
||||
function toRoute (path) {
|
||||
|
@ -190,7 +190,7 @@ function toRoute (path) {
|
|||
}
|
||||
|
||||
function toJSONUrl (route) {
|
||||
return ('/' === route ? '/index' : route) + '.json'
|
||||
return (route === '/' ? '/index' : route) + '.json'
|
||||
}
|
||||
|
||||
function loadComponent (url, fn) {
|
||||
|
@ -212,7 +212,7 @@ function loadComponent (url, fn) {
|
|||
}
|
||||
|
||||
function loadJSON (url, fn) {
|
||||
const xhr = new XMLHttpRequest()
|
||||
const xhr = new window.XMLHttpRequest()
|
||||
xhr.onload = () => {
|
||||
let data
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
export default function shallowEquals (a, b) {
|
||||
for (const i in a) {
|
||||
if (b[i] !== a[i]) return false;
|
||||
if (b[i] !== a[i]) return false
|
||||
}
|
||||
|
||||
for (const i in b) {
|
||||
if (b[i] !== a[i]) return false;
|
||||
if (b[i] !== a[i]) return false
|
||||
}
|
||||
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ export default function withSideEffect (reduceComponentsToState, handleStateChan
|
|||
static contextTypes = WrappedComponent.contextTypes
|
||||
|
||||
// Expose canUseDOM so tests can monkeypatch it
|
||||
static canUseDOM = 'undefined' !== typeof window
|
||||
static canUseDOM = typeof window !== 'undefined'
|
||||
|
||||
static peek () {
|
||||
return state
|
||||
|
|
11
package.json
11
package.json
|
@ -12,7 +12,9 @@
|
|||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
"test": "ava"
|
||||
"test": "standard && gulp test",
|
||||
"lint": "standard",
|
||||
"precommit": "npm run lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"aphrodite": "0.5.0",
|
||||
|
@ -29,6 +31,7 @@
|
|||
"babel-runtime": "6.11.6",
|
||||
"cross-spawn": "4.0.2",
|
||||
"glob-promise": "1.0.6",
|
||||
"gulp-benchmark": "^1.1.1",
|
||||
"htmlescape": "1.1.1",
|
||||
"loader-utils": "0.2.16",
|
||||
"minimist": "1.2.0",
|
||||
|
@ -47,6 +50,7 @@
|
|||
"write-file-webpack-plugin": "3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-plugin-transform-remove-strict-mode": "0.0.2",
|
||||
"del": "2.2.2",
|
||||
"gulp": "3.9.1",
|
||||
|
@ -54,6 +58,8 @@
|
|||
"gulp-babel": "6.1.2",
|
||||
"gulp-cached": "1.1.0",
|
||||
"gulp-notify": "2.2.0",
|
||||
"husky": "^0.11.9",
|
||||
"standard": "^8.4.0",
|
||||
"webpack-stream": "3.2.0"
|
||||
},
|
||||
"ava": {
|
||||
|
@ -69,5 +75,8 @@
|
|||
"transform-runtime"
|
||||
]
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ export default class Error extends React.Component {
|
|||
|
||||
render () {
|
||||
const { statusCode } = this.props
|
||||
const title = 404 === statusCode ? 'This page could not be found' : 'Internal Server Error'
|
||||
const title = statusCode === 404 ? 'This page could not be found' : 'Internal Server Error'
|
||||
|
||||
return <div className={css(styles.error, styles['error_' + statusCode])}>
|
||||
<div className={css(styles.text)}>
|
||||
|
|
|
@ -3,7 +3,7 @@ import webpack from 'webpack'
|
|||
import glob from 'glob-promise'
|
||||
import WriteFilePlugin from 'write-file-webpack-plugin'
|
||||
|
||||
export default async function createCompiler(dir, { hotReload = false } = {}) {
|
||||
export default async function createCompiler (dir, { hotReload = false } = {}) {
|
||||
dir = resolve(dir)
|
||||
|
||||
const pages = await glob('pages/**/*.js', { cwd: dir })
|
||||
|
@ -31,7 +31,7 @@ export default async function createCompiler(dir, { hotReload = false } = {}) {
|
|||
]
|
||||
|
||||
const babelRuntimePath = require.resolve('babel-runtime/package')
|
||||
.replace(/[\\\/]package\.json$/, '');
|
||||
.replace(/[\\\/]package\.json$/, '')
|
||||
|
||||
const loaders = [{
|
||||
test: /\.js$/,
|
||||
|
|
|
@ -15,8 +15,8 @@ export default class Server {
|
|||
this.run(req, res)
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
res.status(500);
|
||||
res.end('error');
|
||||
res.status(500)
|
||||
res.end('error')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ export default class Server {
|
|||
try {
|
||||
html = await render(req.url, { req, res }, { dir, dev })
|
||||
} catch (err) {
|
||||
if ('ENOENT' === err.code) {
|
||||
if (err.code === 'ENOENT') {
|
||||
res.statusCode = 404
|
||||
} else {
|
||||
console.error(err)
|
||||
|
@ -85,7 +85,7 @@ export default class Server {
|
|||
try {
|
||||
json = await renderJSON(req.url, { dir })
|
||||
} catch (err) {
|
||||
if ('ENOENT' === err.code) {
|
||||
if (err.code === 'ENOENT') {
|
||||
res.statusCode = 404
|
||||
} else {
|
||||
console.error(err)
|
||||
|
@ -112,7 +112,7 @@ export default class Server {
|
|||
return new Promise((resolve, reject) => {
|
||||
send(req, path)
|
||||
.on('error', (err) => {
|
||||
if ('ENOENT' === err.code) {
|
||||
if (err.code === 'ENOENT') {
|
||||
this.render404(req, res).then(resolve, reject)
|
||||
} else {
|
||||
reject(err)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { relative, resolve } from 'path'
|
||||
import { resolve } from 'path'
|
||||
import { parse } from 'url'
|
||||
import { createElement } from 'react'
|
||||
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
|
||||
|
|
18
test/fixtures/basic/pages/async-props.js
vendored
Normal file
18
test/fixtures/basic/pages/async-props.js
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react'
|
||||
|
||||
export default class AsyncProps extends React.Component {
|
||||
static async getInitialProps () {
|
||||
return await AsyncProps.fetchData()
|
||||
}
|
||||
|
||||
static fetchData () {
|
||||
const p = new Promise(resolve => {
|
||||
setTimeout(() => resolve({ name: 'Diego Milito' }), 10)
|
||||
})
|
||||
return p
|
||||
}
|
||||
|
||||
render () {
|
||||
return <p>{this.props.name}</p>
|
||||
}
|
||||
}
|
10
test/fixtures/basic/pages/head.js
vendored
Normal file
10
test/fixtures/basic/pages/head.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
|
||||
export default () => <div>
|
||||
<Head>
|
||||
<meta content='my meta' />
|
||||
</Head>
|
||||
<h1>I can haz meta tags</h1>
|
||||
</div>
|
20
test/fixtures/basic/pages/stateful.js
vendored
Normal file
20
test/fixtures/basic/pages/stateful.js
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
import React, { Component } from 'react'
|
||||
|
||||
export default class Statefull extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = { answer: null }
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.setState({ answer: 42 })
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div>
|
||||
<p>The answer is {this.state.answer}</p>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -7,17 +7,33 @@ const dir = resolve(__dirname, 'fixtures', 'basic')
|
|||
|
||||
test.before(() => build(dir))
|
||||
|
||||
test(async (t) => {
|
||||
test(async t => {
|
||||
const html = await render('/stateless')
|
||||
t.true(html.includes('<h1>My component!</h1>'))
|
||||
})
|
||||
|
||||
test(async (t) => {
|
||||
test(async t => {
|
||||
const html = await render('/css')
|
||||
t.true(html.includes('<style data-aphrodite="">.red_im3wl1{color:red !important;}</style>'))
|
||||
t.true(html.includes('<div class="red_im3wl1">This is red</div>'))
|
||||
})
|
||||
|
||||
test(async t => {
|
||||
const html = await render('/stateful')
|
||||
t.true(html.includes('<div><p>The answer is 42</p></div>'))
|
||||
})
|
||||
|
||||
test(async t => {
|
||||
const html = await (render('/head'))
|
||||
t.true(html.includes('<meta content="my meta" class="next-head"/>'))
|
||||
t.true(html.includes('<div><h1>I can haz meta tags</h1></div>'))
|
||||
})
|
||||
|
||||
test(async t => {
|
||||
const html = await render('/async-props')
|
||||
t.true(html.includes('<p>Diego Milito</p>'))
|
||||
})
|
||||
|
||||
function render (url, ctx) {
|
||||
return _render(url, ctx, { dir, staticMarkup: true })
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue