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:
commit
daf8d5bf95
39
.github/issue_template.md
vendored
Normal file
39
.github/issue_template.md
vendored
Normal 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
5
.gitignore
vendored
|
@ -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
|
||||
|
|
|
@ -8,7 +8,6 @@ addons:
|
|||
- google-chrome-stable
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4"
|
||||
- "6"
|
||||
cache:
|
||||
directories:
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "4"
|
||||
- nodejs_version: "6"
|
||||
|
||||
# Install scripts. (runs after repo cloning)
|
||||
|
|
3
bin/next
3
bin/next
|
@ -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
|
||||
])
|
||||
|
||||
|
|
11
bin/next-dev
11
bin/next-dev
|
@ -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
67
bin/next-export
Normal 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)
|
||||
})
|
|
@ -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) {
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`)
|
||||
}
|
||||
|
|
|
@ -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
1
dynamic.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = require('./dist/lib/dynamic')
|
|
@ -11,6 +11,5 @@
|
|||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"name": "custom-server-express",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"name": "custom-server-hapi",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "custom-server-koa",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"name": "custom-server",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
|
|
|
@ -12,6 +12,5 @@
|
|||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "*",
|
||||
"next": "latest",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "*",
|
||||
"next": "latest",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "*",
|
||||
"next": "latest",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
|
|
29
examples/page-transitions/README.md
Normal file
29
examples/page-transitions/README.md
Normal 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.
|
19
examples/page-transitions/package.json
Normal file
19
examples/page-transitions/package.json
Normal 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"
|
||||
}
|
21
examples/page-transitions/pages/_document.js
Normal file
21
examples/page-transitions/pages/_document.js
Normal 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>)
|
||||
}
|
||||
}
|
39
examples/page-transitions/pages/index.js
Normal file
39
examples/page-transitions/pages/index.js
Normal 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} />
|
||||
}
|
||||
}
|
38
examples/page-transitions/pages/main.js
Normal file
38
examples/page-transitions/pages/main.js
Normal 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
|
||||
}
|
6
examples/page-transitions/routes.js
Normal file
6
examples/page-transitions/routes.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const nextRoutes = require('next-routes')
|
||||
const routes = nextRoutes()
|
||||
|
||||
routes.add('main', '/:slug/:child?', '')
|
||||
|
||||
module.exports = routes
|
12
examples/page-transitions/server.js
Normal file
12
examples/page-transitions/server.js
Normal 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))
|
99
examples/page-transitions/static/style.css
Normal file
99
examples/page-transitions/static/style.css
Normal 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;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"name": "parameterized-routing",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "progressive-render",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"name": "root-static-files",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{
|
||||
"name": "ssr-caching",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
|
|
|
@ -13,6 +13,5 @@
|
|||
"module-alias": "^2.0.0",
|
||||
"next": "latest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
19
examples/with-algolia-react-instantsearch/.gitignore
vendored
Normal file
19
examples/with-algolia-react-instantsearch/.gitignore
vendored
Normal 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*
|
34
examples/with-algolia-react-instantsearch/README.md
Normal file
34
examples/with-algolia-react-instantsearch/README.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/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.
|
86
examples/with-algolia-react-instantsearch/components/app.js
Normal file
86
examples/with-algolia-react-instantsearch/components/app.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
48
examples/with-algolia-react-instantsearch/components/head.js
Normal file
48
examples/with-algolia-react-instantsearch/components/head.js
Normal 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
|
|
@ -0,0 +1,3 @@
|
|||
export * from './head'
|
||||
export { default as App } from './app'
|
||||
export { findResultsState } from './instantsearch'
|
|
@ -0,0 +1,5 @@
|
|||
import { createInstantSearch } from 'react-instantsearch/server'
|
||||
|
||||
const { InstantSearch, findResultsState } = createInstantSearch()
|
||||
|
||||
export { InstantSearch, findResultsState }
|
19
examples/with-algolia-react-instantsearch/next.config.js
Normal file
19
examples/with-algolia-react-instantsearch/next.config.js
Normal 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
|
||||
}
|
||||
}
|
19
examples/with-algolia-react-instantsearch/package.json
Normal file
19
examples/with-algolia-react-instantsearch/package.json
Normal 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"
|
||||
}
|
||||
}
|
73
examples/with-algolia-react-instantsearch/pages/index.js
Normal file
73
examples/with-algolia-react-instantsearch/pages/index.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
BIN
examples/with-algolia-react-instantsearch/static/favicon.ico
Normal file
BIN
examples/with-algolia-react-instantsearch/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -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;
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -13,6 +13,5 @@
|
|||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
]
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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",
|
||||
|
|
81
examples/with-antd-mobile/pages/icon.js
Normal file
81
examples/with-antd-mobile/pages/icon.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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}
|
||||
|
|
1
examples/with-antd-mobile/static/reload.svg
Normal file
1
examples/with-antd-mobile/static/reload.svg
Normal 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 |
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
16
examples/with-apollo-auth/.babelrc
Normal file
16
examples/with-apollo-auth/.babelrc
Normal 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
80
examples/with-apollo-auth/.gitignore
vendored
Normal 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
|
56
examples/with-apollo-auth/README.md
Normal file
56
examples/with-apollo-auth/README.md
Normal 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).
|
19
examples/with-apollo-auth/lib/check-logged-in.js
Normal file
19
examples/with-apollo-auth/lib/check-logged-in.js
Normal 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: {} }
|
||||
})
|
||||
)
|
47
examples/with-apollo-auth/lib/init-apollo.js
Normal file
47
examples/with-apollo-auth/lib/init-apollo.js
Normal 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
|
||||
}
|
13
examples/with-apollo-auth/lib/redirect.js
Normal file
13
examples/with-apollo-auth/lib/redirect.js
Normal 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)
|
||||
}
|
||||
}
|
94
examples/with-apollo-auth/lib/with-data.js
Normal file
94
examples/with-apollo-auth/lib/with-data.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
33
examples/with-apollo-auth/package.json
Normal file
33
examples/with-apollo-auth/package.json
Normal 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"
|
||||
}
|
||||
}
|
104
examples/with-apollo-auth/pages/create-account.js
Normal file
104
examples/with-apollo-auth/pages/create-account.js
Normal 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)
|
49
examples/with-apollo-auth/pages/index.js
Normal file
49
examples/with-apollo-auth/pages/index.js
Normal 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)
|
97
examples/with-apollo-auth/pages/signin.js
Normal file
97
examples/with-apollo-auth/pages/signin.js
Normal 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)
|
22
examples/with-apollo-auth/project.graphcool
Normal file
22
examples/with-apollo-auth/project.graphcool
Normal 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!
|
||||
}
|
41
examples/with-apollo-auth/test/shared-apollo.js
Normal file
41
examples/with-apollo-auth/test/shared-apollo.js
Normal 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])
|
||||
})
|
||||
})
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
4
examples/with-babel-macros/.babelrc
Normal file
4
examples/with-babel-macros/.babelrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"presets": ["react"],
|
||||
"plugins": ["babel-macros"]
|
||||
}
|
39
examples/with-babel-macros/README.md
Normal file
39
examples/with-babel-macros/README.md
Normal 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.
|
22
examples/with-babel-macros/package.json
Normal file
22
examples/with-babel-macros/package.json
Normal 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"
|
||||
}
|
||||
}
|
19
examples/with-babel-macros/pages/_document.js
Normal file
19
examples/with-babel-macros/pages/_document.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
21
examples/with-babel-macros/pages/index.js
Normal file
21
examples/with-babel-macros/pages/index.js
Normal 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>
|
||||
)
|
||||
}
|
7
examples/with-custom-reverse-proxy/.babelrc
Normal file
7
examples/with-custom-reverse-proxy/.babelrc
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"presets": [
|
||||
"next/babel",
|
||||
"latest",
|
||||
"stage-0"
|
||||
]
|
||||
}
|
3
examples/with-custom-reverse-proxy/.eslintrc
Normal file
3
examples/with-custom-reverse-proxy/.eslintrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "standard"
|
||||
}
|
52
examples/with-custom-reverse-proxy/README.md
Normal file
52
examples/with-custom-reverse-proxy/README.md
Normal 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/"
|
||||
}
|
||||
```
|
25
examples/with-custom-reverse-proxy/package.json
Normal file
25
examples/with-custom-reverse-proxy/package.json
Normal 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"
|
||||
}
|
||||
}
|
45
examples/with-custom-reverse-proxy/pages/index.js
Normal file
45
examples/with-custom-reverse-proxy/pages/index.js
Normal 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>
|
||||
|
||||
<a href='/'>Reset</a>
|
||||
</p>
|
||||
<pre>
|
||||
{this.state.response ? this.state.response : 'Loading...'}
|
||||
</pre>
|
||||
</content>
|
||||
)
|
||||
}
|
||||
}
|
50
examples/with-custom-reverse-proxy/server.es6.js
Normal file
50
examples/with-custom-reverse-proxy/server.es6.js
Normal 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)
|
||||
})
|
2
examples/with-custom-reverse-proxy/server.js
Normal file
2
examples/with-custom-reverse-proxy/server.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
require('babel-register')
|
||||
module.exports = require('./server.es6.js')
|
13
examples/with-dotenv/.babelrc
Normal file
13
examples/with-dotenv/.babelrc
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"presets": [
|
||||
"next/babel",
|
||||
],
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["inline-dotenv"]
|
||||
},
|
||||
"production": {
|
||||
"plugins": ["transform-inline-environment-variables"]
|
||||
}
|
||||
}
|
||||
}
|
1
examples/with-dotenv/.env
Normal file
1
examples/with-dotenv/.env
Normal file
|
@ -0,0 +1 @@
|
|||
TEST=it works!
|
1
examples/with-dotenv/.env.production
Normal file
1
examples/with-dotenv/.env.production
Normal file
|
@ -0,0 +1 @@
|
|||
TEST=it works!
|
31
examples/with-dotenv/README.md
Normal file
31
examples/with-dotenv/README.md
Normal 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.
|
17
examples/with-dotenv/package.json
Normal file
17
examples/with-dotenv/package.json
Normal 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"
|
||||
}
|
3
examples/with-dotenv/pages/index.js
Normal file
3
examples/with-dotenv/pages/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default () => (
|
||||
<div>{ process.env.TEST }</div>
|
||||
)
|
27
examples/with-dynamic-import/README.md
Normal file
27
examples/with-dynamic-import/README.md
Normal 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
|
19
examples/with-dynamic-import/components/Counter.js
Normal file
19
examples/with-dynamic-import/components/Counter.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
19
examples/with-dynamic-import/components/Header.js
Normal file
19
examples/with-dynamic-import/components/Header.js
Normal 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
|
||||
}
|
||||
}
|
3
examples/with-dynamic-import/components/hello1.js
Normal file
3
examples/with-dynamic-import/components/hello1.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default () => (
|
||||
<p>Hello World 1 (imported dynamiclly) </p>
|
||||
)
|
3
examples/with-dynamic-import/components/hello2.js
Normal file
3
examples/with-dynamic-import/components/hello2.js
Normal 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
Loading…
Reference in a new issue