mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
Merge branch 'canary'
This commit is contained in:
commit
3c97f9664b
8
.github/issue_template.md
vendored
8
.github/issue_template.md
vendored
|
@ -1,12 +1,18 @@
|
|||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
<!--
|
||||
⚠️ IMPORTANT ⚠️
|
||||
If you have a question about Next.js please ask it on Spectrum https://spectrum.chat/next-js
|
||||
or join our Slack community at https://zeit.chat and ask it in the `#next` channel
|
||||
-->
|
||||
|
||||
<!--
|
||||
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.
|
||||
- [ ] I have searched the [issues](https://github.com/zeit/next.js/issues?q=is%3Aissue) of this repository and believe that this is not a duplicate.
|
||||
|
||||
## Expected Behavior
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,8 +7,9 @@ node_modules
|
|||
package-lock.json
|
||||
test/node_modules
|
||||
|
||||
# logs
|
||||
# logs & pids
|
||||
*.log
|
||||
pids
|
||||
|
||||
# coverage
|
||||
.nyc_output
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
email: "leo@zeit.co",
|
||||
tag: "canary",
|
||||
api_key: {
|
||||
secure:
|
||||
"br9gLncKeLSoL7iOq0PXFeD1Gp3jRI8QMCSNIrGdZg/MuLeuwYPv36kmMgf7aGIfpjFLtU2/eVrOHSB+T5g1nKgOsqmWsfZE1tQYWph0//ookd/sj+IyxIx8nVLbUV/C4ctYOhX/efotRgNn56w5Av5hWc1IQLmW9mSN8ijrQnM+GzRI8QitiofeY2EP3N1eO8vC8E2oGkOsAdcypiX6lFG908zyWt7X2SD+iOsK2eAHjxoAEUdrxE5a8gTDhcTH6qnmtBs9rCeEKbO3JZjEy5dvccxlX3Nd+2GC1rckayk6o5L/zveTilsUx6Auqqbwn1dT5ffQuYsV4RPofs8IMrhnizc8y+OfUcCCpBJ4ia4w7N8FEP56TnRNTFoFBGJL5Y6NfeT0HHAlClxUWMG9pRGWGN+sskODDQ9FEntGZoqwV396ogs+3YhkxbY0AIr84QOctflsFcPtOgr/CoBeOsjbB+o9+Rlsqwlf3u3B7qhtU9eV6KcMfK4x9qW+2cwTllK+gD8S9wILx5BChkfx99g/7u/Rg1PCym64tTsDOBtqTVC2YCqeYYvjmpw4Vl3ofLrFsoNQnbmb6Q5+JSpOcJ/bEj7P/FuZdlU0fNV28tFhElu5caKhSCJz/avUlXG7NeveW1Ee8gjhURC4V/l4ryacyjA2vcDY/4RRkWtHNr4="
|
||||
secure: "mvLXMXn1z1ri29wAy5/CCrDuO7ZC3gxckcnY5W00cbL8aFuK5buvpizKr3dz1pYX7vC55I+nZMtVa2vn4oOlTae07dd0BYGmmpiwq692XD4P+PHVs8D5MlbiT62vIJl/ezv/TcyoAbfIVPfUfLXA7H6a04Kodyzj3LffannOYpZNTPRNSdNAtkVtHPCuHksVpZoj6WhmC52MBEWkh0TCV3OdbeQAs6z3m6j81XcTduhXdraO4UmyXi/ML+eAgEBSejH70/7O/RSGludBOtIWMuxSrEey4wB4F4/xpN7k4LjPQRS+EtUBg9DSb3vB5y/hGwbDqwx7gGfN/yP/ssc9j3G8syTevv3fxH4ver9kg5tvltTJ8/gOzV7nF2sK1+Nb8legL4fLwHvgThf4sB9dAgNc7R8dYNgYwivkLUrrAy9WJsu7OsXLhg3NpEb9PcbKH7yFwrAzQjRPguVxg7bC02g41v0c2i1UXTb0D0/1KW5Mdg7HYZEw4uUXRhJCLWZbOcBCWdmiB+hwKSaq+aO49C5aX2UAyIsUPmG0OgqY91H/Y09KP3O6jN4j7ADrIIrdMwcyx15UJDZMwADCHHesnzeelDah7w3H6q+/heOA4nzbWhZaYQEDK4/aouRDINdXMmCmt9NNYjUA50BAI2dPpqetjaqq3gaPSAXsMvzNvRs="
|
||||
},
|
||||
skip_cleanup: true,
|
||||
on: {
|
||||
|
|
|
@ -62,7 +62,9 @@ srv.start(argv.port, argv.hostname)
|
|||
.catch((err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
let errorMessage = `Port ${argv.port} is already in use.`
|
||||
const pkgAppPath = require('pkg-up').sync('.')
|
||||
const pkgAppPath = require('find-up').sync('package.json', {
|
||||
cwd: dir
|
||||
})
|
||||
const appPackage = JSON.parse(readFileSync(pkgAppPath, 'utf8'))
|
||||
const nextScript = Object.entries(appPackage.scripts).find(scriptLine => scriptLine[1] === 'next')
|
||||
if (nextScript) errorMessage += `\nUse \`npm run ${nextScript[0]} -- -p <some other port>\`.`
|
||||
|
|
|
@ -7,6 +7,7 @@ import App from '../lib/app'
|
|||
import { loadGetInitialProps, getURL } from '../lib/utils'
|
||||
import PageLoader from '../lib/page-loader'
|
||||
import * as asset from '../lib/asset'
|
||||
import * as envConfig from '../lib/runtime-config'
|
||||
|
||||
// Polyfill Promise globally
|
||||
// This is needed because Webpack2's dynamic loading(common chunks) code
|
||||
|
@ -26,7 +27,8 @@ const {
|
|||
query,
|
||||
buildId,
|
||||
chunks,
|
||||
assetPrefix
|
||||
assetPrefix,
|
||||
runtimeConfig
|
||||
},
|
||||
location
|
||||
} = window
|
||||
|
@ -36,6 +38,11 @@ const {
|
|||
__webpack_public_path__ = `${assetPrefix}/_next/webpack/` //eslint-disable-line
|
||||
// Initialize next/asset with the assetPrefix
|
||||
asset.setAssetPrefix(assetPrefix)
|
||||
// Initialize next/config with the environment configuration
|
||||
envConfig.setConfig({
|
||||
serverRuntimeConfig: {},
|
||||
publicRuntimeConfig: runtimeConfig
|
||||
})
|
||||
|
||||
const asPath = getURL()
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?autoConnect=false'
|
||||
import 'event-source-polyfill'
|
||||
import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?autoConnect=false&overlay=false&reload=true'
|
||||
import Router from '../lib/router'
|
||||
|
||||
const {
|
||||
|
@ -9,8 +10,6 @@ const {
|
|||
|
||||
export default () => {
|
||||
webpackHotMiddlewareClient.setOptionsAndConnect({
|
||||
overlay: false,
|
||||
reload: true,
|
||||
path: `${assetPrefix}/_next/webpack-hmr`
|
||||
})
|
||||
|
||||
|
|
1
config.js
Normal file
1
config.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = require('./dist/lib/runtime-config')
|
1
constants.js
Normal file
1
constants.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = require('./dist/lib/constants')
|
25
errors/no-on-app-updated-hook.md
Normal file
25
errors/no-on-app-updated-hook.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Router.onAppUpdated is removed
|
||||
|
||||
Due to [this bug fix](https://github.com/zeit/next.js/pull/3849), we had to remove the `Router.onAppUpdated` hook. But the default functionality of this feature is still in effect.
|
||||
|
||||
We use this hook to detect a new app deployment when switching pages and act accordingly. Although there are many things you can do in this hook, it's often used to navigate the page via the server as shown below:
|
||||
|
||||
```js
|
||||
Router.onAppUpdated = function(nextRoute) {
|
||||
location.href = nextRoute
|
||||
}
|
||||
```
|
||||
|
||||
In this hook, you can't wait for a network request or a promise to get resolved. And you can't block the page navigation. So, the things you can do is limited.
|
||||
|
||||
One real use of this hook is to persist your application state to local-storage before the page navigation. For that, you can use the [`window.onbeforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload) hook instead.
|
||||
|
||||
This is code for that:
|
||||
|
||||
```js
|
||||
window.onbeforeunload = function(e) {
|
||||
// Get the application state (usually from a store like Redux)
|
||||
const appState = {}
|
||||
localStorage.setItem('app-state', JSON.stringify(appState));
|
||||
};
|
||||
```
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example active-class-name active-class-name-app
|
||||
```bash
|
||||
npx create-next-app --example active-class-name active-class-name-app
|
||||
# or
|
||||
yarn create next-app --example active-class-name active-class-name-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example basic-css basic-css-app
|
||||
```bash
|
||||
npx create-next-app --example basic-css basic-css-app
|
||||
# or
|
||||
yarn create next-app --example basic-css basic-css-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
99
examples/custom-server-actionhero/README.md
Normal file
99
examples/custom-server-actionhero/README.md
Normal file
|
@ -0,0 +1,99 @@
|
|||
# Running Next.JS and React /inside/ of ActionHero
|
||||
|
||||
This server will render dynamic next.js/react pages on some routes, and normal ActionHero API requests on others.
|
||||
This configuration works with both Next and ActionHero hot reloading of code.
|
||||
|
||||
A more detailed example showcasing how to use fetch and web sockets to interact with your API can be found here: https://github.com/actionhero/next-in-actionhero
|
||||
|
||||
## To install:
|
||||
(assuming you have [node](http://nodejs.org/) and NPM installed)
|
||||
|
||||
`npm install`
|
||||
|
||||
## To Run:
|
||||
`npm start`
|
||||
|
||||
|
||||
## How does this work?
|
||||
|
||||
1. Create an initializer to load next.js and create a handler that can extract the normal node `req` and `res` from the connection
|
||||
|
||||
```js
|
||||
// initializers/next.js
|
||||
|
||||
const {Initializer, api} = require('actionhero')
|
||||
const next = require('next')
|
||||
|
||||
module.exports = class NextInitializer extends Initializer {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'next'
|
||||
}
|
||||
|
||||
async initialize () {
|
||||
api.next = {
|
||||
render: async (connection) => {
|
||||
if (connection.type !== 'web') { throw new Error('Connections for NEXT apps must be of type "web"') }
|
||||
const req = connection.rawConnection.req
|
||||
const res = connection.rawConnection.res
|
||||
return api.next.handle(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
api.next.dev = (api.env === 'development')
|
||||
if (api.next.dev) { api.log('Running next in development mode...') }
|
||||
|
||||
api.next.app = next({dev: api.next.dev})
|
||||
api.next.handle = api.next.app.getRequestHandler()
|
||||
await api.next.app.prepare()
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await api.next.app.close()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Create an action which will run the above `api.next.render(connection)`. Note that we will not be relying on ActionHero to respond to the client's request in this case, and leave that up to next (via: `data.toRender = false`)
|
||||
|
||||
```js
|
||||
// actions/next.js
|
||||
|
||||
const {Action, api} = require('actionhero')
|
||||
|
||||
module.exports = class CreateChatRoom extends Action {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'render'
|
||||
this.description = 'I render the next.js react website'
|
||||
}
|
||||
|
||||
async run (data) {
|
||||
data.toRender = false
|
||||
return api.next.render(data.connection)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
3. Tell ActionHero to use the api rather than the file server as the top-level route in `api.config.servers.web.rootEndpointType = 'api'`. This will allows "/" to listen to API requests. Also update `api.config.general.paths.public = [ path.join(__dirname, '/../static') ]`. In this configuration, the next 'static' renderer will take priority over the ActionHero 'public file' api. Note that any static assets (CSS, fonts, etc) will need to be in "./static" rather than "./public".
|
||||
|
||||
Note that this is where the websocket server, if you enable it, will place the `ActionheroWebsocketClient` libraray.
|
||||
|
||||
4. Configure a wild-card route at the lowest priority of your GET handler to catch all web requests that aren't caught by other actions:
|
||||
|
||||
```js
|
||||
// config/routes.js
|
||||
|
||||
exports['default'] = {
|
||||
routes: (api) => {
|
||||
return {
|
||||
get: [
|
||||
{ path: '/time', action: 'time' },
|
||||
|
||||
{ path: '/', matchTrailingPathParts: true, action: 'render' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
15
examples/custom-server-actionhero/actions/render.js
Normal file
15
examples/custom-server-actionhero/actions/render.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
'use strict'
|
||||
const {Action, api} = require('actionhero')
|
||||
|
||||
module.exports = class CreateChatRoom extends Action {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'render'
|
||||
this.description = 'I render the next.js react website'
|
||||
}
|
||||
|
||||
async run (data) {
|
||||
data.toRender = false
|
||||
return api.next.render(data.connection)
|
||||
}
|
||||
}
|
97
examples/custom-server-actionhero/config/api.js
Normal file
97
examples/custom-server-actionhero/config/api.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
exports['default'] = {
|
||||
general: (api) => {
|
||||
const packageJSON = require(api.projectRoot + path.sep + 'package.json')
|
||||
|
||||
return {
|
||||
apiVersion: packageJSON.version,
|
||||
serverName: packageJSON.name,
|
||||
// id can be set here, or it will be generated dynamically.
|
||||
// Be sure that every server you run has a unique ID (which will happen when generated dynamically)
|
||||
// id: 'myActionHeroServer',
|
||||
// A unique token to your application that servers will use to authenticate to each other
|
||||
serverToken: 'change-me',
|
||||
// the redis prefix for actionhero's cache objects
|
||||
cachePrefix: 'actionhero:cache:',
|
||||
// the redis prefix for actionhero's cache/lock objects
|
||||
lockPrefix: 'actionhero:lock:',
|
||||
// how long will a lock last before it exipres (ms)?
|
||||
lockDuration: 1000 * 10, // 10 seconds
|
||||
// Watch for changes in actions and tasks, and reload/restart them on the fly
|
||||
developmentMode: true,
|
||||
// How many pending actions can a single connection be working on
|
||||
simultaneousActions: 5,
|
||||
// allow connections to be created without remoteIp and remotePort (they will be set to 0)
|
||||
enforceConnectionProperties: true,
|
||||
// disables the whitelisting of client params
|
||||
disableParamScrubbing: false,
|
||||
// params you would like hidden from any logs
|
||||
filteredParams: [],
|
||||
// values that signify missing params
|
||||
missingParamChecks: [null, '', undefined],
|
||||
// The default filetype to server when a user requests a directory
|
||||
directoryFileType: 'index.html',
|
||||
// What log-level should we use for file requests?
|
||||
fileRequestLogLevel: 'info',
|
||||
// The default priority level given to middleware of all types (action, connection, say, and task)
|
||||
defaultMiddlewarePriority: 100,
|
||||
// Which channel to use on redis pub/sub for RPC communication
|
||||
channel: 'actionhero',
|
||||
// How long to wait for an RPC call before considering it a failure
|
||||
rpcTimeout: 5000,
|
||||
// should CLI methods and help include internal ActionHero CLI methods?
|
||||
cliIncludeInternal: true,
|
||||
// configuration for your actionhero project structure
|
||||
paths: {
|
||||
'action': [path.join(__dirname, '/../actions')],
|
||||
'task': [path.join(__dirname, '/../tasks')],
|
||||
'public': [path.join(__dirname, '/../static')],
|
||||
'pid': [path.join(__dirname, '/../pids')],
|
||||
'log': [path.join(__dirname, '/../log')],
|
||||
'server': [path.join(__dirname, '/../servers')],
|
||||
'cli': [path.join(__dirname, '/../bin')],
|
||||
'initializer': [path.join(__dirname, '/../initializers')],
|
||||
'plugin': [path.join(__dirname, '/../node_modules')],
|
||||
'locale': [path.join(__dirname, '/../locales')]
|
||||
},
|
||||
// hash containing chat rooms you wish to be created at server boot
|
||||
startingChatRooms: {
|
||||
// format is {roomName: {authKey, authValue}}
|
||||
// 'secureRoom': {authorized: true},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
general: (api) => {
|
||||
return {
|
||||
id: 'test-server-' + process.pid,
|
||||
serverToken: 'serverToken-' + process.pid,
|
||||
developmentMode: true,
|
||||
startingChatRooms: {
|
||||
'defaultRoom': {},
|
||||
'otherRoom': {}
|
||||
},
|
||||
paths: {
|
||||
'locale': [
|
||||
// require('os').tmpdir() + require('path').sep + 'locales',
|
||||
path.join(__dirname, '/../locales')
|
||||
]
|
||||
},
|
||||
rpcTimeout: 3000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.production = {
|
||||
general: (api) => {
|
||||
return {
|
||||
fileRequestLogLevel: 'debug',
|
||||
developmentMode: false
|
||||
}
|
||||
}
|
||||
}
|
148
examples/custom-server-actionhero/config/errors.js
Normal file
148
examples/custom-server-actionhero/config/errors.js
Normal file
|
@ -0,0 +1,148 @@
|
|||
'use strict'
|
||||
|
||||
// error messages can be strings of objects
|
||||
exports['default'] = {
|
||||
errors: (api) => {
|
||||
return {
|
||||
'_toExpand': false,
|
||||
|
||||
// ///////////////
|
||||
// SERIALIZERS //
|
||||
// ///////////////
|
||||
|
||||
serializers: {
|
||||
servers: {
|
||||
web: (error) => {
|
||||
if (error.message) {
|
||||
return String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
},
|
||||
websocket: (error) => {
|
||||
if (error.message) {
|
||||
return String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
},
|
||||
socket: (error) => {
|
||||
if (error.message) {
|
||||
return String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
},
|
||||
specHelper: (error) => {
|
||||
if (error.message) {
|
||||
return 'Error: ' + String(error.message)
|
||||
} else {
|
||||
return error
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ///////////
|
||||
// ACTIONS //
|
||||
// ///////////
|
||||
|
||||
// When a params for an action is invalid
|
||||
invalidParams: (data, validationErrors) => {
|
||||
if (validationErrors.length >= 0) { return validationErrors[0] }
|
||||
return data.connection.localize('actionhero.errors.invalidParams')
|
||||
},
|
||||
|
||||
// When a required param for an action is not provided
|
||||
missingParams: (data, missingParams) => {
|
||||
return data.connection.localize(['actionhero.errors.missingParams', {param: missingParams[0]}])
|
||||
},
|
||||
|
||||
// user requested an unknown action
|
||||
unknownAction: (data) => {
|
||||
return data.connection.localize('actionhero.errors.unknownAction')
|
||||
},
|
||||
|
||||
// action not useable by this client/server type
|
||||
unsupportedServerType: (data) => {
|
||||
return data.connection.localize(['actionhero.errors.unsupportedServerType', {type: data.connection.type}])
|
||||
},
|
||||
|
||||
// action failed because server is mid-shutdown
|
||||
serverShuttingDown: (data) => {
|
||||
return data.connection.localize('actionhero.errors.serverShuttingDown')
|
||||
},
|
||||
|
||||
// action failed because this client already has too many pending acitons
|
||||
// limit defined in api.config.general.simultaneousActions
|
||||
tooManyPendingActions: (data) => {
|
||||
return data.connection.localize('actionhero.errors.tooManyPendingActions')
|
||||
},
|
||||
|
||||
dataLengthTooLarge: (maxLength, receivedLength) => {
|
||||
return api.i18n.localize(['actionhero.errors.dataLengthTooLarge', {maxLength: maxLength, receivedLength: receivedLength}])
|
||||
},
|
||||
|
||||
// ///////////////
|
||||
// FILE SERVER //
|
||||
// ///////////////
|
||||
|
||||
// The body message to accompany 404 (file not found) errors regarding flat files
|
||||
// You may want to load in the contnet of 404.html or similar
|
||||
fileNotFound: (connection) => {
|
||||
return connection.localize(['actionhero.errors.fileNotFound'])
|
||||
},
|
||||
|
||||
// user didn't request a file
|
||||
fileNotProvided: (connection) => {
|
||||
return connection.localize('actionhero.errors.fileNotProvided')
|
||||
},
|
||||
|
||||
// something went wrong trying to read the file
|
||||
fileReadError: (connection, error) => {
|
||||
return connection.localize(['actionhero.errors.fileReadError', {error: String(error)}])
|
||||
},
|
||||
|
||||
// ///////////////
|
||||
// CONNECTIONS //
|
||||
// ///////////////
|
||||
|
||||
verbNotFound: (connection, verb) => {
|
||||
return connection.localize(['actionhero.errors.verbNotFound', {verb: verb}])
|
||||
},
|
||||
|
||||
verbNotAllowed: (connection, verb) => {
|
||||
return connection.localize(['actionhero.errors.verbNotAllowed', {verb: verb}])
|
||||
},
|
||||
|
||||
connectionRoomAndMessage: (connection) => {
|
||||
return connection.localize('actionhero.errors.connectionRoomAndMessage')
|
||||
},
|
||||
|
||||
connectionNotInRoom: (connection, room) => {
|
||||
return connection.localize(['actionhero.errors.connectionNotInRoom', {room: room}])
|
||||
},
|
||||
|
||||
connectionAlreadyInRoom: (connection, room) => {
|
||||
return connection.localize(['actionhero.errors.connectionAlreadyInRoom', {room: room}])
|
||||
},
|
||||
|
||||
connectionRoomHasBeenDeleted: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomHasBeenDeleted')
|
||||
},
|
||||
|
||||
connectionRoomNotExist: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomNotExist')
|
||||
},
|
||||
|
||||
connectionRoomExists: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomExists')
|
||||
},
|
||||
|
||||
connectionRoomRequired: (room) => {
|
||||
return api.i18n.localize('actionhero.errors.connectionRoomRequired')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
36
examples/custom-server-actionhero/config/i18n.js
Normal file
36
examples/custom-server-actionhero/config/i18n.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
exports['default'] = {
|
||||
i18n: (api) => {
|
||||
return {
|
||||
// visit https://github.com/mashpie/i18n-node to see all configuration options
|
||||
// locale path can be configired from within ./config/api.js
|
||||
locales: ['en'],
|
||||
|
||||
// how would you like your lanaguages to fall back if a translation string is missing?
|
||||
fallbacks: {
|
||||
// 'es': 'en'
|
||||
},
|
||||
|
||||
// configure i18n to allow for object-style key lookup
|
||||
objectNotation: true,
|
||||
|
||||
// should actionhero append any missing translations to the locale file?
|
||||
updateFiles: true,
|
||||
|
||||
// this will configure logging and error messages in the log(s)
|
||||
defaultLocale: 'en',
|
||||
|
||||
// the name of the method by which to determine the connection's locale
|
||||
// by default, every request will be in the 'en' locale
|
||||
// this method will be called witin the localiazation middleware on all requests
|
||||
determineConnectionLocale: 'api.i18n.determineConnectionLocale'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
i18n: (api) => {
|
||||
return {
|
||||
updateFiles: true
|
||||
}
|
||||
}
|
||||
}
|
60
examples/custom-server-actionhero/config/logger.js
Normal file
60
examples/custom-server-actionhero/config/logger.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const cluster = require('cluster')
|
||||
|
||||
exports['default'] = {
|
||||
logger: (api) => {
|
||||
let logger = {transports: []}
|
||||
|
||||
// console logger
|
||||
if (cluster.isMaster) {
|
||||
logger.transports.push(function (api, winston) {
|
||||
return new (winston.transports.Console)({
|
||||
colorize: true,
|
||||
level: 'info',
|
||||
timestamp: function () { return api.id + ' @ ' + new Date().toISOString() }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// file logger
|
||||
logger.transports.push(function (api, winston) {
|
||||
if (api.config.general.paths.log.length === 1) {
|
||||
const logDirectory = api.config.general.paths.log[0]
|
||||
try {
|
||||
fs.mkdirSync(logDirectory)
|
||||
} catch (e) {
|
||||
if (e.code !== 'EEXIST') {
|
||||
throw (new Error('Cannot create log directory @ ' + logDirectory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new (winston.transports.File)({
|
||||
filename: api.config.general.paths.log[0] + '/' + api.pids.title + '.log',
|
||||
level: 'info',
|
||||
timestamp: function () { return api.id + ' @ ' + new Date().toISOString() }
|
||||
})
|
||||
})
|
||||
|
||||
// the maximum length of param to log (we will truncate)
|
||||
logger.maxLogStringLength = 100
|
||||
|
||||
// you can optionally set custom log levels
|
||||
// logger.levels = {good: 0, bad: 1};
|
||||
|
||||
// you can optionally set custom log colors
|
||||
// logger.colors = {good: 'blue', bad: 'red'};
|
||||
|
||||
return logger
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
logger: (api) => {
|
||||
return {
|
||||
transports: null
|
||||
}
|
||||
}
|
||||
}
|
27
examples/custom-server-actionhero/config/plugins.js
Normal file
27
examples/custom-server-actionhero/config/plugins.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
exports['default'] = {
|
||||
plugins: (api) => {
|
||||
/*
|
||||
If you want to use plugins in your application, include them here:
|
||||
|
||||
return {
|
||||
'myPlugin': { path: __dirname + '/../node_modules/myPlugin' }
|
||||
}
|
||||
|
||||
You can also toggle on or off sections of a plugin to include (default true for all sections):
|
||||
|
||||
return {
|
||||
'myPlugin': {
|
||||
path: __dirname + '/../node_modules/myPlugin',
|
||||
actions: true,
|
||||
tasks: true,
|
||||
initializers: true,
|
||||
servers: true,
|
||||
public: true,
|
||||
cli: true
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return {}
|
||||
}
|
||||
}
|
49
examples/custom-server-actionhero/config/redis.js
Normal file
49
examples/custom-server-actionhero/config/redis.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
let host = process.env.REDIS_HOST || '127.0.0.1'
|
||||
let port = process.env.REDIS_PORT || 6379
|
||||
let db = process.env.REDIS_DB || 0
|
||||
let password = process.env.REDIS_PASSWORD || null
|
||||
const maxBackoff = 1000
|
||||
|
||||
if (process.env.REDIS_URL) {
|
||||
password = process.env.REDIS_URL.match(/redis:\/\/.*:(.*)@.*:\d*$/i)[1]
|
||||
host = process.env.REDIS_URL.match(/redis:\/\/.*:.*@(.*):\d*$/i)[1]
|
||||
port = parseInt(process.env.REDIS_URL.match(/redis:\/\/.*:.*@.*:(\d*)$/i)[1])
|
||||
}
|
||||
|
||||
exports['default'] = {
|
||||
redis: (api) => {
|
||||
// konstructor: The redis client constructor method. All redis methods must be promises
|
||||
// args: The arguments to pass to the constructor
|
||||
// buildNew: is it `new konstructor()` or just `konstructor()`?
|
||||
|
||||
function retryStrategy (times) {
|
||||
if (times === 1) {
|
||||
const error = 'Unable to connect to Redis - please check your Redis config!'
|
||||
if (process.env.NODE_ENV === 'test') { console.error(error) } else { api.log(error, 'error') }
|
||||
return 5000
|
||||
}
|
||||
return Math.min(times * 50, maxBackoff)
|
||||
}
|
||||
|
||||
return {
|
||||
enabled: true,
|
||||
|
||||
'_toExpand': false,
|
||||
client: {
|
||||
konstructor: require('ioredis'),
|
||||
args: [{ port: port, host: host, password: password, db: db, retryStrategy: retryStrategy }],
|
||||
buildNew: true
|
||||
},
|
||||
subscriber: {
|
||||
konstructor: require('ioredis'),
|
||||
args: [{ port: port, host: host, password: password, db: db, retryStrategy: retryStrategy }],
|
||||
buildNew: true
|
||||
},
|
||||
tasks: {
|
||||
konstructor: require('ioredis'),
|
||||
args: [{ port: port, host: host, password: password, db: db, retryStrategy: retryStrategy }],
|
||||
buildNew: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
examples/custom-server-actionhero/config/routes.js
Normal file
9
examples/custom-server-actionhero/config/routes.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
exports['default'] = {
|
||||
routes: (api) => {
|
||||
return {
|
||||
get: [
|
||||
{ path: '/', matchTrailingPathParts: true, action: 'render' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
37
examples/custom-server-actionhero/config/servers/socket.js
Normal file
37
examples/custom-server-actionhero/config/servers/socket.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
'use strict'
|
||||
|
||||
exports['default'] = {
|
||||
servers: {
|
||||
socket: (api) => {
|
||||
return {
|
||||
enabled: (process.env.ENABLE_TCP_SERVER !== undefined),
|
||||
// TCP or TLS?
|
||||
secure: false,
|
||||
// Passed to tls.createServer if secure=true. Should contain SSL certificates
|
||||
serverOptions: {},
|
||||
// Port or Socket
|
||||
port: 5000,
|
||||
// Which IP to listen on (use 0.0.0.0 for all)
|
||||
bindIP: '0.0.0.0',
|
||||
// Enable TCP KeepAlive pings on each connection?
|
||||
setKeepAlive: false,
|
||||
// Delimiter string for incoming messages
|
||||
delimiter: '\n',
|
||||
// Maximum incoming message string length in Bytes (use 0 for Infinite)
|
||||
maxDataLength: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
servers: {
|
||||
socket: (api) => {
|
||||
return {
|
||||
enabled: true,
|
||||
port: 1001 + (process.pid % 64535),
|
||||
secure: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
121
examples/custom-server-actionhero/config/servers/web.js
Normal file
121
examples/custom-server-actionhero/config/servers/web.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
|
||||
exports['default'] = {
|
||||
servers: {
|
||||
web: (api) => {
|
||||
return {
|
||||
enabled: true,
|
||||
// HTTP or HTTPS?
|
||||
secure: false,
|
||||
// Passed to https.createServer if secure=true. Should contain SSL certificates
|
||||
serverOptions: {},
|
||||
// Should we redirect all traffic to the first host in this array if hte request header doesn't match?
|
||||
// i.e.: [ 'https://www.site.com' ]
|
||||
allowedRequestHosts: process.env.ALLOWED_HOSTS ? process.env.ALLOWED_HOSTS.split(',') : [],
|
||||
// Port or Socket Path
|
||||
port: process.env.PORT || 8080,
|
||||
// Which IP to listen on (use '0.0.0.0' for all; '::' for all on ipv4 and ipv6)
|
||||
// Set to `null` when listening to socket
|
||||
bindIP: '0.0.0.0',
|
||||
// Any additional headers you want actionhero to respond with
|
||||
httpHeaders: {
|
||||
'X-Powered-By': api.config.general.serverName,
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS, TRACE',
|
||||
'Access-Control-Allow-Headers': 'Content-Type'
|
||||
},
|
||||
// Route that actions will be served from; secondary route against this route will be treated as actions,
|
||||
// IE: /api/?action=test == /api/test/
|
||||
urlPathForActions: 'api',
|
||||
// Route that static files will be served from;
|
||||
// path (relative to your project root) to serve static content from
|
||||
// set to `null` to disable the file server entirely
|
||||
urlPathForFiles: 'public',
|
||||
// When visiting the root URL, should visitors see 'api' or 'file'?
|
||||
// Visitors can always visit /api and /public as normal
|
||||
rootEndpointType: 'api',
|
||||
// simple routing also adds an 'all' route which matches /api/:action for all actions
|
||||
simpleRouting: true,
|
||||
// queryRouting allows an action to be defined via a URL param, ie: /api?action=:action
|
||||
queryRouting: true,
|
||||
// The cache or (if etags are enabled) next-revalidation time to be returned for all flat files served from /public; defined in seconds
|
||||
flatFileCacheDuration: 60,
|
||||
// Add an etag header to requested flat files which acts as fingerprint that changes when the file is updated;
|
||||
// Client will revalidate the fingerprint at latest after flatFileCacheDuration and reload it if the etag (and therfore the file) changed
|
||||
// or continue to use the cached file if it's still valid
|
||||
enableEtag: true,
|
||||
// should we save the un-parsed HTTP POST/PUT payload to connection.rawConnection.params.rawBody?
|
||||
saveRawBody: false,
|
||||
// How many times should we try to boot the server?
|
||||
// This might happen if the port is in use by another process or the socketfile is claimed
|
||||
bootAttempts: 1,
|
||||
// Settings for determining the id of an http(s) request (browser-fingerprint)
|
||||
fingerprintOptions: {
|
||||
cookieKey: 'sessionID',
|
||||
toSetCookie: true,
|
||||
onlyStaticElements: false,
|
||||
settings: {
|
||||
path: '/',
|
||||
expires: 3600000
|
||||
}
|
||||
},
|
||||
// Options to be applied to incoming file uploads.
|
||||
// More options and details at https://github.com/felixge/node-formidable
|
||||
formOptions: {
|
||||
uploadDir: os.tmpdir(),
|
||||
keepExtensions: false,
|
||||
maxFieldsSize: 1024 * 1024 * 100
|
||||
},
|
||||
// Should we pad JSON responses with whitespace to make them more human-readable?
|
||||
// set to null to disable
|
||||
padding: 2,
|
||||
// Options to configure metadata in responses
|
||||
metadataOptions: {
|
||||
serverInformation: true,
|
||||
requesterInformation: true
|
||||
},
|
||||
// When true, returnErrorCodes will modify the response header for http(s) clients if connection.error is not null.
|
||||
// You can also set connection.rawConnection.responseHttpCode to specify a code per request.
|
||||
returnErrorCodes: true,
|
||||
// should this node server attempt to gzip responses if the client can accept them?
|
||||
// this will slow down the performance of actionhero, and if you need this funcionality, it is recommended that you do this upstream with nginx or your load balancer
|
||||
compress: false,
|
||||
// options to pass to the query parser
|
||||
// learn more about the options @ https://github.com/hapijs/qs
|
||||
queryParseOptions: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.production = {
|
||||
servers: {
|
||||
web: (api) => {
|
||||
return {
|
||||
padding: null,
|
||||
metadataOptions: {
|
||||
serverInformation: false,
|
||||
requesterInformation: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
servers: {
|
||||
web: (api) => {
|
||||
return {
|
||||
secure: false,
|
||||
port: process.env.PORT || 1000 + (process.pid % 64535),
|
||||
matchExtensionMime: true,
|
||||
metadataOptions: {
|
||||
serverInformation: true,
|
||||
requesterInformation: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
'use strict'
|
||||
|
||||
// Note that to use the websocket server, you also need the web server enabled
|
||||
|
||||
exports['default'] = {
|
||||
servers: {
|
||||
websocket: (api) => {
|
||||
return {
|
||||
enabled: true,
|
||||
// you can pass a FQDN (string) here or 'window.location.origin'
|
||||
clientUrl: 'window.location.origin',
|
||||
// Directory to render client-side JS.
|
||||
// Path should start with "/" and will be built starting from api.config..general.paths.public
|
||||
clientJsPath: 'javascript/',
|
||||
// the name of the client-side JS file to render. Both `.js` and `.min.js` versions will be created
|
||||
// do not include the file exension
|
||||
// set to `undefined` to not render the client-side JS on boot
|
||||
clientJsName: 'ActionheroWebsocketClient',
|
||||
// should the server signal clients to not reconnect when the server is shutdown/reboot
|
||||
destroyClientsOnShutdown: false,
|
||||
|
||||
// websocket Server Options:
|
||||
server: {
|
||||
// authorization: null,
|
||||
// pathname: '/primus',
|
||||
// parser: 'JSON',
|
||||
// transformer: 'websockets',
|
||||
// plugin: {},
|
||||
// timeout: 35000,
|
||||
// origins: '*',
|
||||
// methods: ['GET','HEAD','PUT','POST','DELETE','OPTIONS'],
|
||||
// credentials: true,
|
||||
// maxAge: '30 days',
|
||||
// exposed: false,
|
||||
},
|
||||
|
||||
// websocket Client Options:
|
||||
client: {
|
||||
apiPath: '/api' // the api base endpoint on your actionhero server
|
||||
// reconnect: {},
|
||||
// timeout: 10000,
|
||||
// ping: 25000,
|
||||
// pong: 10000,
|
||||
// strategy: "online",
|
||||
// manual: false,
|
||||
// websockets: true,
|
||||
// network: true,
|
||||
// transport: {},
|
||||
// queueSize: Infinity,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports['test'] = {
|
||||
servers: {
|
||||
websocket: (api) => {
|
||||
return { clientUrl: null }
|
||||
}
|
||||
}
|
||||
}
|
61
examples/custom-server-actionhero/config/tasks.js
Normal file
61
examples/custom-server-actionhero/config/tasks.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
exports['default'] = {
|
||||
tasks: (api) => {
|
||||
return {
|
||||
// Should this node run a scheduler to promote delayed tasks?
|
||||
scheduler: false,
|
||||
// what queues should the taskProcessors work?
|
||||
queues: ['*'],
|
||||
// Logging levels of task workers
|
||||
workerLogging: {
|
||||
failure: 'error', // task failure
|
||||
success: 'info', // task success
|
||||
start: 'info',
|
||||
end: 'info',
|
||||
cleaning_worker: 'info',
|
||||
poll: 'debug',
|
||||
job: 'debug',
|
||||
pause: 'debug',
|
||||
internalError: 'error',
|
||||
multiWorkerAction: 'debug'
|
||||
},
|
||||
// Logging levels of the task scheduler
|
||||
schedulerLogging: {
|
||||
start: 'info',
|
||||
end: 'info',
|
||||
poll: 'debug',
|
||||
enqueue: 'debug',
|
||||
reEnqueue: 'debug',
|
||||
working_timestamp: 'debug',
|
||||
transferred_job: 'debug'
|
||||
},
|
||||
// how long to sleep between jobs / scheduler checks
|
||||
timeout: 5000,
|
||||
// at minimum, how many parallel taskProcessors should this node spawn?
|
||||
// (have number > 0 to enable, and < 1 to disable)
|
||||
minTaskProcessors: 0,
|
||||
// at maximum, how many parallel taskProcessors should this node spawn?
|
||||
maxTaskProcessors: 0,
|
||||
// how often should we check the event loop to spawn more taskProcessors?
|
||||
checkTimeout: 500,
|
||||
// how many ms would constitue an event loop delay to halt taskProcessors spawning?
|
||||
maxEventLoopDelay: 5,
|
||||
// When we kill off a taskProcessor, should we disconnect that local redis connection?
|
||||
toDisconnectProcessors: true,
|
||||
// Customize Resque primitives, replace null with required replacement.
|
||||
resque_overrides: {
|
||||
queue: null,
|
||||
multiWorker: null,
|
||||
scheduler: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = {
|
||||
tasks: (api) => {
|
||||
return {
|
||||
timeout: 100,
|
||||
checkTimeout: 50
|
||||
}
|
||||
}
|
||||
}
|
32
examples/custom-server-actionhero/initializers/next.js
Normal file
32
examples/custom-server-actionhero/initializers/next.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
'use strict'
|
||||
const {Initializer, api} = require('actionhero')
|
||||
const next = require('next')
|
||||
|
||||
module.exports = class NextInitializer extends Initializer {
|
||||
constructor () {
|
||||
super()
|
||||
this.name = 'next'
|
||||
}
|
||||
|
||||
async initialize () {
|
||||
api.next = {
|
||||
render: async (connection) => {
|
||||
if (connection.type !== 'web') { throw new Error('Connections for NEXT apps must be of type "web"') }
|
||||
const req = connection.rawConnection.req
|
||||
const res = connection.rawConnection.res
|
||||
return api.next.handle(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
api.next.dev = (api.env === 'development')
|
||||
if (api.next.dev) { api.log('Running next in development mode...') }
|
||||
|
||||
api.next.app = next({dev: api.next.dev})
|
||||
api.next.handle = api.next.app.getRequestHandler()
|
||||
await api.next.app.prepare()
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await api.next.app.close()
|
||||
}
|
||||
}
|
34
examples/custom-server-actionhero/locales/en.json
Normal file
34
examples/custom-server-actionhero/locales/en.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"actionhero": {
|
||||
"welcomeMessage": "Hello! Welcome to the actionhero api",
|
||||
"goodbyeMessage": "Bye!",
|
||||
"cache": {
|
||||
"objectNotFound": "Object not found",
|
||||
"objectLocked": "Object locked",
|
||||
"objectExpired": "Object expired"
|
||||
},
|
||||
"errors": {
|
||||
"invalidParams": "validation error",
|
||||
"missingParams": "{{param}} is a required parameter for this action",
|
||||
"unknownAction": "unknown action or invalid apiVersion",
|
||||
"unsupportedServerType": "this action does not support the {{type}} connection type",
|
||||
"serverShuttingDown": "the server is shutting down",
|
||||
"tooManyPendingActions": "you have too many pending requests",
|
||||
"dataLengthTooLarge": "data length is too big ({{maxLength}} received/{{receivedLength}} max)",
|
||||
"fileNotFound": "That file is not found",
|
||||
"fileNotProvided": "file is a required param to send a file",
|
||||
"fileReadError": "error reading file: {{error}}",
|
||||
"verbNotFound": "I do not know know to perform this verb ({{verb}})",
|
||||
"verbNotAllowed": "verb not found or not allowed ({{verb}})",
|
||||
"connectionRoomAndMessage": "both room and message are required",
|
||||
"connectionNotInRoom": "connection not in this room ({{room}})",
|
||||
"connectionAlreadyInRoom": "connection already in this room ({{room}})",
|
||||
"connectionRoomHasBeenDeleted": "this room has been deleted",
|
||||
"connectionRoomNotExist": "room does not exist",
|
||||
"connectionRoomExists": "room exists",
|
||||
"connectionRoomRequired": "a room is required"
|
||||
}
|
||||
},
|
||||
"Your random number is {{number}}": "Your random number is {{number}}",
|
||||
"Node Healthy": "Node Healthy"
|
||||
}
|
17
examples/custom-server-actionhero/package.json
Normal file
17
examples/custom-server-actionhero/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"actionhero": "^18.1.2",
|
||||
"ioredis": "^3.2.2",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"next": "^5.0.1-canary.9",
|
||||
"react": "^16.1.1",
|
||||
"react-dom": "^16.1.1",
|
||||
"ws": "^4.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "actionhero start"
|
||||
}
|
||||
}
|
1
examples/custom-server-actionhero/pages/a.js
Normal file
1
examples/custom-server-actionhero/pages/a.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default () => <div>a</div>
|
1
examples/custom-server-actionhero/pages/b.js
Normal file
1
examples/custom-server-actionhero/pages/b.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default () => <div>b</div>
|
9
examples/custom-server-actionhero/pages/index.js
Normal file
9
examples/custom-server-actionhero/pages/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default () => (
|
||||
<ul>
|
||||
<li><Link href='/b' as='/a'><a>a</a></Link></li>
|
||||
<li><Link href='/a' as='/b'><a>b</a></Link></li>
|
||||
</ul>
|
||||
)
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example custom-server-express custom-server-express-app
|
||||
```bash
|
||||
npx create-next-app --example custom-server-express custom-server-express-app
|
||||
# or
|
||||
yarn create next-app --example custom-server-express custom-server-express-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example custom-server-fastify custom-server-fastify-app
|
||||
```bash
|
||||
npx create-next-app --example custom-server-fastify custom-server-fastify-app
|
||||
# or
|
||||
yarn create next-app --example custom-server-fastify custom-server-fastify-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example custom-server-hapi custom-server-hapi-app
|
||||
```bash
|
||||
npx create-next-app --example custom-server-hapi custom-server-hapi-app
|
||||
# or
|
||||
yarn create next-app --example custom-server-hapi custom-server-hapi-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example custom-server-koa custom-server-koa-app
|
||||
```bash
|
||||
npx create-next-app --example custom-server-koa custom-server-koa-app
|
||||
# or
|
||||
yarn create next-app --example custom-server-koa custom-server-koa-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -33,8 +33,7 @@ app.prepare()
|
|||
})
|
||||
|
||||
server.use(router.routes())
|
||||
server.listen(port, (err) => {
|
||||
if (err) throw err
|
||||
server.listen(port, () => {
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example custom-server-micro custom-server-micro-app
|
||||
```bash
|
||||
npx create-next-app --example custom-server-micro custom-server-micro-app
|
||||
# or
|
||||
yarn create next-app --example custom-server-micro custom-server-micro-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example custom-server-nodemon custom-server-nodemon-app
|
||||
```bash
|
||||
npx create-next-app --example custom-server-nodemon custom-server-nodemon-app
|
||||
# or
|
||||
yarn create next-app --example custom-server-nodemon custom-server-nodemon-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
43
examples/custom-server-typescript/README.md
Normal file
43
examples/custom-server-typescript/README.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/zeit/next.js/tree/master/examples/custom-server-typescript)
|
||||
|
||||
# Custom server with TypeScript + Nodemon example
|
||||
|
||||
## How to use
|
||||
|
||||
### Using `create-next-app`
|
||||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example custom-server-typescript custom-server-typescript-app
|
||||
# or
|
||||
yarn create next-app --example custom-server-typescript custom-server-typescript-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
||||
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||
|
||||
```bash
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/custom-server-typescript
|
||||
cd custom-server-typescript
|
||||
```
|
||||
|
||||
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 example shows how you can use [TypeScript](https://typescriptlang.com) on both the server and the client while using [Nodemon](https://nodemon.io/) to live reload the server code without affecting the Next.js universal code.
|
||||
Server entry point is `server/index.ts` in development and `production-server/index.js` in production.
|
||||
The second directory should be added to `.gitignore`.
|
2
examples/custom-server-typescript/next.config.js
Normal file
2
examples/custom-server-typescript/next.config.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
const withTypescript = require('@zeit/next-typescript')
|
||||
module.exports = withTypescript()
|
6
examples/custom-server-typescript/nodemon.json
Normal file
6
examples/custom-server-typescript/nodemon.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"watch": ["server/**/*.ts"],
|
||||
"execMap": {
|
||||
"ts": "ts-node --compilerOptions '{\"module\":\"commonjs\"}'"
|
||||
}
|
||||
}
|
20
examples/custom-server-typescript/package.json
Normal file
20
examples/custom-server-typescript/package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"scripts": {
|
||||
"dev": "nodemon server/index.ts",
|
||||
"build": "next build && tsc --project tsconfig.server.json",
|
||||
"start": "NODE_ENV=production node production-server/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "latest",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/next": "^2.4.7",
|
||||
"@types/react": "^16.0.36",
|
||||
"@zeit/next-typescript": "^0.0.8",
|
||||
"nodemon": "^1.12.1",
|
||||
"ts-node": "^4.1.0",
|
||||
"typescript": "^2.7.1"
|
||||
}
|
||||
}
|
3
examples/custom-server-typescript/pages/a.tsx
Normal file
3
examples/custom-server-typescript/pages/a.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import React from 'react'
|
||||
|
||||
export default () => <div>a</div>
|
3
examples/custom-server-typescript/pages/b.tsx
Normal file
3
examples/custom-server-typescript/pages/b.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import React from 'react'
|
||||
|
||||
export default () => <div>b</div>
|
9
examples/custom-server-typescript/pages/index.tsx
Normal file
9
examples/custom-server-typescript/pages/index.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default () => (
|
||||
<ul>
|
||||
<li><Link href='/a' as='/a'><a>a</a></Link></li>
|
||||
<li><Link href='/b' as='/b'><a>b</a></Link></li>
|
||||
</ul>
|
||||
)
|
28
examples/custom-server-typescript/server/index.ts
Normal file
28
examples/custom-server-typescript/server/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { createServer } from 'http'
|
||||
import { parse } from 'url'
|
||||
import * as next from 'next'
|
||||
|
||||
const port = parseInt(process.env.PORT, 10) || 3000
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({ dev })
|
||||
const handle = app.getRequestHandler()
|
||||
|
||||
app.prepare()
|
||||
.then(() => {
|
||||
createServer((req, res) => {
|
||||
const parsedUrl = parse(req.url, true)
|
||||
const { pathname, query } = parsedUrl
|
||||
|
||||
if (pathname === '/a') {
|
||||
app.render(req, res, '/a', query)
|
||||
} else if (pathname === '/b') {
|
||||
app.render(req, res, '/b', query)
|
||||
} else {
|
||||
handle(req, res, parsedUrl)
|
||||
}
|
||||
})
|
||||
.listen(port, (err) => {
|
||||
if (err) throw err
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
})
|
26
examples/custom-server-typescript/tsconfig.json
Normal file
26
examples/custom-server-typescript/tsconfig.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"jsx": "preserve",
|
||||
"allowJs": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"removeComments": false,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015",
|
||||
"es2016"
|
||||
]
|
||||
}
|
||||
}
|
8
examples/custom-server-typescript/tsconfig.server.json
Normal file
8
examples/custom-server-typescript/tsconfig.server.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "production-server/"
|
||||
},
|
||||
"include": ["server/**/*.ts"]
|
||||
}
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example custom-server custom-server-app
|
||||
```bash
|
||||
npx create-next-app --example custom-server custom-server-app
|
||||
# or
|
||||
yarn create next-app --example custom-server custom-server-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example data-fetch data-fetch-app
|
||||
```bash
|
||||
npx create-next-app --example data-fetch data-fetch-app
|
||||
# or
|
||||
yarn create next-app --example data-fetch data-fetch-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example form-handler form-handler-app
|
||||
```bash
|
||||
npx create-next-app --example form-handler form-handler-app
|
||||
# or
|
||||
yarn create next-app --example form-handler form-handler-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example head-elements head-elements-app
|
||||
```bash
|
||||
npx create-next-app --example head-elements head-elements-app
|
||||
# or
|
||||
yarn create next-app --example head-elements head-elements-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example hello-world hello-world-app
|
||||
```bash
|
||||
npx create-next-app --example hello-world hello-world-app
|
||||
# or
|
||||
yarn create next-app --example hello-world hello-world-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example layout-component layout-component-app
|
||||
```bash
|
||||
npx create-next-app --example layout-component layout-component-app
|
||||
# or
|
||||
yarn create next-app --example layout-component layout-component-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example nested-components nested-components-app
|
||||
```bash
|
||||
npx create-next-app --example nested-components nested-components-app
|
||||
# or
|
||||
yarn create next-app --example nested-components nested-components-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example page-transitions page-transitions-app
|
||||
```bash
|
||||
npx create-next-app --example page-transitions page-transitions-app
|
||||
# or
|
||||
yarn create next-app --example page-transitions page-transitions-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example parameterized-routing parameterized-routing-app
|
||||
```bash
|
||||
npx create-next-app --example parameterized-routing parameterized-routing-app
|
||||
# or
|
||||
yarn create next-app --example parameterized-routing parameterized-routing-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example progressive-render progressive-render-app
|
||||
```bash
|
||||
npx create-next-app --example progressive-render progressive-render-app
|
||||
# or
|
||||
yarn create next-app --example progressive-render progressive-render-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example root-static-files root-static-files-app
|
||||
```bash
|
||||
npx create-next-app --example root-static-files root-static-files-app
|
||||
# or
|
||||
yarn create next-app --example root-static-files root-static-files-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example shared-modules shared-modules-app
|
||||
```bash
|
||||
npx create-next-app --example shared-modules shared-modules-app
|
||||
# or
|
||||
yarn create next-app --example shared-modules shared-modules-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example ssr-caching ssr-caching-app
|
||||
```bash
|
||||
npx create-next-app --example ssr-caching ssr-caching-app
|
||||
# or
|
||||
yarn create next-app --example ssr-caching ssr-caching-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example svg-components svg-components-app
|
||||
```bash
|
||||
npx create-next-app --example svg-components svg-components-app
|
||||
# or
|
||||
yarn create next-app --example svg-components svg-components-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example using-inferno using-inferno-app
|
||||
```bash
|
||||
npx create-next-app --example using-inferno using-inferno-app
|
||||
# or
|
||||
yarn create next-app --example using-inferno using-inferno-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example using-nerv using-nerv-app
|
||||
```bash
|
||||
npx create-next-app --example using-nerv using-nerv-app
|
||||
# or
|
||||
yarn create next-app --example using-nerv using-nerv-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example using-preact using-preact-app
|
||||
```bash
|
||||
npx create-next-app --example using-preact using-preact-app
|
||||
# or
|
||||
yarn create next-app --example using-preact using-preact-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example using-router using-router-app
|
||||
```bash
|
||||
npx create-next-app --example using-router using-router-app
|
||||
# or
|
||||
yarn create next-app --example using-router using-router-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example using-with-router using-with-router-app
|
||||
```bash
|
||||
npx create-next-app --example using-with-router using-with-router-app
|
||||
# or
|
||||
yarn create next-app --example using-with-router using-with-router-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"presets": "next/babel",
|
||||
"presets": ["next/babel"],
|
||||
"plugins": [
|
||||
[
|
||||
"module-resolver",
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-absolute-imports with-absolute-imports-app
|
||||
```bash
|
||||
npx create-next-app --example with-absolute-imports with-absolute-imports-app
|
||||
# or
|
||||
yarn create next-app --example with-absolute-imports with-absolute-imports-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
@ -16,8 +17,8 @@ create-next-app --example with-absolute-imports with-absolute-imports-app
|
|||
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||
|
||||
```bash
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-absolute-import
|
||||
cd with-absolute-import
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-absolute-imports
|
||||
cd with-absolute-imports
|
||||
```
|
||||
|
||||
Install it and run:
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-algolia-react-instantsearch with-algolia-react-instantsearch-app
|
||||
```bash
|
||||
npx create-next-app --example with-algolia-react-instantsearch with-algolia-react-instantsearch-app
|
||||
# or
|
||||
yarn create next-app --example with-algolia-react-instantsearch with-algolia-react-instantsearch-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-amp with-amp-app
|
||||
```bash
|
||||
npx create-next-app --example with-amp with-amp-app
|
||||
# or
|
||||
yarn create next-app --example with-amp with-amp-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-analytics with-analytics-app
|
||||
```bash
|
||||
npx create-next-app --example with-analytics with-analytics-app
|
||||
# or
|
||||
yarn create next-app --example with-analytics with-analytics-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"import",
|
||||
{
|
||||
"libraryName": "antd",
|
||||
"style": false
|
||||
"style": "css"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-ant-design with-ant-design-app
|
||||
```bash
|
||||
npx create-next-app --example with-ant-design with-ant-design-app
|
||||
# or
|
||||
yarn create next-app --example with-ant-design with-ant-design-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -4,7 +4,7 @@ export default ({ children }) =>
|
|||
<Head>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1' />
|
||||
<meta charSet='utf-8' />
|
||||
<link rel='stylesheet' href='https://unpkg.com/antd@3/dist/antd.min.css' />
|
||||
<link rel='stylesheet' href='/_next/static/style.css' />
|
||||
</Head>
|
||||
<style jsx global>{`
|
||||
body {
|
||||
|
|
8
examples/with-ant-design/next.config.js
Normal file
8
examples/with-ant-design/next.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
const withCss = require('@zeit/next-css')
|
||||
|
||||
// fix: prevents error when .css files are required by node
|
||||
if (typeof require !== 'undefined') {
|
||||
require.extensions['.css'] = (file) => {}
|
||||
}
|
||||
|
||||
module.exports = withCss()
|
|
@ -7,7 +7,8 @@
|
|||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"antd": "^3.0.2",
|
||||
"@zeit/next-css": "^0.1.4",
|
||||
"antd": "^3.3.1",
|
||||
"babel-plugin-import": "^1.1.1",
|
||||
"next": "latest",
|
||||
"react": "^16.0.0",
|
||||
|
|
|
@ -12,7 +12,7 @@ export default () => (
|
|||
label='Input Number'
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 8 }}
|
||||
>
|
||||
>
|
||||
<InputNumber size='large' min={1} max={10} style={{ width: 100 }} defaultValue={3} name='inputNumber' />
|
||||
<a href='#'>Link</a>
|
||||
</FormItem>
|
||||
|
@ -21,7 +21,7 @@ export default () => (
|
|||
label='Switch'
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 8 }}
|
||||
>
|
||||
>
|
||||
<Switch defaultChecked name='switch' />
|
||||
</FormItem>
|
||||
|
||||
|
@ -29,7 +29,7 @@ export default () => (
|
|||
label='Slider'
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 8 }}
|
||||
>
|
||||
>
|
||||
<Slider defaultValue={70} />
|
||||
</FormItem>
|
||||
|
||||
|
@ -37,7 +37,7 @@ export default () => (
|
|||
label='Select'
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 8 }}
|
||||
>
|
||||
>
|
||||
<Select size='large' defaultValue='lucy' style={{ width: 192 }} name='select'>
|
||||
<Option value='jack'>jack</Option>
|
||||
<Option value='lucy'>lucy</Option>
|
||||
|
@ -50,19 +50,19 @@ export default () => (
|
|||
label='DatePicker'
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 8 }}
|
||||
>
|
||||
>
|
||||
<DatePicker name='startDate' />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
style={{ marginTop: 48 }}
|
||||
wrapperCol={{ span: 8, offset: 8 }}
|
||||
>
|
||||
>
|
||||
<Button size='large' type='primary' htmlType='submit'>
|
||||
OK
|
||||
</Button>
|
||||
</Button>
|
||||
<Button size='large' style={{ marginLeft: 8 }}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-antd-mobile with-antd-mobile-app
|
||||
```bash
|
||||
npx create-next-app --example with-antd-mobile with-antd-mobile-app
|
||||
# or
|
||||
yarn create next-app --example with-antd-mobile with-antd-mobile-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```
|
||||
npm i -g create-next-app
|
||||
create-next-app --example with-aphrodite with-aphrodite-app
|
||||
```bash
|
||||
npx create-next-app --example with-aphrodite with-aphrodite-app
|
||||
# or
|
||||
yarn create next-app --example with-aphrodite with-aphrodite-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
|
51
examples/with-apollo-and-redux-saga/README.md
Normal file
51
examples/with-apollo-and-redux-saga/README.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
[![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-and-redux)
|
||||
# Apollo & Redux Saga Example
|
||||
|
||||
## How to use
|
||||
|
||||
### Using `create-next-app`
|
||||
|
||||
Download [`create-next-app`](https://github.com/segmentio/create-next-app) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example with-apollo-and-redux-saga with-apollo-and-redux-saga-app
|
||||
# or
|
||||
yarn create next-app --example with-apollo-and-redux-saga with-apollo-and-redux-saga-app
|
||||
```
|
||||
|
||||
### Download manually
|
||||
|
||||
Download the example [or clone the repo](https://github.com/zeit/next.js):
|
||||
|
||||
```bash
|
||||
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-apollo-and-redux-saga
|
||||
cd with-apollo-and-redux-saga
|
||||
```
|
||||
|
||||
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
|
||||
In 2.0.0, Apollo Client severs out-of-the-box support for redux in favor of Apollo's client side state management. This example aims to be an amalgamation of the [`with-apollo`](https://github.com/zeit/next.js/tree/master/examples/with-apollo) and [`with-redux-saga`](https://github.com/zeit/next.js/tree/master/examples/with-redux-saga) examples.
|
||||
|
||||
Note that you can access the redux store like you normally would using `react-redux`'s `connect`. Here's a quick example:
|
||||
|
||||
```js
|
||||
const mapStateToProps = state => ({
|
||||
location: state.form.location,
|
||||
});
|
||||
|
||||
export default withReduxSaga(connect(mapStateToProps, null)(Index));
|
||||
```
|
||||
|
||||
`connect` must go inside `withReduxSaga` otherwise `connect` will not be able to find the store.
|
44
examples/with-apollo-and-redux-saga/components/App.js
Normal file
44
examples/with-apollo-and-redux-saga/components/App.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
export default ({ children }) => (
|
||||
<main>
|
||||
{children}
|
||||
<style jsx global>{`
|
||||
* {
|
||||
font-family: Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 25px 50px;
|
||||
}
|
||||
a {
|
||||
color: #22BAD9;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
article {
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
max-width: 650px;
|
||||
}
|
||||
button {
|
||||
align-items: center;
|
||||
background-color: #22BAD9;
|
||||
border: 0;
|
||||
color: white;
|
||||
display: flex;
|
||||
padding: 5px 7px;
|
||||
}
|
||||
button:active {
|
||||
background-color: #1B9DB7;
|
||||
transition: background-color .3s
|
||||
}
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
`}</style>
|
||||
</main>
|
||||
)
|
35
examples/with-apollo-and-redux-saga/components/Clock.js
Normal file
35
examples/with-apollo-and-redux-saga/components/Clock.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import React from 'react'
|
||||
|
||||
const pad = n => (n < 10 ? `0${n}` : n)
|
||||
|
||||
const format = t => {
|
||||
const hours = t.getUTCHours()
|
||||
const minutes = t.getUTCMinutes()
|
||||
const seconds = t.getUTCSeconds()
|
||||
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`
|
||||
}
|
||||
|
||||
function Clock ({ lastUpdate, light }) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h2>Clock:</h2>
|
||||
<div className={light ? 'light' : ''}>
|
||||
{format(new Date(lastUpdate || Date.now()))}
|
||||
<style jsx>{`
|
||||
div {
|
||||
padding: 15px;
|
||||
display: inline-block;
|
||||
color: #82fa58;
|
||||
font: 50px menlo, monaco, monospace;
|
||||
background-color: #000;
|
||||
}
|
||||
.light {
|
||||
background-color: #999;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default Clock
|
|
@ -0,0 +1,13 @@
|
|||
export default ({message}) => (
|
||||
<aside>
|
||||
{message}
|
||||
<style jsx>{`
|
||||
aside {
|
||||
padding: 1.5em;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
background-color: red;
|
||||
}
|
||||
`}</style>
|
||||
</aside>
|
||||
)
|
31
examples/with-apollo-and-redux-saga/components/Header.js
Normal file
31
examples/with-apollo-and-redux-saga/components/Header.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import Link from 'next/link'
|
||||
import { withRouter } from 'next/router'
|
||||
|
||||
const Header = ({ router: { pathname } }) => (
|
||||
<header>
|
||||
<Link prefetch href='/'>
|
||||
<a className={pathname === '/' ? 'is-active' : ''}>Home</a>
|
||||
</Link>
|
||||
<Link prefetch href='/about'>
|
||||
<a className={pathname === '/about' ? 'is-active' : ''}>About</a>
|
||||
</Link>
|
||||
<Link prefetch href='/blog'>
|
||||
<a className={pathname === '/blog' ? 'is-active' : ''}>Blog</a>
|
||||
</Link>
|
||||
<style jsx>{`
|
||||
header {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
a {
|
||||
font-size: 14px;
|
||||
margin-right: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.is-active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`}</style>
|
||||
</header>
|
||||
)
|
||||
|
||||
export default withRouter(Header)
|
19
examples/with-apollo-and-redux-saga/components/Page.js
Normal file
19
examples/with-apollo-and-redux-saga/components/Page.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import PageCount from './PageCount'
|
||||
import Clock from './Clock'
|
||||
import Placeholder from './Placeholder'
|
||||
|
||||
function Page ({ clock, placeholder, linkTo, title }) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1>{title}</h1>
|
||||
<Clock lastUpdate={clock.lastUpdate} light={clock.light} />
|
||||
<PageCount />
|
||||
<Placeholder placeholder={placeholder} />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => state)(Page)
|
34
examples/with-apollo-and-redux-saga/components/PageCount.js
Normal file
34
examples/with-apollo-and-redux-saga/components/PageCount.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { countIncrease, countDecrease } from '../lib/count/actions'
|
||||
|
||||
class PageCount extends Component {
|
||||
increase = () => {
|
||||
this.props.dispatch(countIncrease())
|
||||
}
|
||||
decrease = () => {
|
||||
this.props.dispatch(countDecrease())
|
||||
}
|
||||
|
||||
render () {
|
||||
const { count } = this.props
|
||||
return (
|
||||
<div>
|
||||
<style jsx>{`
|
||||
div {
|
||||
padding: 0 0 20px 0;
|
||||
}
|
||||
`}</style>
|
||||
<h1>
|
||||
PageCount: <span>{count}</span>
|
||||
</h1>
|
||||
<button onClick={this.increase}>Increase Count (+1)</button>
|
||||
<button onClick={this.decrease}>Decrease Count (-1)</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({ count }) => ({ count })
|
||||
export default connect(mapStateToProps)(PageCount)
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react'
|
||||
|
||||
export default ({placeholder}) => (
|
||||
<React.Fragment>
|
||||
<h2>JSON:</h2>
|
||||
{placeholder.data && (
|
||||
<pre>
|
||||
<code>{JSON.stringify(placeholder.data, null, 2)}</code>
|
||||
</pre>
|
||||
)}
|
||||
{placeholder.error && (
|
||||
<p style={{ color: 'red' }}>Error: {placeholder.error.message}</p>
|
||||
)}
|
||||
<style jsx>{`
|
||||
aside {
|
||||
font-size: 14px;
|
||||
}
|
||||
`}</style>
|
||||
</React.Fragment>
|
||||
)
|
62
examples/with-apollo-and-redux-saga/components/Post.js
Normal file
62
examples/with-apollo-and-redux-saga/components/Post.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import React from 'react'
|
||||
import { withRouter } from 'next/router'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import ErrorMessage from './ErrorMessage'
|
||||
import PostVoteUp from './PostVoteUp'
|
||||
import PostVoteDown from './PostVoteDown'
|
||||
import PostVoteCount from './PostVoteCount'
|
||||
|
||||
function Post ({ id, data: { error, Post } }) {
|
||||
if (error) return <ErrorMessage message='Error loading blog post.' />
|
||||
if (Post) {
|
||||
return (
|
||||
<section>
|
||||
<div key={Post.id}>
|
||||
<h1>{Post.title}</h1>
|
||||
<p>ID: {Post.id}<br />URL: {Post.url}</p>
|
||||
<span>
|
||||
<PostVoteUp id={Post.id} votes={Post.votes} />
|
||||
<PostVoteCount votes={Post.votes} />
|
||||
<PostVoteDown id={Post.id} votes={Post.votes} />
|
||||
</span>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
span {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
return <div>Loading</div>
|
||||
}
|
||||
|
||||
const post = gql`
|
||||
query post($id: ID!) {
|
||||
Post(id: $id) {
|
||||
id
|
||||
title
|
||||
votes
|
||||
url
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// The `graphql` wrapper executes a GraphQL query and makes the results
|
||||
// available on the `data` prop of the wrapped component (PostList)
|
||||
const ComponentWithMutation = graphql(post, {
|
||||
options: ({ router: { query } }) => ({
|
||||
variables: {
|
||||
id: query.id
|
||||
}
|
||||
}),
|
||||
props: ({ data }) => ({
|
||||
data
|
||||
})
|
||||
})(Post)
|
||||
|
||||
export default withRouter(ComponentWithMutation)
|
143
examples/with-apollo-and-redux-saga/components/PostList.js
Normal file
143
examples/with-apollo-and-redux-saga/components/PostList.js
Normal file
|
@ -0,0 +1,143 @@
|
|||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import { Router } from '../routes'
|
||||
import ErrorMessage from './ErrorMessage'
|
||||
import PostVoteUp from './PostVoteUp'
|
||||
import PostVoteDown from './PostVoteDown'
|
||||
import PostVoteCount from './PostVoteCount'
|
||||
|
||||
const POSTS_PER_PAGE = 10
|
||||
|
||||
function handleClick (event, id) {
|
||||
event.preventDefault()
|
||||
// With route name and params
|
||||
// Router.pushRoute('blog/entry', { id: id })
|
||||
// With route URL
|
||||
Router.pushRoute(`/blog/${id}`)
|
||||
}
|
||||
|
||||
function PostList ({
|
||||
data: { loading, error, allPosts, _allPostsMeta },
|
||||
loadMorePosts
|
||||
}) {
|
||||
if (error) return <ErrorMessage message='Error loading posts.' />
|
||||
if (allPosts && allPosts.length) {
|
||||
const areMorePosts = allPosts.length < _allPostsMeta.count
|
||||
return (
|
||||
<section>
|
||||
<ul>
|
||||
{allPosts.map((post, index) => (
|
||||
<li key={post.id}>
|
||||
<div>
|
||||
<span>{index + 1}. </span>
|
||||
<a
|
||||
href={`/blog/${post.id}`}
|
||||
onClick={(event) => handleClick(event, post.id)}
|
||||
>
|
||||
{post.title}
|
||||
</a>
|
||||
<PostVoteUp id={post.id} votes={post.votes} />
|
||||
<PostVoteCount votes={post.votes} />
|
||||
<PostVoteDown id={post.id} votes={post.votes} />
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{areMorePosts ? (
|
||||
<button onClick={() => loadMorePosts()}>
|
||||
{' '}
|
||||
{loading ? 'Loading...' : 'Show More'}{' '}
|
||||
</button>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<style jsx>{`
|
||||
section {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
li {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
a {
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
text-decoration: none;
|
||||
padding-bottom: 0;
|
||||
border: 0;
|
||||
}
|
||||
span {
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
button:before {
|
||||
align-self: center;
|
||||
border-style: solid;
|
||||
border-width: 6px 4px 0 4px;
|
||||
border-color: #ffffff transparent transparent transparent;
|
||||
content: '';
|
||||
height: 0;
|
||||
margin-right: 5px;
|
||||
width: 0;
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
return <div>Loading</div>
|
||||
}
|
||||
|
||||
export const allPosts = gql`
|
||||
query allPosts($first: Int!, $skip: Int!) {
|
||||
allPosts(orderBy: createdAt_DESC, first: $first, skip: $skip) {
|
||||
id
|
||||
title
|
||||
votes
|
||||
url
|
||||
createdAt
|
||||
}
|
||||
_allPostsMeta {
|
||||
count
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const allPostsQueryVars = {
|
||||
skip: 0,
|
||||
first: POSTS_PER_PAGE
|
||||
}
|
||||
|
||||
// The `graphql` wrapper executes a GraphQL query and makes the results
|
||||
// available on the `data` prop of the wrapped component (PostList)
|
||||
export default graphql(allPosts, {
|
||||
options: {
|
||||
variables: allPostsQueryVars
|
||||
},
|
||||
props: ({ data }) => ({
|
||||
data,
|
||||
loadMorePosts: () => {
|
||||
return data.fetchMore({
|
||||
variables: {
|
||||
skip: data.allPosts.length
|
||||
},
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
if (!fetchMoreResult) {
|
||||
return previousResult
|
||||
}
|
||||
return Object.assign({}, previousResult, {
|
||||
// Append the new posts results to the old one
|
||||
allPosts: [...previousResult.allPosts, ...fetchMoreResult.allPosts]
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})(PostList)
|
|
@ -0,0 +1,30 @@
|
|||
export default ({id, votes, onClickHandler, className}) => (
|
||||
<button className={className} onClick={() => onClickHandler()}>
|
||||
<style jsx>{`
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: 1px solid #e4e4e4;
|
||||
color: #000;
|
||||
}
|
||||
button:active {
|
||||
background-color: transparent;
|
||||
}
|
||||
button:before {
|
||||
align-self: center;
|
||||
border-color: transparent transparent #000000 transparent;
|
||||
border-style: solid;
|
||||
border-width: 0 4px 6px 4px;
|
||||
content: '';
|
||||
height: 0;
|
||||
margin-right: 0px;
|
||||
width: 0;
|
||||
}
|
||||
.downvote {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.upvote {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
`}</style>
|
||||
</button>
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
export default ({votes}) => (
|
||||
<span>
|
||||
{votes}
|
||||
<style jsx>{`
|
||||
span {
|
||||
padding: 0.5em 0.5em;
|
||||
font-size: 14px;
|
||||
color: inherit;
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
)
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import PostVoteButton from './PostVoteButton'
|
||||
|
||||
function PostVoteDown ({ downvote, votes, id }) {
|
||||
return (
|
||||
<PostVoteButton
|
||||
id={id}
|
||||
votes={votes}
|
||||
className='downvote'
|
||||
onClickHandler={() => downvote(id, votes - 1)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const downvotePost = gql`
|
||||
mutation updatePost($id: ID!, $votes: Int) {
|
||||
updatePost(id: $id, votes: $votes) {
|
||||
id
|
||||
__typename
|
||||
votes
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default graphql(downvotePost, {
|
||||
props: ({ ownProps, mutate }) => ({
|
||||
downvote: (id, votes) =>
|
||||
mutate({
|
||||
variables: { id, votes },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updatePost: {
|
||||
__typename: 'Post',
|
||||
id: ownProps.id,
|
||||
votes: ownProps.votes - 1
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})(PostVoteDown)
|
42
examples/with-apollo-and-redux-saga/components/PostVoteUp.js
Normal file
42
examples/with-apollo-and-redux-saga/components/PostVoteUp.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import React from 'react'
|
||||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import PostVoteButton from './PostVoteButton'
|
||||
|
||||
function PostVoteUp ({ upvote, votes, id }) {
|
||||
return (
|
||||
<PostVoteButton
|
||||
id={id}
|
||||
votes={votes}
|
||||
className='upvote'
|
||||
onClickHandler={() => upvote(id, votes + 1)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const upvotePost = gql`
|
||||
mutation updatePost($id: ID!, $votes: Int) {
|
||||
updatePost(id: $id, votes: $votes) {
|
||||
id
|
||||
__typename
|
||||
votes
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default graphql(upvotePost, {
|
||||
props: ({ ownProps, mutate }) => ({
|
||||
upvote: (id, votes) =>
|
||||
mutate({
|
||||
variables: { id, votes },
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
updatePost: {
|
||||
__typename: 'Post',
|
||||
id: ownProps.id,
|
||||
votes: ownProps.votes + 1
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})(PostVoteUp)
|
71
examples/with-apollo-and-redux-saga/components/Submit.js
Normal file
71
examples/with-apollo-and-redux-saga/components/Submit.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { graphql } from 'react-apollo'
|
||||
import gql from 'graphql-tag'
|
||||
import { allPosts, allPostsQueryVars } from './PostList'
|
||||
|
||||
function Submit ({ createPost }) {
|
||||
function handleSubmit (event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = event.target
|
||||
|
||||
const formData = new window.FormData(form)
|
||||
createPost(formData.get('title'), formData.get('url'))
|
||||
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<h1>Submit</h1>
|
||||
<input placeholder='title' name='title' type='text' required />
|
||||
<input placeholder='url' name='url' type='url' required />
|
||||
<button type='submit'>Submit</button>
|
||||
<style jsx>{`
|
||||
form {
|
||||
border-bottom: 1px solid #ececec;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
input {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
`}</style>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
const createPost = gql`
|
||||
mutation createPost($title: String!, $url: String!) {
|
||||
createPost(title: $title, url: $url) {
|
||||
id
|
||||
title
|
||||
votes
|
||||
url
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default graphql(createPost, {
|
||||
props: ({ mutate }) => ({
|
||||
createPost: (title, url) =>
|
||||
mutate({
|
||||
variables: { title, url },
|
||||
update: (proxy, { data: { createPost } }) => {
|
||||
const data = proxy.readQuery({
|
||||
query: allPosts,
|
||||
variables: allPostsQueryVars
|
||||
})
|
||||
proxy.writeQuery({
|
||||
query: allPosts,
|
||||
data: {
|
||||
...data,
|
||||
allPosts: [createPost, ...data.allPosts]
|
||||
},
|
||||
variables: allPostsQueryVars
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})(Submit)
|
20
examples/with-apollo-and-redux-saga/lib/clock/actions.js
Normal file
20
examples/with-apollo-and-redux-saga/lib/clock/actions.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
export const actionTypes = {
|
||||
START_CLOCK: 'START_CLOCK',
|
||||
TICK_CLOCK: 'TICK_CLOCK'
|
||||
}
|
||||
|
||||
export function startClock(isServer=true) {
|
||||
return {
|
||||
type: actionTypes.START_CLOCK,
|
||||
light: isServer,
|
||||
lastUpdate: null
|
||||
}
|
||||
}
|
||||
|
||||
export function tickClock(isServer) {
|
||||
return {
|
||||
type: actionTypes.TICK_CLOCK,
|
||||
light: !isServer,
|
||||
lastUpdate: Date.now()
|
||||
}
|
||||
}
|
21
examples/with-apollo-and-redux-saga/lib/clock/reducers.js
Normal file
21
examples/with-apollo-and-redux-saga/lib/clock/reducers.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { actionTypes } from './actions'
|
||||
|
||||
export const initialState = {
|
||||
lastUpdate: 0,
|
||||
light: false
|
||||
}
|
||||
|
||||
function reducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actionTypes.TICK_CLOCK:
|
||||
return {
|
||||
...state,
|
||||
...{ lastUpdate: action.lastUpdate, light: !!action.light }
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default reducer
|
18
examples/with-apollo-and-redux-saga/lib/clock/sagas.js
Normal file
18
examples/with-apollo-and-redux-saga/lib/clock/sagas.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { delay } from 'redux-saga'
|
||||
import { call, put, take } from 'redux-saga/effects'
|
||||
import es6promise from 'es6-promise'
|
||||
import 'isomorphic-unfetch'
|
||||
|
||||
import { actionTypes, tickClock } from './actions'
|
||||
|
||||
es6promise.polyfill()
|
||||
|
||||
function* runClockSaga() {
|
||||
yield take(actionTypes.START_CLOCK)
|
||||
while (true) {
|
||||
yield put(tickClock(false))
|
||||
yield call(delay, 800)
|
||||
}
|
||||
}
|
||||
|
||||
export default call(runClockSaga)
|
12
examples/with-apollo-and-redux-saga/lib/count/actions.js
Normal file
12
examples/with-apollo-and-redux-saga/lib/count/actions.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
export const actionTypes = {
|
||||
COUNT_INCREASE: 'COUNT_INCREASE',
|
||||
COUNT_DECREASE: 'COUNT_DECREASE'
|
||||
}
|
||||
|
||||
export function countIncrease() {
|
||||
return { type: actionTypes.COUNT_INCREASE }
|
||||
}
|
||||
|
||||
export function countDecrease() {
|
||||
return { type: actionTypes.COUNT_DECREASE }
|
||||
}
|
18
examples/with-apollo-and-redux-saga/lib/count/reducers.js
Normal file
18
examples/with-apollo-and-redux-saga/lib/count/reducers.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { actionTypes } from './actions'
|
||||
|
||||
const initialState = 0
|
||||
|
||||
function reducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case actionTypes.COUNT_INCREASE:
|
||||
return state + 1
|
||||
|
||||
case actionTypes.COUNT_DECREASE:
|
||||
return state - 1
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default reducer
|
38
examples/with-apollo-and-redux-saga/lib/initApollo.js
Normal file
38
examples/with-apollo-and-redux-saga/lib/initApollo.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { ApolloClient } from 'apollo-client'
|
||||
import { HttpLink } from 'apollo-link-http'
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
|
||||
let apolloClient = null
|
||||
|
||||
// Polyfill fetch() on the server (used by apollo-client)
|
||||
if (!process.browser) {
|
||||
global.fetch = fetch
|
||||
}
|
||||
|
||||
function create(initialState) {
|
||||
return new ApolloClient({
|
||||
connectToDevTools: process.browser,
|
||||
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
|
||||
link: new HttpLink({
|
||||
uri: 'https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn', // Server URL (must be absolute)
|
||||
credentials: 'same-origin' // Additional fetch() options like `credentials` or `headers`
|
||||
}),
|
||||
cache: new InMemoryCache().restore(initialState || {})
|
||||
})
|
||||
}
|
||||
|
||||
export default function initApollo(initialState) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Reuse client on the client-side
|
||||
if (!apolloClient) {
|
||||
apolloClient = create(initialState)
|
||||
}
|
||||
|
||||
return apolloClient
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue