1
0
Fork 0
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:
Tim Neutkens 2018-03-26 18:44:50 +02:00
commit 3c97f9664b
301 changed files with 4485 additions and 1213 deletions

View file

@ -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
View file

@ -7,8 +7,9 @@ node_modules
package-lock.json
test/node_modules
# logs
# logs & pids
*.log
pids
# coverage
.nyc_output

View file

@ -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: {

View file

@ -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>\`.`

View file

@ -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()

View file

@ -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
View file

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

1
constants.js Normal file
View file

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

View 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));
};
```

View file

@ -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

View file

@ -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

View 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' }
]
}
}
}
```

View 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)
}
}

View 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
}
}
}

View 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')
}
}
}
}

View 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
}
}
}

View 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
}
}
}

View 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 {}
}
}

View 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
}
}
}
}

View file

@ -0,0 +1,9 @@
exports['default'] = {
routes: (api) => {
return {
get: [
{ path: '/', matchTrailingPathParts: true, action: 'render' }
]
}
}
}

View 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
}
}
}
}

View 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
}
}
}
}
}

View file

@ -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 }
}
}
}

View 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
}
}
}

View 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()
}
}

View 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"
}

View 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"
}
}

View file

@ -0,0 +1 @@
export default () => <div>a</div>

View file

@ -0,0 +1 @@
export default () => <div>b</div>

View 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>
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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}`)
})
})

View file

@ -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

View file

@ -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

View 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`.

View file

@ -0,0 +1,2 @@
const withTypescript = require('@zeit/next-typescript')
module.exports = withTypescript()

View file

@ -0,0 +1,6 @@
{
"watch": ["server/**/*.ts"],
"execMap": {
"ts": "ts-node --compilerOptions '{\"module\":\"commonjs\"}'"
}
}

View 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"
}
}

View file

@ -0,0 +1,3 @@
import React from 'react'
export default () => <div>a</div>

View file

@ -0,0 +1,3 @@
import React from 'react'
export default () => <div>b</div>

View 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>
)

View 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}`)
})
})

View 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"
]
}
}

View file

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "production-server/"
},
"include": ["server/**/*.ts"]
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,5 +1,5 @@
{
"presets": "next/babel",
"presets": ["next/babel"],
"plugins": [
[
"module-resolver",

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -6,7 +6,7 @@
"import",
{
"libraryName": "antd",
"style": false
"style": "css"
}
]
]

View file

@ -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

View file

@ -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 {

View 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()

View file

@ -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",

View file

@ -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

View file

@ -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

View 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.

View 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>
)

View 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

View file

@ -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>
)

View 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)

View 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)

View 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)

View file

@ -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>
)

View 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)

View 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)

View file

@ -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>
)

View file

@ -0,0 +1,12 @@
export default ({votes}) => (
<span>
{votes}
<style jsx>{`
span {
padding: 0.5em 0.5em;
font-size: 14px;
color: inherit;
}
`}</style>
</span>
)

View file

@ -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)

View 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)

View 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)

View 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()
}
}

View 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

View 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)

View 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 }
}

View 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

View 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
}

View file

@ -0,0 +1,23 @@
export const actionTypes = {
LOAD_DATA: 'LOAD_DATA',
LOAD_DATA_SUCCESS: 'LOAD_DATA_SUCCESS',
LOAD_DATA_ERROR: 'LOAD_DATA_ERROR'
}
export function loadData() {
return { type: actionTypes.LOAD_DATA }
}
export function loadDataSuccess(data) {
return {
type: actionTypes.LOAD_DATA_SUCCESS,
data
}
}
export function loadDataError(error) {
return {
type: actionTypes.LOAD_DATA_ERROR,
error
}
}

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