提交 1be8447a 编写于 作者: N nkzawa

Merge branch 'master' into add/hot-reload

......@@ -2,3 +2,4 @@
node_modules
dist
.next
yarn.lock
......@@ -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)
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)
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
}
import React from 'react'
export default () => <h1>My component!</h1>
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 })
}
#!/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']
})
......
#!/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)
......
#!/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')
......
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'
}
}
})
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>
)
import React from 'react'
export default () => (
<div>About us</div>
)
import React from 'react'
import Link from 'next/link'
export default () => (
<div>Hello World. <Link href='/about'>About</Link></div>
)
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'
}
})
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'
}
})
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'
}
}
})
......@@ -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 {
......
......@@ -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
......
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
......
......@@ -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)
......
import { relative, resolve } from 'path'
import { resolve } from 'path'
import { parse } from 'url'
import { createElement } from 'react'
import { renderToString, renderToStaticMarkup } from 'react-dom/server'
......
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>
}
}
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>
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 })
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册