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

Merge branch 'v3-beta'

This commit is contained in:
Arunoda Susiripala 2017-08-09 12:04:42 +05:30
commit daf8d5bf95
340 changed files with 8278 additions and 1031 deletions

39
.github/issue_template.md vendored Normal file
View file

@ -0,0 +1,39 @@
<!--- Provide a general summary of the issue in the Title above -->
<!--
Thank you very much for contributing to Next.js by creating an issue! ❤️
To avoid duplicate issues we ask you to check off the following list
-->
<!-- Checked checkbox should look like this: [x] -->
- [ ] I have searched the [issues](https://github.com/zeit/next.js/issues) of this repository and believe that this is not a duplicate.
## Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
## Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
## Steps to Reproduce (for bugs)
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.
2.
3.
4.
## Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
| Tech | Version |
|---------|---------|
| next | |
| node | |
| OS | |
| browser | |
| etc | |

5
.gitignore vendored
View file

@ -4,12 +4,15 @@ dist
# dependencies
node_modules
package-lock.json
# logs
npm-debug.log
*.log
# coverage
.nyc_output
coverage
# test output
test/**/out
.DS_Store

View file

@ -8,7 +8,6 @@ addons:
- google-chrome-stable
language: node_js
node_js:
- "4"
- "6"
cache:
directories:

View file

@ -1,6 +1,5 @@
environment:
matrix:
- nodejs_version: "4"
- nodejs_version: "6"
# Install scripts. (runs after repo cloning)

View file

@ -12,7 +12,7 @@ if (pkg.peerDependencies) {
// When 'npm link' is used it checks the clone location. Not the project.
require.resolve(dependency)
} catch (err) {
console.warn(`${dependency} not found. Please install ${dependency} using 'npm install ${dependency}'`)
console.warn(`The module '${dependency}' was not found. Next.js requires that you include it in 'dependencies' of your 'package.json'. To add it, run 'npm install --save ${dependency}'`)
}
})
}
@ -22,6 +22,7 @@ const commands = new Set([
'init',
'build',
'start',
'export',
defaultCommand
])

View file

@ -12,12 +12,12 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
H: 'hostname',
p: 'port'
},
boolean: ['h'],
default: {
p: 3000
}
string: ['H'],
default: { p: 3000 }
})
if (argv.help) {
@ -35,6 +35,7 @@ if (argv.help) {
Options
--port, -p A port number on which to start the application
--hostname, -H Hostname on which to start the application
--help, -h Displays this message
`)
process.exit(0)
@ -56,10 +57,10 @@ if (!existsSync(join(dir, 'pages'))) {
}
const srv = new Server({ dir, dev: true })
srv.start(argv.port)
srv.start(argv.port, argv.hostname)
.then(async () => {
if (!process.env.NOW) {
console.log(`> Ready on http://localhost:${argv.port}`)
console.log(`> Ready on http://${argv.hostname ? argv.hostname : 'localhost'}:${argv.port}`)
}
})
.catch((err) => {

67
bin/next-export Normal file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env node
import { resolve, join } from 'path'
import { existsSync } from 'fs'
import parseArgs from 'minimist'
import exportApp from '../server/export'
import { printAndExit } from '../lib/utils'
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
const argv = parseArgs(process.argv.slice(2), {
alias: {
h: 'help',
s: 'silent',
o: 'outdir'
},
boolean: ['h'],
default: {
s: false,
o: null
}
})
if (argv.help) {
console.log(`
Description
Exports the application for production deployment
Usage
$ next export [options] <dir>
<dir> represents where the compiled dist folder should go.
If no directory is provided, the dist folder will be created in the current directory.
You can set a custom folder in config https://github.com/zeit/next.js#custom-configuration, otherwise it will be created inside '.next'
Options
-h - list this help
-o - set the output dir (defaults to 'out')
-s - do not print any messages to console
`)
process.exit(0)
}
const dir = resolve(argv._[0] || '.')
// Check if pages dir exists and warn if not
if (!existsSync(dir)) {
printAndExit(`> No such directory exists as the project root: ${dir}`)
}
if (!existsSync(join(dir, 'pages'))) {
if (existsSync(join(dir, '..', 'pages'))) {
printAndExit('> No `pages` directory found. Did you mean to run `next` in the parent (`../`) directory?')
}
printAndExit('> Couldn\'t find a `pages` directory. Please create one under the project root')
}
const options = {
silent: argv.silent,
outdir: argv.outdir ? resolve(argv.outdir) : resolve(dir, 'out')
}
exportApp(dir, options)
.catch((err) => {
console.error(err)
process.exit(1)
})

View file

@ -3,8 +3,6 @@
import { resolve } from 'path'
import parseArgs from 'minimist'
import Server from '../server'
import { existsSync } from 'fs'
import getConfig from '../server/config'
process.env.NODE_ENV = process.env.NODE_ENV || 'production'
@ -47,15 +45,8 @@ if (argv.help) {
}
const dir = resolve(argv._[0] || '.')
const dist = getConfig(dir).distDir
const srv = new Server({ dir })
if (!existsSync(resolve(dir, dist, 'BUILD_ID'))) {
console.error(`> Could not find a valid build in the '${dist}' directory! Try building your app with 'next build' before starting the server.`)
process.exit(1)
}
srv.start(argv.port, argv.hostname)
.then(() => {
if (!process.env.NOW) {

View file

@ -1,8 +1,8 @@
import { createElement } from 'react'
import ReactDOM from 'react-dom'
import mitt from 'mitt'
import HeadManager from './head-manager'
import { createRouter } from '../lib/router'
import EventEmitter from '../lib/EventEmitter'
import App from '../lib/app'
import { loadGetInitialProps, getURL } from '../lib/utils'
import ErrorDebugComponent from '../lib/error-debug'
@ -24,6 +24,7 @@ const {
pathname,
query,
buildId,
chunks,
assetPrefix
},
location
@ -37,7 +38,13 @@ window.__NEXT_LOADED_PAGES__.forEach(({ route, fn }) => {
})
delete window.__NEXT_LOADED_PAGES__
window.__NEXT_LOADED_CHUNKS__.forEach(({ chunkName, fn }) => {
pageLoader.registerChunk(chunkName, fn)
})
delete window.__NEXT_LOADED_CHUNKS__
window.__NEXT_REGISTER_PAGE = pageLoader.registerPage.bind(pageLoader)
window.__NEXT_REGISTER_CHUNK = pageLoader.registerChunk.bind(pageLoader)
const headManager = new HeadManager()
const appContainer = document.getElementById('__next')
@ -49,6 +56,11 @@ export let ErrorComponent
let Component
export default async () => {
// Wait for all the dynamic chunks to get loaded
for (const chunkName of chunks) {
await pageLoader.waitForChunk(chunkName)
}
ErrorComponent = await pageLoader.loadPage('/_error')
try {
@ -65,7 +77,7 @@ export default async () => {
err
})
const emitter = mitt()
const emitter = new EventEmitter()
router.subscribe(({ Component, props, hash, err }) => {
render({ Component, props, err, hash, emitter })

View file

@ -42,8 +42,10 @@ ReactReconciler.mountComponent = function (...args) {
try {
return originalMountComponent(...args)
} catch (err) {
if (!err.abort) {
next.renderError(err)
err.abort = true
}
throw err
}
}

View file

@ -11,11 +11,20 @@ export default () => {
async function ping () {
try {
const url = `/_next/on-demand-entries-ping?page=${Router.pathname}`
const res = await fetch(url)
const res = await fetch(url, {
credentials: 'same-origin'
})
const payload = await res.json()
if (payload.invalid) {
// Payload can be invalid even if the page is not exists.
// So, we need to make sure it's exists before reloading.
const pageRes = await fetch(location.href, {
credentials: 'same-origin'
})
if (pageRes.status === 200) {
location.reload()
}
}
} catch (err) {
console.error(`Error with on-demand-entries-ping: ${err.message}`)
}

View file

@ -32,17 +32,22 @@ export default () => {
const { err, Component } = Router.components[route] || {}
if (err) {
// reload to recover from runtime errors
Router.reload(route)
}
if (Router.route !== route) {
// If this is a not a change for a currently viewing page.
// We don't need to worry about it.
return
}
if (!Component) {
// This only happens when we create a new page without a default export.
// If you removed a default export from a exising viewing page, this has no effect.
console.log(`Hard reloading due to no default component in page: ${route}`)
window.location.reload()
return
}
if (err) {
// reload to recover from runtime errors
Router.reload(route)
}
}
}

1
dynamic.js Normal file
View file

@ -0,0 +1 @@
module.exports = require('./dist/lib/dynamic')

View file

@ -11,6 +11,5 @@
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -1,4 +1,6 @@
{
"name": "custom-server-express",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",

View file

@ -1,4 +1,6 @@
{
"name": "custom-server-hapi",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",

View file

@ -1,5 +1,6 @@
{
"name": "custom-server-koa",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",

View file

@ -1,4 +1,6 @@
{
"name": "custom-server",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",

View file

@ -12,6 +12,5 @@
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -7,10 +7,9 @@
"start": "next start"
},
"dependencies": {
"next": "*",
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -7,10 +7,9 @@
"start": "next start"
},
"dependencies": {
"next": "*",
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -1,5 +1,5 @@
{
"name": "hello-world",
"name": "layout-component",
"version": "1.0.0",
"scripts": {
"dev": "next",
@ -7,10 +7,9 @@
"start": "next start"
},
"dependencies": {
"next": "*",
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -7,10 +7,9 @@
"start": "next start"
},
"dependencies": {
"next": "*",
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -0,0 +1,29 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/page-transitions)
# Example app with custom page transitions
## How to use
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/page-transitions
cd page-transitions
```
Install it and run:
```bash
yarn
yarn dev
```
Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download))
```bash
now
```
## The idea behind the example
Being able to animate out old content and animate in new content is a fairly standard thing to do these days. We can hijack the route change and do any animations that we want: sliding, cross fading, scaling, et al.

View file

@ -0,0 +1,19 @@
{
"name": "page-transitions",
"version": "1.0.0",
"scripts": {
"dev": "NODE_ENV=development node server.js",
"build": "next build",
"start": "NODE_ENV=production node server.js"
},
"dependencies": {
"compression": "^1.7.0",
"express": "^4.15.3",
"next": "latest",
"next-routes": "^1.0.40",
"raf": "^3.3.2",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"license": "ISC"
}

View file

@ -0,0 +1,21 @@
import React from 'react'
import Document, { Head, Main, NextScript } from 'next/document'
// ---------------------------------------------
export default class CustomDocument extends Document {
render () {
return (<html lang='en-US'>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta name='robots' content='noindex' />
<link rel='stylesheet' href='/static/style.css' />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>)
}
}

View file

@ -0,0 +1,39 @@
import { PureComponent } from 'react'
import raf from 'raf'
import { Router } from '../routes'
import Main from './main'
// -----------------------------------------------
Router.onRouteChangeStart = () => {
const $container = document.getElementById('container')
const $clone = $container.cloneNode(true)
document.body.classList.add('loading')
$clone.classList.add('clone')
raf(() => {
$container.parentNode.insertBefore($clone, $container.nextSibling)
$clone.classList.add('animate-out')
$container.classList.add('animate-in')
})
$clone.addEventListener('animationend', () => {
document.body.classList.remove('loading')
$container.classList.remove('animate-in')
$clone.parentNode.removeChild($clone)
}, { once: true })
}
// -----------------------------------------------
export default class Index extends PureComponent {
static async getInitialProps ({ query }) {
const pathname = query.slug || '/'
return { pathname }
}
render () {
return <Main {...this.props} />
}
}

View file

@ -0,0 +1,38 @@
import { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Head from 'next/head'
import { Link } from '../routes'
// -----------------------------------------------
export default class Main extends PureComponent {
render () {
return (<div>
<Head>
<title>{this.props.pathname} - Page Transitions</title>
</Head>
<header>
<Link to='/'>
<a className={this.props.pathname === '/' ? 'active' : ''}>Homepage</a>
</Link>
<Link route='main' params={{ slug: 'about' }}>
<a className={this.props.pathname === 'about' ? 'active' : ''}>About</a>
</Link>
<Link route='main' params={{ slug: 'contact' }}>
<a className={this.props.pathname === 'contact' ? 'active' : ''}>Contact</a>
</Link>
</header>
<div id='container' className={`page-${this.props.pathname}`}>
<h1 dangerouslySetInnerHTML={{ __html: this.props.pathname }} />
</div>
</div>)
}
}
Main.propTypes = {
pathname: PropTypes.string.isRequired
}

View file

@ -0,0 +1,6 @@
const nextRoutes = require('next-routes')
const routes = nextRoutes()
routes.add('main', '/:slug/:child?', '')
module.exports = routes

View file

@ -0,0 +1,12 @@
const next = require('next')
const routes = require('./routes')
const express = require('express')
const compression = require('compression')
const port = process.env.PORT || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = routes.getRequestHandler(app)
app.prepare().then(express().use(compression()).use(handle).listen(port))

View file

@ -0,0 +1,99 @@
body, html {
margin: 0;
padding: 0;
}
body {
background: #000;
}
/* ------------------------------------------- */
header {
z-index: 100;
position: fixed;
right: 0;
top: 20px;
left: 0;
text-align: center;
padding: 15px;
}
header a {
color: blue;
}
header a:not(:last-child) {
margin-right: 15px;
}
header a.active {
color: #000;
text-decoration: none;
}
/* ------------------------------------------- */
@keyframes animateIn {
from {
transform: translate3d(100vw, 0, 0);;
}
to {
transform: translate3d(0, 0, 0);
}
}
@keyframes animateOut {
to {
transform: translate3d(-100vw, 0, 0);
}
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
/* ------------------------------------------- */
#container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
box-shadow: inset 0 0 0 20px;
background: #FFF;
}
#container.page-about {
background: tan;
}
#container.page-contact {
background: violet;
}
#container h1 {
font-size: 10vw;
text-transform: uppercase;
letter-spacing: -1px;
}
#container[class*="animate-"] {
position: fixed;
animation: animateIn .75s ease-in-out forwards;
transform-origin: center;
will-change: transform;
}
#container.animate-out {
animation-name: animateOut;
}
.loading #container:not(.clone) h1 {
opacity: 0;
animation: fadeIn 1s .15s ease-in-out forwards;
}

View file

@ -1,4 +1,6 @@
{
"name": "parameterized-routing",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",

View file

@ -1,5 +1,6 @@
{
"name": "progressive-render",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",

View file

@ -1,4 +1,6 @@
{
"name": "root-static-files",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",

View file

@ -1,18 +1,15 @@
{
"name": "shared-modules",
"version": "1.0.0",
"description": "This example features:",
"main": "index.js",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "*",
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -1,4 +1,6 @@
{
"name": "ssr-caching",
"version": "1.0.0",
"scripts": {
"dev": "node server.js",
"build": "next build",

View file

@ -13,6 +13,5 @@
"module-alias": "^2.0.0",
"next": "latest"
},
"author": "",
"license": "MIT"
}

View file

@ -1,7 +1,7 @@
module.exports = {
webpack: function (config, { dev }) {
// For the development version, we'll use React.
// Because, it support react hot loading and so on.
// Because, it supports react hot loading and so on.
if (dev) {
return config
}

View file

@ -12,6 +12,9 @@
"preact": "^7.2.0",
"preact-compat": "^3.14.0"
},
"author": "",
"license": "ISC"
"license": "ISC",
"devDependencies": {
"react": "~15.6.1",
"react-dom": "~15.6.1"
}
}

View file

@ -1,8 +1,6 @@
{
"name": "shared-modules",
"name": "using-router",
"version": "1.0.0",
"description": "This example features:",
"main": "index.js",
"scripts": {
"dev": "next",
"build": "next build",
@ -13,6 +11,5 @@
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -0,0 +1,19 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# testing
/coverage
# production
/build
/dist
/.next
# misc
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View 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/with-algolia-react-instantsearch)
# With Algolia React InstantSearch example
## How to use
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-algolia-react-instantsearch
cd with-algolia-react-instantsearch
```
Set up Algolia:
- create an [algolia](https://www.algolia.com/) account or use this already [configured index](https://community.algolia.com/react-instantsearch/Getting_started.html#before-we-start)
- update the appId, apikey and indexName you want to search on in components/app.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
The goal of this example is to illustrate how you can use [Algolia React InstantSearch](https://community.algolia.com/react-instantsearch/) to perform
your search with a Server-rendered application developed with Next.js. It also illustrates how you
can keep in sync the Url with the search.

View file

@ -0,0 +1,86 @@
import React from 'react'
import PropTypes from 'prop-types'
import {
RefinementList,
SearchBox,
Hits,
Configure,
Highlight,
Pagination
} from 'react-instantsearch/dom'
import { InstantSearch } from './instantsearch'
const HitComponent = ({ hit }) =>
<div className='hit'>
<div>
<div className='hit-picture'>
<img src={`${hit.image}`} />
</div>
</div>
<div className='hit-content'>
<div>
<Highlight attributeName='name' hit={hit} />
<span>
{' '}- ${hit.price}
</span>
<span>
{' '}- {hit.rating} stars
</span>
</div>
<div className='hit-type'>
<Highlight attributeName='type' hit={hit} />
</div>
<div className='hit-description'>
<Highlight attributeName='description' hit={hit} />
</div>
</div>
</div>
HitComponent.propTypes = {
hit: PropTypes.object
}
export default class extends React.Component {
static propTypes = {
searchState: PropTypes.object,
resultsState: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
onSearchStateChange: PropTypes.func
};
render () {
return (
<InstantSearch
appId='appId' // change this
apiKey='apiKey' // change this
indexName='indexName' // change this
resultsState={this.props.resultsState}
onSearchStateChange={this.props.onSearchStateChange}
searchState={this.props.searchState}
>
<Configure hitsPerPage={10} />
<header>
<h1>React InstantSearch + Next.Js</h1>
<SearchBox />
</header>
<content>
<menu>
<RefinementList attributeName='category' />
</menu>
<results>
<Hits hitComponent={HitComponent} />
</results>
</content>
<footer>
<Pagination />
<div>
See{' '}
<a href='https://github.com/algolia/react-instantsearch/tree/master/packages/react-instantsearch/examples/next-app'>
source code
</a>{' '}
on github
</div>
</footer>
</InstantSearch>
)
}
}

View file

@ -0,0 +1,48 @@
import NextHead from 'next/head'
import { string } from 'prop-types'
import React from 'react'
const defaultDescription = ''
const defaultOGURL = ''
const defaultOGImage = ''
export const Head = props =>
<NextHead>
<meta charSet='UTF-8' />
<title>{props.title || ''}</title>
<meta
name='description'
content={props.description || defaultDescription}
/>
<meta name='viewport' content='width=device-width, initial-scale=1' />
<link rel='icon' sizes='192x192' href='/static/touch-icon.png' />
<link rel='apple-touch-icon' href='/static/touch-icon.png' />
<link rel='mask-icon' href='/static/favicon-mask.svg' color='#49B882' />
<link rel='icon' href='/static/favicon.ico' />
<meta property='og:url' content={props.url || defaultOGURL} />
<meta property='og:title' content={props.title || ''} />
<meta
property='og:description'
content={props.description || defaultDescription}
/>
<meta name='twitter:site' content={props.url || defaultOGURL} />
<meta name='twitter:card' content='summary_large_image' />
<meta name='twitter:image' content={props.ogImage || defaultOGImage} />
<meta property='og:image' content={props.ogImage || defaultOGImage} />
<meta property='og:image:width' content='1200' />
<meta property='og:image:height' content='630' />
<link
rel='stylesheet'
href='https://unpkg.com/react-instantsearch-theme-algolia@3.0.0/style.min.css'
/>
<link rel='stylesheet' href='../static/instantsearch.css' />
</NextHead>
Head.propTypes = {
title: string,
description: string,
url: string,
ogImage: string
}
export default Head

View file

@ -0,0 +1,3 @@
export * from './head'
export { default as App } from './app'
export { findResultsState } from './instantsearch'

View file

@ -0,0 +1,5 @@
import { createInstantSearch } from 'react-instantsearch/server'
const { InstantSearch, findResultsState } = createInstantSearch()
export { InstantSearch, findResultsState }

View file

@ -0,0 +1,19 @@
/* eslint-disable import/no-commonjs */
module.exports = {
webpack: config => {
// Fixes npm packages that depend on `fs` module
config.node = {
fs: 'empty'
}
config.module.rules.push({
test: /\.css$/,
loader: ['style-loader', 'css-loader']
})
if (config.resolve.alias) {
delete config.resolve.alias.react
delete config.resolve.alias['react-dom']
}
return config
}
}

View file

@ -0,0 +1,19 @@
{
"name": "create-next-example-app",
"scripts": {
"dev": "next",
"build": "NODE_ENV=development next build",
"start": "next start"
},
"dependencies": {
"css-loader": "^0.28.1",
"next": "^2.4.7",
"prop-types": "^15.5.10",
"qs": "^6.4.0",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-instantsearch": "beta",
"react-instantsearch-theme-algolia": "beta",
"style-loader": "^0.17.0"
}
}

View file

@ -0,0 +1,73 @@
import { Head, App, findResultsState } from '../components'
import React from 'react'
import PropTypes from 'prop-types'
import Router from 'next/router'
import qs from 'qs'
const updateAfter = 700
const searchStateToUrl = searchState =>
searchState ? `${window.location.pathname}?${qs.stringify(searchState)}` : ''
export default class extends React.Component {
static propTypes = {
resultsState: PropTypes.object,
searchState: PropTypes.object
};
constructor (props) {
super(props)
this.onSearchStateChange = this.onSearchStateChange.bind(this)
}
/*
nextjs params.query doesn't handle nested objects
once it does, params.query could be used directly here, but also inside the constructor
to initialize the searchState.
*/
static async getInitialProps (params) {
const searchState = qs.parse(
params.asPath.substring(params.asPath.indexOf('?') + 1)
)
const resultsState = await findResultsState(App, { searchState })
return { resultsState, searchState }
}
onSearchStateChange = searchState => {
clearTimeout(this.debouncedSetState)
this.debouncedSetState = setTimeout(() => {
const href = searchStateToUrl(searchState)
Router.push(href, href, {
shallow: true
})
}, updateAfter)
this.setState({ searchState })
};
componentDidMount () {
this.setState({ searchState: qs.parse(window.location.search.slice(1)) })
}
componentWillReceiveProps () {
this.setState({ searchState: qs.parse(window.location.search.slice(1)) })
}
render () {
return (
<div>
<Head title='Home' />
<div>
<App
resultsState={this.props.resultsState}
onSearchStateChange={this.onSearchStateChange}
searchState={
this.state && this.state.searchState
? this.state.searchState
: this.props.searchState
}
/>
</div>
</div>
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,48 @@
.ais-InstantSearch__root {
align-items: center;
}
header {
display: flex;
flex-direction: column;
align-items: center;
}
content {
display: flex;
}
menu {
flex: 2;
}
footer {
text-align: center;
}
results {
flex: 10;
}
.hit {
display: flex;
align-items: center;
}
.hit-actions {
display: flex;
}
.hit-content {
padding: 0px 10px;
}
.hit-picture img {
width: 80px;
height: 80px;
}
.hit-type {
color: #888888;
font-size: 13px;
}

View file

@ -1,5 +1,5 @@
{
"name": "amp",
"name": "with-amp",
"version": "1.0.0",
"scripts": {
"dev": "next",
@ -11,6 +11,5 @@
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "",
"license": "ISC"
}

View file

@ -13,6 +13,5 @@
"react": "^15.5.4",
"react-dom": "^15.5.4"
},
"author": "",
"license": "ISC"
}

View file

@ -1,4 +1,4 @@
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/with-material-ui)
[![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-antd-mobile)
# Ant Design Mobile example
## How to use

View file

@ -36,9 +36,15 @@ const tabBarData = [
link: '/home'
},
{
title: 'Trick',
title: 'Icon',
icon: 'check-circle-o',
selectedIcon: 'check-circle',
link: '/icon'
},
{
title: 'Trick',
icon: 'cross-circle-o',
selectedIcon: 'cross-circle',
link: '/trick'
}
]

View file

@ -3,29 +3,20 @@ const fs = require('fs')
const requireHacker = require('require-hacker')
function setupRequireHacker () {
requireHacker.resolver((filename, module) => {
if (filename.endsWith('/style/css')) {
return requireHacker.resolve(`${filename}.web.js`, module)
}
})
const webjs = '.web.js'
const webModules = ['antd-mobile', 'rmc-picker'].map(m => path.join('node_modules', m))
requireHacker.hook('js', filename => {
if (
filename.endsWith('.web.js') ||
!filename.includes('/node_modules/') ||
['antd-mobile', 'rc-swipeout', 'rmc-picker'].every(p => !filename.includes(p))
) return
if (filename.endsWith(webjs) || webModules.every(p => !filename.includes(p))) return
const webjs = filename.replace(/\.js$/, '.web.js')
if (!fs.existsSync(webjs)) return
const webFilename = filename.replace(/\.js$/, webjs)
if (!fs.existsSync(webFilename)) return
return fs.readFileSync(webjs, { encoding: 'utf8' })
return fs.readFileSync(webFilename, { encoding: 'utf8' })
})
requireHacker.hook('css', () => '')
requireHacker.hook('svg', filename => {
return requireHacker.to_javascript_module_source(fs.readFileSync(filename, { encoding: 'utf8' }))
return requireHacker.to_javascript_module_source(`#${path.parse(filename).name}`)
})
}
@ -40,11 +31,23 @@ module.exports = {
config.resolve.extensions = ['.web.js', '.js', '.json']
config.module.rules.push(
{
test: /\.(svg)$/i,
loader: 'emit-file-loader',
options: {
name: 'dist/[path][name].[ext]'
},
include: [
moduleDir('antd-mobile'),
__dirname
]
},
{
test: /\.(svg)$/i,
loader: 'svg-sprite-loader',
include: [
moduleDir('antd-mobile')
moduleDir('antd-mobile'),
__dirname
]
}
)

View file

@ -1,12 +1,14 @@
{
"name": "with-antd-mobile",
"version": "1.1.0",
"dependencies": {
"antd-mobile": "^1.1.2",
"babel-plugin-import": "^1.1.1",
"antd-mobile": "1.4.0",
"babel-plugin-import": "^1.2.1",
"next": "latest",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"require-hacker": "^3.0.0",
"svg-sprite-loader": "~0.3.0"
"svg-sprite-loader": "0.3.1"
},
"scripts": {
"dev": "next",

View file

@ -0,0 +1,81 @@
import React, { Component } from 'react'
import { WhiteSpace, WingBlank, Card, Icon } from 'antd-mobile'
import Layout from '../components/Layout'
import MenuBar from '../components/MenuBar'
export default class Home extends Component {
static getInitialProps ({ req }) {
const language = req ? req.headers['accept-language'] : navigator.language
return {
language
}
}
render () {
const {
language,
url: { pathname }
} = this.props
return (
<Layout language={language}>
<MenuBar
pathname={pathname}
>
<WingBlank>
<WhiteSpace />
<Card>
<Card.Header
extra='Internal svg'
thumb={<Icon type='check' />}
/>
<Card.Body>
<code>
{`<Icon type='check' />`}
</code>
</Card.Body>
</Card>
<WhiteSpace />
<Card>
<Card.Header
extra='Custom svg'
thumb={<Icon type={require('../static/reload.svg')} />}
/>
<Card.Body>
<code>
{`<Icon type={require('../static/reload.svg')} />`}
</code>
</Card.Body>
</Card>
<WhiteSpace />
<Card>
<Card.Header
extra='Fill color'
thumb={
<Icon
type={require('../static/reload.svg')}
style={{ fill: '#108ee9' }}
/>
}
/>
<Card.Body>
<code>{`
<Icon
type={require('../static/reload.svg')}
style={{ fill: '#108ee9' }}
/>
`}</code>
</Card.Body>
</Card>
<style jsx>{`
code {
color: gray;
}
`}</style>
</WingBlank>
</MenuBar>
</Layout>
)
}
}

View file

@ -1,8 +1,5 @@
import React, { Component } from 'react'
import {
WhiteSpace,
List, Switch, Modal, Button, Menu
} from 'antd-mobile'
import { WhiteSpace, List, Switch, Menu } from 'antd-mobile'
import Layout from '../components/Layout'
import MenuBar from '../components/MenuBar'
@ -19,9 +16,7 @@ export default class Trick extends Component {
}
}
constructor (props) {
super(props)
componentWillMount () {
this.menuData = [
{
label: 'Menu 1',
@ -58,8 +53,7 @@ export default class Trick extends Component {
]
this.state = {
switchChecked: true,
modalOpened: false
switchChecked: true
}
}
@ -71,8 +65,7 @@ export default class Trick extends Component {
} = this.props
const {
switchChecked,
modalOpened
switchChecked
} = this.state
return (
@ -81,7 +74,7 @@ export default class Trick extends Component {
pathname={pathname}
>
<WhiteSpace />
<List renderHeader={() => 'Switch and Modal platform prop is required in SSR mode'}>
<List renderHeader={() => 'Switch platform prop is required in SSR mode'}>
<List.Item
extra={
<Switch
@ -93,22 +86,7 @@ export default class Trick extends Component {
>
Switch {platform}
</List.Item>
<Button onClick={() => this.setState({ modalOpened: true })}>
Open {platform} modal
</Button>
</List>
<Modal
title='Modal'
platform={platform}
visible={modalOpened}
transparent
closable
footer={[{ text: 'OK', onPress: () => this.setState({ modalOpened: false }) }]}
onClose={() => this.setState({ modalOpened: false })}
>
I am a modal.<br />
Must set platform prop
</Modal>
<List renderHeader={() => 'Menu height prop is required in SSR mode'}>
<Menu
height={500}

View file

@ -0,0 +1 @@
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><title>Share Icons Copy</title><path d="M59.177 29.5s-1.25 0-1.25 2.5c0 14.471-11.786 26.244-26.253 26.244C17.206 58.244 5.417 46.47 5.417 32c0-13.837 11.414-25.29 25.005-26.26v6.252c0 .622-.318 1.635.198 1.985a1.88 1.88 0 0 0 1.75.19l21.37-8.545c.837-.334 1.687-1.133 1.687-2.384C55.425 1.99 53.944 2 53.044 2h-21.37C15.134 2 1.667 15.46 1.667 32c0 16.543 13.467 30 30.007 30 16.538 0 29.918-13.458 29.993-30 .01-2.5-1.24-2.5-1.24-2.5h-1.25" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 553 B

View file

@ -33,6 +33,7 @@ const upvotePost = gql`
mutation updatePost($id: ID!, $votes: Int) {
updatePost(id: $id, votes: $votes) {
id
__typename
votes
}
}
@ -43,7 +44,9 @@ export default graphql(upvotePost, {
upvote: (id, votes) => mutate({
variables: { id, votes },
optimisticResponse: {
__typename: 'Mutation',
updatePost: {
__typename: 'Post',
id: ownProps.id,
votes: ownProps.votes + 1
}

View file

@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { ApolloProvider, getDataFromTree } from 'react-apollo'
import Head from 'next/head'
import initApollo from './initApollo'
import initRedux from './initRedux'
@ -47,6 +48,9 @@ export default ComposedComponent => {
// Handle them in components via the data.error prop:
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
// Extract query data from the store
const state = redux.getState()

View file

@ -0,0 +1,16 @@
{
"env": {
"development": {
"presets": "next/babel"
},
"production": {
"presets": "next/babel"
},
"test": {
"presets": [
["env", { "modules": "commonjs" }],
"next/babel"
]
}
}
}

80
examples/with-apollo-auth/.gitignore vendored Normal file
View file

@ -0,0 +1,80 @@
.next
# Created by https://www.gitignore.io/api/vim,node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
### Vim ###
# swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags
# End of https://www.gitignore.io/api/vim,node

View file

@ -0,0 +1,56 @@
[![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-apollo-auth)
# Apollo With Authentication Example
## Demo
https://next-with-apollo-auth.now.sh
## How to use
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/v3-beta | tar -xz --strip=2 next.js-3-beta/examples/with-apollo-auth
cd with-apollo-auth
```
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 is an extention of the _[with Apollo](https://github.com/zeit/next.js/tree/master/examples/with-apollo#the-idea-behind-the-example)_ example:
> [Apollo](http://dev.apollodata.com) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server.
>
> In this simple example, we integrate Apollo seamlessly with Next by wrapping our *pages* inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). Using the HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application.
>
> On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized.
>
> This example relies on [graph.cool](https://www.graph.cool) for its GraphQL backend.
>
> *Note: 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.*
[graph.cool](https://www.graph.cool) can be setup with many different
[authentication providers](https://www.graph.cool/docs/reference/integrations/overview-seimeish6e/#authentication-providers), the most basic of which is [email-password authentication](https://www.graph.cool/docs/reference/simple-api/user-authentication-eixu9osueb/#email-and-password). Once email-password authentication is enabled for your graph.cool project, you are provided with 2 useful mutations: `createUser` and `signinUser`.
On loading each route, we perform a `user` query to see if the current visitor is logged in (based on a cookie, more on that in a moment). Depending on the query result, and the route, the user may be [redirected](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/lib/redirect.js) to a different page.
When creating an account, both the `createUser` and `signinUser` mutations are executed on graph.cool, which returns a token that can be used to [authenticate the user for future requests](https://www.graph.cool/docs/reference/auth/authentication-tokens-eip7ahqu5o/). The token is stored in a cookie for easy access (_note: This may have security implications. Please understand XSS and JWT before deploying this to production_).
A similar process is followed when signing in, except `signinUser` is the only mutation executed.
It is important to note the use of Apollo's `resetStore()` method after signing in and signing out to ensure that no user data is kept in the browser's memory.
To get this example running locally, you will need to create a graph.cool
account, and provide [the `project.graphcool` schema](https://github.com/zeit/next.js/blob/master/examples/with-apollo-auth/project.graphcool).

View file

@ -0,0 +1,19 @@
import gql from 'graphql-tag'
export default (context, apolloClient) => (
apolloClient.query({
query: gql`
query getUser {
user {
id
name
}
}
`
}).then(({ data }) => {
return { loggedInUser: data }
}).catch(() => {
// Fail gracefully
return { loggedInUser: {} }
})
)

View file

@ -0,0 +1,47 @@
import { ApolloClient, createNetworkInterface } from 'react-apollo'
import fetch from 'isomorphic-fetch'
let apolloClient = null
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
global.fetch = fetch
}
function create (initialState, { getToken }) {
const networkInterface = createNetworkInterface({
uri: 'https://api.graph.cool/simple/v1/cj3h80ffbllm20162alevpcby'
})
networkInterface.use([{
applyMiddleware (req, next) {
if (!req.options.headers) {
req.options.headers = {} // Create the header object if needed.
}
const token = getToken()
req.options.headers.authorization = token ? `Bearer ${token}` : null
next()
}
}])
return new ApolloClient({
initialState,
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
networkInterface
})
}
export default function initApollo (initialState, options) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (!process.browser) {
return create(initialState, options)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState, options)
}
return apolloClient
}

View file

@ -0,0 +1,13 @@
import Router from 'next/router'
export default (context, target) => {
if (context.res) {
// server
// 303: "See other"
context.res.writeHead(303, { Location: target })
context.res.end()
} else {
// In the browser, we just pretend like this never even happened ;)
Router.replace(target)
}
}

View file

@ -0,0 +1,94 @@
import React from 'react'
import cookie from 'cookie'
import PropTypes from 'prop-types'
import { ApolloProvider, getDataFromTree } from 'react-apollo'
import initApollo from './init-apollo'
function parseCookies (ctx = {}, options = {}) {
return cookie.parse(
ctx.req && ctx.req.headers.cookie
? ctx.req.headers.cookie
: document.cookie,
options
)
}
export default ComposedComponent => {
return class WithData extends React.Component {
static displayName = `WithData(${ComposedComponent.displayName})`
static propTypes = {
serverState: PropTypes.object.isRequired
}
static async getInitialProps (context) {
let serverState = {}
// Setup a server-side one-time-use apollo client for initial props and
// rendering (on server)
let apollo = initApollo({}, {
getToken: () => parseCookies(context).token
})
// Evaluate the composed component's getInitialProps()
let composedInitialProps = {}
if (ComposedComponent.getInitialProps) {
composedInitialProps = await ComposedComponent.getInitialProps(context, apollo)
}
// Run all graphql queries in the component tree
// and extract the resulting data
if (!process.browser) {
if (context.res && context.res.finished) {
// When redirecting, the response is finished.
// No point in continuing to render
return
}
// Provide the `url` prop data in case a graphql query uses it
const url = {query: context.query, pathname: context.pathname}
// Run all graphql queries
const app = (
<ApolloProvider client={apollo}>
<ComposedComponent url={url} {...composedInitialProps} />
</ApolloProvider>
)
await getDataFromTree(app)
// Extract query data from the Apollo's store
const state = apollo.getInitialState()
serverState = {
apollo: { // Make sure to only include Apollo's data state
data: state.data
}
}
}
return {
serverState,
...composedInitialProps
}
}
constructor (props) {
super(props)
// Note: Apollo should never be used on the server side beyond the initial
// render within `getInitialProps()` above (since the entire prop tree
// will be initialized there), meaning the below will only ever be
// executed on the client.
this.apollo = initApollo(this.props.serverState, {
getToken: () => parseCookies().token
})
}
render () {
return (
<ApolloProvider client={this.apollo}>
<ComposedComponent {...this.props} />
</ApolloProvider>
)
}
}
}

View file

@ -0,0 +1,33 @@
{
"name": "with-apollo-auth",
"version": "0.0.0",
"author": "",
"license": "ISC",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"test": "NODE_ENV=test ava"
},
"dependencies": {
"cookie": "^0.3.1",
"graphql": "^0.9.3",
"isomorphic-fetch": "^2.2.1",
"next": "latest",
"prop-types": "^15.5.10",
"react": "^15.5.4",
"react-apollo": "^1.1.3",
"react-dom": "^15.5.4"
},
"devDependencies": {
"ava": "^0.19.1",
"clear-require": "^2.0.0",
"glob": "^7.1.2"
},
"ava": {
"require": [
"babel-register"
],
"babel": "inherit"
}
}

View file

@ -0,0 +1,104 @@
import React from 'react'
import { graphql, withApollo, compose } from 'react-apollo'
import cookie from 'cookie'
import Link from 'next/link'
import gql from 'graphql-tag'
import withData from '../lib/with-data'
import redirect from '../lib/redirect'
import checkLoggedIn from '../lib/check-logged-in'
class CreateAccount extends React.Component {
static async getInitialProps (context, apolloClient) {
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
if (loggedInUser.user) {
// Already signed in? No need to continue.
// Throw them back to the main page
redirect(context, '/')
}
return {}
}
render () {
return (
<div>
{/* this.props.create is the mutation function provided by apollo below */}
<form onSubmit={this.props.create}>
<input type='text' placeholder='Your Name' name='name' /><br />
<input type='email' placeholder='Email' name='email' /><br />
<input type='password' placeholder='Password' name='password' /><br />
<button>Create account</button>
</form>
<hr />
Already have an account? <Link prefetch href='/signin'><a>Sign in</a></Link>
</div>
)
}
};
export default compose(
// withData gives us server-side graphql queries before rendering
withData,
// withApollo exposes `this.props.client` used when logging out
withApollo,
graphql(
// The `createUser` & `signinUser` mutations are provided by graph.cool by
// default.
// Multiple mutations are executed by graphql sequentially
gql`
mutation Create($name: String!, $email: String!, $password: String!) {
createUser(name: $name, authProvider: { email: { email: $email, password: $password }}) {
id
}
signinUser(email: { email: $email, password: $password }) {
token
}
}
`,
{
// Use an unambiguous name for use in the `props` section below
name: 'createWithEmail',
// Apollo's way of injecting new props which are passed to the component
props: ({
createWithEmail,
// `client` is provided by the `withApollo` HOC
ownProps: { client }
}) => ({
// `create` is the name of the prop passed to the component
create: (event) => {
/* global FormData */
const data = new FormData(event.target)
event.preventDefault()
event.stopPropagation()
createWithEmail({
variables: {
email: data.get('email'),
password: data.get('password'),
name: data.get('name')
}
}).then(({ data: { signinUser: { token } } }) => {
// Store the token in cookie
document.cookie = cookie.serialize('token', token, {
maxAge: 30 * 24 * 60 * 60 // 30 days
})
// Force a reload of all the current queries now that the user is
// logged in
client.resetStore().then(() => {
// Now redirect to the homepage
redirect({}, '/')
})
}).catch((error) => {
// Something went wrong, such as incorrect password, or no network
// available, etc.
console.error(error)
})
}
})
}
)
)(CreateAccount)

View file

@ -0,0 +1,49 @@
import React from 'react'
import cookie from 'cookie'
import { withApollo, compose } from 'react-apollo'
import withData from '../lib/with-data'
import redirect from '../lib/redirect'
import checkLoggedIn from '../lib/check-logged-in'
class Index extends React.Component {
static async getInitialProps (context, apolloClient) {
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
if (!loggedInUser.user) {
// If not signed in, send them somewhere more useful
redirect(context, '/signin')
}
return { loggedInUser }
}
signout = () => {
document.cookie = cookie.serialize('token', '', {
maxAge: -1 // Expire the cookie immediately
})
// Force a reload of all the current queries now that the user is
// logged in, so we don't accidentally leave any state around.
this.props.client.resetStore().then(() => {
// Redirect to a more useful page when signed out
redirect({}, '/signin')
})
}
render () {
return (
<div>
Hello {this.props.loggedInUser.user.name}!<br />
<button onClick={this.signout}>Sign out</button>
</div>
)
}
};
export default compose(
// withData gives us server-side graphql queries before rendering
withData,
// withApollo exposes `this.props.client` used when logging out
withApollo
)(Index)

View file

@ -0,0 +1,97 @@
import React from 'react'
import { graphql, withApollo, compose } from 'react-apollo'
import cookie from 'cookie'
import Link from 'next/link'
import gql from 'graphql-tag'
import withData from '../lib/with-data'
import redirect from '../lib/redirect'
import checkLoggedIn from '../lib/check-logged-in'
class Signin extends React.Component {
static async getInitialProps (context, apolloClient) {
const { loggedInUser } = await checkLoggedIn(context, apolloClient)
if (loggedInUser.user) {
// Already signed in? No need to continue.
// Throw them back to the main page
redirect(context, '/')
}
return {}
}
render () {
return (
<div>
{/* this.props.signin is the mutation function provided by apollo below */}
<form onSubmit={this.props.signin}>
<input type='email' placeholder='Email' name='email' /><br />
<input type='password' placeholder='Password' name='password' /><br />
<button>Sign in</button>
</form>
<hr />
New? <Link prefetch href='/create-account'><a>Create account</a></Link>
</div>
)
}
};
export default compose(
// withData gives us server-side graphql queries before rendering
withData,
// withApollo exposes `this.props.client` used when logging out
withApollo,
graphql(
// The `signinUser` mutation is provided by graph.cool by default
gql`
mutation Signin($email: String!, $password: String!) {
signinUser(email: { email: $email, password: $password }) {
token
}
}
`,
{
// Use an unambiguous name for use in the `props` section below
name: 'signinWithEmail',
// Apollo's way of injecting new props which are passed to the component
props: ({
signinWithEmail,
// `client` is provided by the `withApollo` HOC
ownProps: { client }
}) => ({
// `signin` is the name of the prop passed to the component
signin: (event) => {
/* global FormData */
const data = new FormData(event.target)
event.preventDefault()
event.stopPropagation()
signinWithEmail({
variables: {
email: data.get('email'),
password: data.get('password')
}
}).then(({ data: { signinUser: { token } } }) => {
// Store the token in cookie
document.cookie = cookie.serialize('token', token, {
maxAge: 30 * 24 * 60 * 60 // 30 days
})
// Force a reload of all the current queries now that the user is
// logged in
client.resetStore().then(() => {
// Now redirect to the homepage
redirect({}, '/')
})
}).catch((error) => {
// Something went wrong, such as incorrect password, or no network
// available, etc.
console.error(error)
})
}
})
}
)
)(Signin)

View file

@ -0,0 +1,22 @@
# projectId: cj3h80ffbllm20162alevpcby
# version: 3
type File implements Node {
contentType: String!
createdAt: DateTime!
id: ID! @isUnique
name: String!
secret: String! @isUnique
size: Int!
updatedAt: DateTime!
url: String! @isUnique
}
type User implements Node {
createdAt: DateTime!
email: String @isUnique
id: ID! @isUnique
name: String!
password: String
updatedAt: DateTime!
}

View file

@ -0,0 +1,41 @@
const clearRequire = require('clear-require')
const glob = require('glob')
const test = require('ava')
/**
* Motivations:
*
* - Client-side getInitialProps() wont have access to the apollo client for
* that page (because it's not shared across page bundles), so wont be able to
* reset the state, leaving all the logged in user data there :(
* - So, we have to have a shared module. BUT; next's code splitting means the
* bundle for each page will include its own copy of the module, _unless it's
* inlcuded in every page_.
* - https://github.com/zeit/next.js/issues/659#issuecomment-271824223
* - https://github.com/zeit/next.js/issues/1635#issuecomment-292236785
* - Therefore, this test ensures that every page includes that module, and
* hence it will be shared across every page, giving us a global store in
* Apollo that we can clear, etc
*/
const apolloFilePath = require.resolve('../lib/init-apollo')
test.beforeEach(() => {
// Clean up the cache
clearRequire.all()
})
glob.sync('./pages/**/*.js').forEach((file) => {
test(`.${file} imports shared apollo module`, (t) => {
t.falsy(require.cache[apolloFilePath])
try {
require(`.${file}`)
} catch (error) {
// Don't really care if it fails to execute, etc, just want to be
// certain the expected require call was made
}
t.truthy(require.cache[apolloFilePath])
})
})

View file

@ -33,6 +33,7 @@ const upvotePost = gql`
mutation updatePost($id: ID!, $votes: Int) {
updatePost(id: $id, votes: $votes) {
id
__typename
votes
}
}
@ -43,7 +44,9 @@ export default graphql(upvotePost, {
upvote: (id, votes) => mutate({
variables: { id, votes },
optimisticResponse: {
__typename: 'Mutation',
updatePost: {
__typename: 'Post',
id: ownProps.id,
votes: ownProps.votes + 1
}

View file

@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { ApolloProvider, getDataFromTree } from 'react-apollo'
import Head from 'next/head'
import initApollo from './initApollo'
// Gets the display name of a JSX component for dev tools
@ -30,7 +31,6 @@ export default ComposedComponent => {
const apollo = initApollo()
// Provide the `url` prop data in case a GraphQL query uses it
const url = {query: ctx.query, pathname: ctx.pathname}
try {
// Run all GraphQL queries
await getDataFromTree(
@ -43,6 +43,9 @@ export default ComposedComponent => {
// Handle them in components via the data.error prop:
// http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
// Extract query data from the Apollo store
const state = apollo.getInitialState()

View file

@ -0,0 +1,4 @@
{
"presets": ["react"],
"plugins": ["babel-macros"]
}

View file

@ -0,0 +1,39 @@
[![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-babel-macros)
# Example app with [babel-macros](https://github.com/kentcdodds/babel-macros)
## How to use
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-babel-macros
cd with-babel-macros
```
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 configure and use [`babel-macros`](https://github.com/kentcdodds/babel-macros) which allows you
to easily add babel plugins which export themselves as a macro without needing
to configure them.
You'll notice the configuration in `.babelrc` includes the `babel-macros`
plugin, then we can use the `preval.macro` in `pages/index.js` to pre-evaluate
code at build-time. `preval.macro` is effectively transforming our code, but
we didn't have to configure it to make that happen!
Specifically what we're doing is we're prevaling the username of the user who
ran the build.

View file

@ -0,0 +1,22 @@
{
"name": "with-babel-macros",
"version": "1.0.0",
"description": "using next.js with babel-macros",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2"
},
"author": "Kent C. Dodds <kent@doddsfamily.us> (http://kentcdodds.com/)",
"license": "MIT",
"devDependencies": {
"babel-macros": "^0.5.1",
"babel-preset-react": "^6.24.1",
"preval.macro": "^1.0.1"
}
}

View file

@ -0,0 +1,19 @@
import React from 'react'
import Document, {Head, Main, NextScript} from 'next/document'
export default class MyDocument extends Document {
render () {
return (
<html>
<Head>
<title>With babel-macros</title>
<style dangerouslySetInnerHTML={{__html: this.props.css}} />
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
)
}
}

View file

@ -0,0 +1,21 @@
import React from 'react'
import preval from 'preval.macro'
const whoami = preval`
const userInfo = require('os').userInfo()
module.exports = userInfo.username
`
export default WhoAmI
function WhoAmI () {
return (
<div style={{display: 'flex', justifyContent: 'center'}}>
<h1>
<pre>
whoami: {whoami}
</pre>
</h1>
</div>
)
}

View file

@ -0,0 +1,7 @@
{
"presets": [
"next/babel",
"latest",
"stage-0"
]
}

View file

@ -0,0 +1,3 @@
{
"extends": "standard"
}

View file

@ -0,0 +1,52 @@
## The idea behind the example
This example applies this gist https://gist.github.com/jamsesso/67fd937b74989dc52e33 to Nextjs and provides:
* Reverse proxy in development mode by add `http-proxy-middleware` to custom server
* NOT a recommended approach to production scale (hence explicit dev flag) as we should scope proxy as outside UI applications and have separate web server taking care of that.
Sorry for the extra packages. I belong to the minority camp of writing ES6 code on Windows developers. Essentially you only need `http-proxy-middleware` on top of bare-bone Nextjs setup to run this example.
## How to run it
```
npm i; npm run build; npm run dev;
```
## What it does
Take any random query string to the index page and does a GET to `/api/<query string>` which gets routed internally to `https://swapi.co/api/<query string>`, or any API endpoint you wish to configure through the proxy.
## Expectation
```
/api/people/2 routed to https://swapi.co/api/people/2
Try Reset
{
"name": "C-3PO",
"height": "167",
"mass": "75",
"hair_color": "n/a",
"skin_color": "gold",
"eye_color": "yellow",
"birth_year": "112BBY",
"gender": "n/a",
"homeworld": "https://swapi.co/api/planets/1/",
"films": [
"https://swapi.co/api/films/2/",
"https://swapi.co/api/films/5/",
"https://swapi.co/api/films/4/",
"https://swapi.co/api/films/6/",
"https://swapi.co/api/films/3/",
"https://swapi.co/api/films/1/"
],
"species": [
"https://swapi.co/api/species/2/"
],
"vehicles": [],
"starships": [],
"created": "2014-12-10T15:10:51.357000Z",
"edited": "2014-12-20T21:17:50.309000Z",
"url": "https://swapi.co/api/people/2/"
}
```

View file

@ -0,0 +1,25 @@
{
"name": "with-custom-reverse-proxy",
"engines": {
"node": "7.x.x"
},
"license": "ISC",
"dependencies": {
"express": "^4.15.3",
"next": "^3.0.1-beta.13",
"react": "^15.5.4",
"react-dom": "^15.5.4"
},
"devDependencies": {
"babel-preset-latest": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-register": "^6.24.1",
"cross-env": "^5.0.1",
"http-proxy-middleware": "^0.17.4"
},
"scripts": {
"dev": "cross-env NODE_ENV=development PORT=3000 node server.js",
"build": "next build",
"prod": "cross-env NODE_ENV=production PORT=3000 node server.js"
}
}

View file

@ -0,0 +1,45 @@
import React from 'react'
export default class extends React.Component {
constructor (props) {
super(props)
this.state = { response: '' }
}
static async getInitialProps ({ pathname, query }) {
return {
pathname,
query,
queryString: Object.keys(query).join('')
}
}
async componentDidMount () {
const response = JSON.stringify(
await window
.fetch(`/api/${this.props.queryString}`)
.then(response => response.json().then(data => data)),
null,
2
)
this.setState({ response })
}
render () {
return (
<content>
<p>
/api/{this.props.queryString} routed to https://swapi.co/api/{this.props.queryString}
</p>
<p>
<a href='?people/2'>Try</a>
&nbsp;
<a href='/'>Reset</a>
</p>
<pre>
{this.state.response ? this.state.response : 'Loading...'}
</pre>
</content>
)
}
}

View file

@ -0,0 +1,50 @@
/* eslint-disable no-console */
import express from 'express'
import next from 'next'
const devProxy = {
'/api': {
target: 'https://swapi.co/api/',
pathRewrite: {'^/api': '/'},
changeOrigin: true
}
}
const port = process.env.PORT || 3000
const env = process.env.NODE_ENV
const dev = env !== 'production'
const app = next({
dir: '.', // base directory where everything is, could move to src later
dev
})
const handle = app.getRequestHandler()
let server
app
.prepare()
.then(() => {
server = express()
// Set up the proxy.
if (dev && devProxy) {
const proxyMiddleware = require('http-proxy-middleware')
Object.keys(devProxy).forEach(function (context) {
server.use(proxyMiddleware(context, devProxy[context]))
})
}
// Default catch-all handler to allow Next.js to handle all other routes
server.all('*', (req, res) => handle(req, res))
server.listen(port, err => {
if (err) {
throw err
}
console.log(`> Ready on port ${port} [${env}]`)
})
})
.catch(err => {
console.log('An error occurred, unable to start the server')
console.log(err)
})

View file

@ -0,0 +1,2 @@
require('babel-register')
module.exports = require('./server.es6.js')

View file

@ -0,0 +1,13 @@
{
"presets": [
"next/babel",
],
"env": {
"development": {
"plugins": ["inline-dotenv"]
},
"production": {
"plugins": ["transform-inline-environment-variables"]
}
}
}

View file

@ -0,0 +1 @@
TEST=it works!

View file

@ -0,0 +1 @@
TEST=it works!

View file

@ -0,0 +1,31 @@
[![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-dotenv)
# With Dotenv example
## How to use
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-dotenv
cd with-dotenv
```
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 shows the most basic idea of babel replacement from multiple environment. We have 1 env variable: `TEST` which will be replaced in development env and in production env with different babel plugin. In local development, babel reads .env file and replace process.env.* in your nextjs files. In production env (such as heroku), babel reads the ENV and replace process.env.* in your nextjs files. Thus no more needed to commit your secrets anymore.
Of course, please put .env* in your .gitignore when using this example locally.

View file

@ -0,0 +1,17 @@
{
"name": "with-dotenv",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "latest",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"babel-plugin-inline-dotenv": "^1.1.1",
"babel-plugin-transform-inline-environment-variables": "^0.1.1"
},
"license": "ISC"
}

View file

@ -0,0 +1,3 @@
export default () => (
<div>{ process.env.TEST }</div>
)

View file

@ -0,0 +1,27 @@
# Example app with dynamic-imports
## How to use
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/v3-beta | tar -xz --strip=2 next.js-3-beta/examples/with-dynamic-import
cd with-dynamic-import
```
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 examples shows how to dynamically import modules via [`import()`](https://github.com/tc39/proposal-dynamic-import) API

View file

@ -0,0 +1,19 @@
import React from 'react'
let count = 0
export default class Counter extends React.Component {
add () {
count += 1
this.forceUpdate()
}
render () {
return (
<div>
<p>Count is: {count}</p>
<button onClick={() => this.add()}>Add</button>
</div>
)
}
}

View file

@ -0,0 +1,19 @@
import Link from 'next/link'
export default () => (
<div>
<Link href='/'>
<a style={styles.a} >Home</a>
</Link>
<Link href='/about'>
<a style={styles.a} >About</a>
</Link>
</div>
)
const styles = {
a: {
marginRight: 10
}
}

View file

@ -0,0 +1,3 @@
export default () => (
<p>Hello World 1 (imported dynamiclly) </p>
)

View file

@ -0,0 +1,3 @@
export default () => (
<p>Hello World 2 (imported dynamiclly) </p>
)

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