Skip to content

Commit

Permalink
Merge branch 'canary' into swc-up
Browse files Browse the repository at this point in the history
  • Loading branch information
kdy1 committed Mar 1, 2022
2 parents e547b6f + 57702cb commit fe78db2
Show file tree
Hide file tree
Showing 28 changed files with 502 additions and 283 deletions.
2 changes: 1 addition & 1 deletion docs/advanced-features/react-18/streaming.md
@@ -1,7 +1,7 @@
# Streaming SSR (Alpha)

React 18 will include architectural improvements to React server-side rendering (SSR) performance. This means you can use `Suspense` in your React components in streaming SSR mode and React will render them on the server and send them through HTTP streams.
It's worth noting that another experimental feature, React Server Components, is based on streaming. You can read more about server components related streaming APIs in [`next/streaming`](docs/api-reference/next/streaming.md). However, this guide focuses on basic React 18 streaming.
It's worth noting that another experimental feature, React Server Components, is based on streaming. You can read more about server components related streaming APIs in [`next/streaming`](/docs/api-reference/next/streaming.md). However, this guide focuses on basic React 18 streaming.

## Enable Streaming SSR

Expand Down
Expand Up @@ -8,7 +8,7 @@ Next.js fails your **production build** (`next build`) when TypeScript errors ar

If you'd like Next.js to dangerously produce production code even when your application has errors, you can disable the built-in type checking step.

> Be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous.
If disabled, be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous.

Open `next.config.js` and enable the `ignoreBuildErrors` option in the `typescript` config:

Expand Down
58 changes: 35 additions & 23 deletions docs/basic-features/typescript.md
Expand Up @@ -5,14 +5,19 @@ description: Next.js supports TypeScript by default and has built-in types for p
# TypeScript

<details>
<summary><b>Examples</b></summary>
<ul>
<li><a href="https://github.com/vercel/next.js/tree/canary/examples/with-typescript">TypeScript</a></li>
</ul>
<summary><b>Version History</b></summary>

| Version | Changes |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `v12.0.0` | [SWC](https://nextjs.org/docs/advanced-features/compiler) is now used by default to compile TypeScript and TSX for faster builds. |
| `v10.2.1` | [Incremental type checking](https://www.typescriptlang.org/tsconfig#incremental) support added when enabled in your `tsconfig.json`. |

</details>

Next.js provides an integrated [TypeScript](https://www.typescriptlang.org/)
experience out of the box, similar to an IDE.
Next.js provides an integrated [TypeScript](https://www.typescriptlang.org/) experience, including zero-configuration set up and built-in types for Pages, APIs, and more.

- [Clone and deploy the TypeScript starter](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-typescript&project-name=with-typescript&repository-name=with-typescript)
- [View an example application](https://github.com/vercel/next.js/tree/canary/examples/with-typescript)

## `create-next-app` support

Expand Down Expand Up @@ -120,26 +125,11 @@ export default (req: NextApiRequest, res: NextApiResponse<Data>) => {
If you have a [custom `App`](/docs/advanced-features/custom-app.md), you can use the built-in type `AppProps` and change file name to `./pages/_app.tsx` like so:

```ts
// import App from "next/app";
import type { AppProps /*, AppContext */ } from 'next/app'
import type { AppProps } from 'next/app'

function MyApp({ Component, pageProps }: AppProps) {
export default function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

// Only uncomment this method if you have blocking data requirements for
// every single page in your application. This disables the ability to
// perform automatic static optimization, causing every page in your app to
// be server-side rendered.
//
// MyApp.getInitialProps = async (appContext: AppContext) => {
// // calls page's `getInitialProps` and fills `appProps.pageProps`
// const appProps = await App.getInitialProps(appContext);

// return { ...appProps }
// }

export default MyApp
```

## Path aliases and baseUrl
Expand Down Expand Up @@ -170,3 +160,25 @@ module.exports = nextConfig
Since `v10.2.1` Next.js supports [incremental type checking](https://www.typescriptlang.org/tsconfig#incremental) when enabled in your `tsconfig.json`, this can help speed up type checking in larger applications.

It is highly recommended to be on at least `v4.3.2` of TypeScript to experience the [best performance](https://devblogs.microsoft.com/typescript/announcing-typescript-4-3/#lazier-incremental) when leveraging this feature.

## Ignoring TypeScript Errors

Next.js fails your **production build** (`next build`) when TypeScript errors are present in your project.

If you'd like Next.js to dangerously produce production code even when your application has errors, you can disable the built-in type checking step.

If disabled, be sure you are running type checks as part of your build or deploy process, otherwise this can be very dangerous.

Open `next.config.js` and enable the `ignoreBuildErrors` option in the `typescript` config:

```js
module.exports = {
typescript: {
// !! WARN !!
// Dangerously allow production builds to successfully complete even if
// your project has type errors.
// !! WARN !!
ignoreBuildErrors: true,
},
}
```
2 changes: 1 addition & 1 deletion packages/next/bin/next.ts
Expand Up @@ -15,7 +15,7 @@ import { NON_STANDARD_NODE_ENV } from '../lib/constants'

const defaultCommand = 'dev'
export type cliCommand = (argv?: string[]) => void
const commands: { [command: string]: () => Promise<cliCommand> } = {
export const commands: { [command: string]: () => Promise<cliCommand> } = {
build: () => Promise.resolve(require('../cli/next-build').nextBuild),
start: () => Promise.resolve(require('../cli/next-start').nextStart),
export: () => Promise.resolve(require('../cli/next-export').nextExport),
Expand Down
26 changes: 10 additions & 16 deletions packages/next/build/webpack-config.ts
@@ -1,7 +1,6 @@
import ReactRefreshWebpackPlugin from 'next/dist/compiled/@next/react-refresh-utils/ReactRefreshWebpackPlugin'
import chalk from 'next/dist/compiled/chalk'
import crypto from 'crypto'
import { stringify } from 'querystring'
import { webpack } from 'next/dist/compiled/webpack/webpack'
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
import path, { join as pathJoin, relative as relativePath } from 'path'
Expand Down Expand Up @@ -1180,10 +1179,11 @@ export default async function getBaseWebpackConfig(
...codeCondition,
test: serverComponentsRegex,
use: {
loader: `next-flight-server-loader?${stringify({
loader: 'next-flight-server-loader',
options: {
client: 1,
pageExtensions: JSON.stringify(rawPageExtensions),
})}`,
pageExtensions: rawPageExtensions,
},
},
},
]
Expand All @@ -1195,22 +1195,16 @@ export default async function getBaseWebpackConfig(
? [
{
...codeCondition,
test: serverComponentsRegex,
use: {
loader: `next-flight-server-loader?${stringify({
pageExtensions: JSON.stringify(rawPageExtensions),
})}`,
},
},
{
...codeCondition,
test: clientComponentsRegex,
use: {
loader: 'next-flight-client-loader',
loader: 'next-flight-server-loader',
options: {
pageExtensions: rawPageExtensions,
},
},
},
{
test: /next[\\/](dist[\\/]client[\\/])?(link|image)/,
test: codeCondition.test,
resourceQuery: /__sc_client__/,
use: {
loader: 'next-flight-client-loader',
},
Expand Down
Expand Up @@ -94,11 +94,8 @@ export default async function transformSource(
this: any,
source: string
): Promise<string> {
const { resourcePath, resourceQuery } = this
const { resourcePath } = this

if (resourceQuery !== '?flight') return source

let url = resourcePath
const transformedSource = source
if (typeof transformedSource !== 'string') {
throw new Error('Expected source to have been transformed to a string.')
Expand All @@ -108,7 +105,7 @@ export default async function transformSource(
await parseExportNamesInto(resourcePath, transformedSource, names)

// next.js/packages/next/<component>.js
if (/[\\/]next[\\/](link|image)\.js$/.test(url)) {
if (/[\\/]next[\\/](link|image)\.js$/.test(resourcePath)) {
names.push('default')
}

Expand All @@ -122,7 +119,7 @@ export default async function transformSource(
newSrc += 'export const ' + name + ' = '
}
newSrc += '{ $$typeof: MODULE_REFERENCE, filepath: '
newSrc += JSON.stringify(url)
newSrc += JSON.stringify(resourcePath)
newSrc += ', name: '
newSrc += JSON.stringify(name)
newSrc += '};\n'
Expand Down
121 changes: 83 additions & 38 deletions packages/next/build/webpack/loaders/next-flight-server-loader.ts
Expand Up @@ -5,17 +5,19 @@ import { parse } from '../../swc'
import { getBaseSWCOptions } from '../../swc/options'
import { getRawPageExtensions } from '../../utils'

function isClientComponent(importSource: string, pageExtensions: string[]) {
return new RegExp(`\\.client(\\.(${pageExtensions.join('|')}))?`).test(
importSource
)
}
const getIsClientComponent =
(pageExtensions: string[]) => (importSource: string) => {
return new RegExp(`\\.client(\\.(${pageExtensions.join('|')}))?`).test(
importSource
)
}

function isServerComponent(importSource: string, pageExtensions: string[]) {
return new RegExp(`\\.server(\\.(${pageExtensions.join('|')}))?`).test(
importSource
)
}
const getIsServerComponent =
(pageExtensions: string[]) => (importSource: string) => {
return new RegExp(`\\.server(\\.(${pageExtensions.join('|')}))?`).test(
importSource
)
}

function isNextComponent(importSource: string) {
return (
Expand All @@ -31,21 +33,28 @@ export function isImageImport(importSource: string) {
)
}

async function parseImportsInfo(
resourcePath: string,
source: string,
imports: Array<string>,
isClientCompilation: boolean,
pageExtensions: string[]
): Promise<{
async function parseImportsInfo({
resourcePath,
source,
imports,
isClientCompilation,
isServerComponent,
isClientComponent,
}: {
resourcePath: string
source: string
imports: Array<string>
isClientCompilation: boolean
isServerComponent: (name: string) => boolean
isClientComponent: (name: string) => boolean
}): Promise<{
source: string
defaultExportName: string
}> {
const opts = getBaseSWCOptions({
filename: resourcePath,
globalWindow: isClientCompilation,
})

const ast = await parse(source, { ...opts.jsc.parser, isModule: true })
const { body } = ast
const beginPos = ast.span.start
Expand All @@ -58,29 +67,49 @@ async function parseImportsInfo(
case 'ImportDeclaration': {
const importSource = node.source.value
if (!isClientCompilation) {
// Server compilation for .server.js.
if (isServerComponent(importSource)) {
continue
}

const importDeclarations = source.substring(
lastIndex,
node.source.span.start - beginPos
)

if (
!(
isClientComponent(importSource, pageExtensions) ||
isClientComponent(importSource) ||
isNextComponent(importSource) ||
isImageImport(importSource)
)
) {
continue
if (
['react/jsx-runtime', 'react/jsx-dev-runtime'].includes(
importSource
)
) {
continue
}

// A shared component. It should be handled as a server
// component.
transformedSource += importDeclarations
transformedSource += JSON.stringify(`${importSource}?__sc_server__`)
} else {
// A client component. It should be loaded as module reference.
transformedSource += importDeclarations
transformedSource += JSON.stringify(`${importSource}?__sc_client__`)
imports.push(`require(${JSON.stringify(importSource)})`)
}
const importDeclarations = source.substring(
lastIndex,
node.source.span.start - beginPos
)
transformedSource += importDeclarations
transformedSource += JSON.stringify(`${node.source.value}?flight`)
} else {
// For the client compilation, we skip all modules imports but
// always keep client components in the bundle. All client components
// have to be imported from either server or client components.
if (
!(
isClientComponent(importSource, pageExtensions) ||
isServerComponent(importSource, pageExtensions) ||
isClientComponent(importSource) ||
isServerComponent(importSource) ||
// Special cases for Next.js APIs that are considered as client
// components:
isNextComponent(importSource) ||
Expand All @@ -89,11 +118,12 @@ async function parseImportsInfo(
) {
continue
}

imports.push(`require(${JSON.stringify(importSource)})`)
}

lastIndex = node.source.span.end - beginPos
imports.push(`require(${JSON.stringify(importSource)})`)
continue
break
}
case 'ExportDefaultDeclaration': {
const def = node.decl
Expand Down Expand Up @@ -126,28 +156,44 @@ export default async function transformSource(
this: any,
source: string
): Promise<string> {
const { client: isClientCompilation, pageExtensions: pageExtensionsJson } =
this.getOptions()
const { resourcePath } = this
const pageExtensions = JSON.parse(pageExtensionsJson)
const { client: isClientCompilation, pageExtensions } = this.getOptions()
const { resourcePath, resourceQuery } = this

if (typeof source !== 'string') {
throw new Error('Expected source to have been transformed to a string.')
}

// We currently assume that all components are shared components (unsuffixed)
// from node_modules.
if (resourcePath.includes('/node_modules/')) {
return source
}

const rawRawPageExtensions = getRawPageExtensions(pageExtensions)
const isServerComponent = getIsServerComponent(rawRawPageExtensions)
const isClientComponent = getIsClientComponent(rawRawPageExtensions)

if (!isClientCompilation) {
// We only apply the loader to server components, or shared components that
// are imported by a server component.
if (
!isServerComponent(resourcePath) &&
resourceQuery !== '?__sc_server__'
) {
return source
}
}

const imports: string[] = []
const { source: transformedSource, defaultExportName } =
await parseImportsInfo(
await parseImportsInfo({
resourcePath,
source,
imports,
isClientCompilation,
getRawPageExtensions(pageExtensions)
)
isServerComponent,
isClientComponent,
})

/**
* For .server.js files, we handle this loader differently.
Expand Down Expand Up @@ -177,6 +223,5 @@ export default async function transformSource(
}

const transformed = transformedSource + '\n' + noop + '\n' + defaultExportNoop

return transformed
}

0 comments on commit fe78db2

Please sign in to comment.