Skip to content

Commit

Permalink
Merge branch 'canary' into canary
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] committed Nov 6, 2022
2 parents f1b65ee + 98106ba commit ce88e9a
Show file tree
Hide file tree
Showing 20 changed files with 120 additions and 90 deletions.
4 changes: 4 additions & 0 deletions examples/custom-server-express/README.md
Expand Up @@ -2,8 +2,12 @@

Most of the time the default Next.js server will be enough but there are times you'll want to run your own server to integrate into an existing application. Next.js provides [a custom server api](https://nextjs.org/docs/advanced-features/custom-server).

The example shows a server that serves the component living in `pages/a.ts` when the route `/b` is requested and `pages/b.ts` when the route `/a` is accessed. This is obviously a non-standard routing strategy. You can see how this custom routing is being made inside `server.ts`.

Because the Next.js server is a Node.js module you can combine it with any other part of the Node.js ecosystem. In this case we are using express.

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.

## Deploy your own

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) or preview live with [StackBlitz](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/custom-server-express?runScript=dev)
Expand Down
5 changes: 5 additions & 0 deletions examples/custom-server-express/nodemon.json
@@ -0,0 +1,5 @@
{
"watch": ["server.ts"],
"exec": "ts-node --project tsconfig.server.json server.ts",
"ext": "js ts"
}
19 changes: 14 additions & 5 deletions examples/custom-server-express/package.json
@@ -1,15 +1,24 @@
{
"private": true,
"scripts": {
"dev": "node server.js",
"build": "next build",
"start": "cross-env NODE_ENV=production node server.js"
"dev": "nodemon",
"build": "next build && tsc --project tsconfig.server.json",
"start": "cross-env NODE_ENV=production node dist/server.js"
},
"dependencies": {
"cross-env": "^7.0.2",
"express": "^4.17.1",
"cross-env": "^7.0.3",
"express": "^4.18.2",
"next": "latest",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/express": "^4.17.14",
"@types/node": "^18.11.7",
"@types/react": "^18.0.24",
"@types/react-dom": "^18.0.8",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"typescript": "^4.8.4"
}
}
File renamed without changes.
File renamed without changes.
@@ -1,15 +1,16 @@
const express = require('express')
const next = require('next')
import type { Request, Response } from 'express'
import express from 'express'
import next from 'next'

const port = parseInt(process.env.PORT, 10) || 3000
const port = parseInt(process.env.PORT || '3000', 10)
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
const server = express()

server.all('*', (req, res) => {
server.all('*', (req: Request, res: Response) => {
return handle(req, res)
})

Expand Down
20 changes: 20 additions & 0 deletions examples/custom-server-express/tsconfig.json
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
12 changes: 12 additions & 0 deletions examples/custom-server-express/tsconfig.server.json
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "dist",
"lib": ["es2019"],
"target": "es2019",
"isolatedModules": false,
"noEmit": false
},
"include": ["server.ts"]
}
4 changes: 4 additions & 0 deletions packages/next/build/utils.ts
Expand Up @@ -59,6 +59,10 @@ if (process.env.NEXT_PREBUNDLED_REACT) {
overrideBuiltInReactPackages()
}

// expose AsyncLocalStorage on global for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(global as any).AsyncLocalStorage = AsyncLocalStorage

export type ROUTER_TYPE = 'pages' | 'app'

const RESERVED_PAGE = /^\/(_app|_error|_document|api(\/|$))/
Expand Down
6 changes: 4 additions & 2 deletions packages/next/client/components/request-async-storage.ts
Expand Up @@ -13,6 +13,8 @@ export interface RequestStore {
export let requestAsyncStorage: AsyncLocalStorage<RequestStore> | RequestStore =
{} as any

if (process.env.NEXT_RUNTIME !== 'edge' && typeof window === 'undefined') {
requestAsyncStorage = new (require('async_hooks').AsyncLocalStorage)()
// @ts-expect-error we provide this on global in
// the edge and node runtime
if (global.AsyncLocalStorage) {
requestAsyncStorage = new (global as any).AsyncLocalStorage()
}
23 changes: 19 additions & 4 deletions packages/next/client/components/static-generation-async-storage.ts
@@ -1,4 +1,19 @@
export {
staticGenerationAsyncStorage,
StaticGenerationStore,
} from './static-generation-async-storage/storage.js'
import type { AsyncLocalStorage } from 'async_hooks'

export interface StaticGenerationStore {
inUse?: boolean
pathname?: string
revalidate?: number
fetchRevalidate?: number
isStaticGeneration?: boolean
}

export let staticGenerationAsyncStorage:
| AsyncLocalStorage<StaticGenerationStore>
| StaticGenerationStore = {}

// @ts-expect-error we provide this on global in
// the edge and node runtime
if (global.AsyncLocalStorage) {
staticGenerationAsyncStorage = new (global as any).AsyncLocalStorage()
}

This file was deleted.

This file was deleted.

This file was deleted.

4 changes: 4 additions & 0 deletions packages/next/export/worker.ts
Expand Up @@ -100,6 +100,10 @@ interface RenderOpts {
supportsDynamicHTML?: boolean
}

// expose AsyncLocalStorage on global for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(global as any).AsyncLocalStorage = AsyncLocalStorage

export default async function exportPage({
parentSpanId,
path,
Expand Down
4 changes: 4 additions & 0 deletions packages/next/server/dev/static-paths-worker.ts
Expand Up @@ -22,6 +22,10 @@ if (process.env.NEXT_PREBUNDLED_REACT) {

let workerWasUsed = false

// expose AsyncLocalStorage on global for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(global as any).AsyncLocalStorage = AsyncLocalStorage

// we call getStaticPaths in a separate process to ensure
// side-effects aren't relied on in dev that will break
// during a production build
Expand Down
13 changes: 6 additions & 7 deletions packages/next/server/next-server.ts
Expand Up @@ -256,11 +256,10 @@ export default class NextNodeServer extends BaseServer {
}).catch(() => {})
}

if (this.nextConfig.experimental.appDir) {
// expose AsyncLocalStorage on global for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(global as any).AsyncLocalStorage = AsyncLocalStorage
}
// expose AsyncLocalStorage on global for react usage
const { AsyncLocalStorage } = require('async_hooks')
;(global as any).AsyncLocalStorage = AsyncLocalStorage

// ensure options are set when loadConfig isn't called
setHttpClientAndAgentOptions(this.nextConfig)
}
Expand Down Expand Up @@ -1775,7 +1774,7 @@ export default class NextNodeServer extends BaseServer {
page: page,
body: getRequestMeta(params.request, '__NEXT_CLONABLE_BODY'),
},
useCache: false,
useCache: !this.renderOpts.dev,
onWarning: params.onWarning,
})

Expand Down Expand Up @@ -2132,7 +2131,7 @@ export default class NextNodeServer extends BaseServer {
},
body: getRequestMeta(params.req, '__NEXT_CLONABLE_BODY'),
},
useCache: false,
useCache: !this.renderOpts.dev,
onWarning: params.onWarning,
})

Expand Down
22 changes: 18 additions & 4 deletions packages/next/server/web/sandbox/context.ts
Expand Up @@ -14,6 +14,7 @@ import { validateURL } from '../utils'
import { pick } from '../../../lib/pick'
import { fetchInlineAsset } from './fetch-inline-assets'
import type { EdgeFunctionDefinition } from '../../../build/webpack/plugins/middleware-plugin'
import { UnwrapPromise } from '../../../lib/coalesced-function'

const WEBPACK_HASH_REGEX =
/__webpack_require__\.h = function\(\) \{ return "[0-9a-f]+"; \}/g
Expand Down Expand Up @@ -319,8 +320,15 @@ interface ModuleContextOptions {
edgeFunctionEntry: Pick<EdgeFunctionDefinition, 'assets' | 'wasm'>
}

const pendingModuleCaches = new Map<string, Promise<ModuleContext>>()

function getModuleContextShared(options: ModuleContextOptions) {
return createModuleContext(options)
let deferredModuleContext = pendingModuleCaches.get(options.moduleName)
if (!deferredModuleContext) {
deferredModuleContext = createModuleContext(options)
pendingModuleCaches.set(options.moduleName, deferredModuleContext)
}
return deferredModuleContext
}

/**
Expand All @@ -335,9 +343,15 @@ export async function getModuleContext(options: ModuleContextOptions): Promise<{
paths: Map<string, string>
warnedEvals: Set<string>
}> {
let moduleContext = options.useCache
? moduleContexts.get(options.moduleName)
: await getModuleContextShared(options)
let moduleContext:
| UnwrapPromise<ReturnType<typeof getModuleContextShared>>
| undefined

if (options.useCache) {
moduleContext =
moduleContexts.get(options.moduleName) ||
(await getModuleContextShared(options))
}

if (!moduleContext) {
moduleContext = await createModuleContext(options)
Expand Down
29 changes: 1 addition & 28 deletions packages/next/taskfile.js
Expand Up @@ -2108,11 +2108,7 @@ export async function compile(task, opts) {
],
opts
)
await task.serial([
'ncc_react_refresh_utils',
'ncc_next__react_dev_overlay',
'copy_package_json',
])
await task.serial(['ncc_react_refresh_utils', 'ncc_next__react_dev_overlay'])
}

export async function bin(task, opts) {
Expand Down Expand Up @@ -2199,29 +2195,6 @@ export async function nextbuildjest(task, opts) {
notify('Compiled build/jest files')
}

export async function copy_package_json(task, opts) {
await fs.copy(
join(
__dirname,
'client/components/static-generation-async-storage/package.json'
),
join(
__dirname,
'dist/client/components/static-generation-async-storage/package.json'
)
)
await fs.copy(
join(
__dirname,
'client/components/static-generation-async-storage/package.json'
),
join(
__dirname,
'dist/esm/client/components/static-generation-async-storage/package.json'
)
)
}

export async function client(task, opts) {
await task
.source(opts.src || 'client/**/*.+(js|ts|tsx)')
Expand Down

0 comments on commit ce88e9a

Please sign in to comment.