Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow pages to be async modules to enable top-level-await #17590

Merged
merged 21 commits into from Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintignore
Expand Up @@ -13,4 +13,5 @@ packages/next-codemod/transforms/__testfixtures__/**/*
packages/next-codemod/transforms/__tests__/**/*
packages/next-codemod/**/*.js
packages/next-codemod/**/*.d.ts
packages/next-env/**/*.d.ts
packages/next-env/**/*.d.ts
test/integration/async-modules/**
1 change: 1 addition & 0 deletions .github/workflows/build_test_deploy.yml
Expand Up @@ -113,6 +113,7 @@ jobs:
- run: yarn install --check-files
- run: node run-tests.js test/integration/production/test/index.test.js
- run: node run-tests.js test/integration/basic/test/index.test.js
- run: node run-tests.js test/integration/async-modules/test/index.test.js
- run: node run-tests.js test/integration/font-optimization/test/index.test.js
- run: node run-tests.js test/acceptance/*

Expand Down
2 changes: 1 addition & 1 deletion packages/next/build/index.ts
Expand Up @@ -531,7 +531,7 @@ export default async function build(
const serverBundle = getPagePath(page, distDir, isLikeServerless)

if (customAppGetInitialProps === undefined) {
customAppGetInitialProps = hasCustomGetInitialProps(
customAppGetInitialProps = await hasCustomGetInitialProps(
isLikeServerless
? serverBundle
: getPagePath('/_app', distDir, isLikeServerless),
Expand Down
25 changes: 13 additions & 12 deletions packages/next/build/utils.ts
Expand Up @@ -704,21 +704,21 @@ export async function isPageStatic(
}> {
try {
require('../next-server/lib/runtime-config').setConfig(runtimeEnvConfig)
const mod = require(serverBundle)
const Comp = mod.default || mod
const mod = await require(serverBundle)
const Comp = await (mod.default || mod)

if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') {
throw new Error('INVALID_DEFAULT_EXPORT')
}

const hasGetInitialProps = !!(Comp as any).getInitialProps
const hasStaticProps = !!mod.getStaticProps
const hasStaticPaths = !!mod.getStaticPaths
const hasServerProps = !!mod.getServerSideProps
const hasLegacyServerProps = !!mod.unstable_getServerProps
const hasLegacyStaticProps = !!mod.unstable_getStaticProps
const hasLegacyStaticPaths = !!mod.unstable_getStaticPaths
const hasLegacyStaticParams = !!mod.unstable_getStaticParams
const hasStaticProps = !!(await mod.getStaticProps)
Janpot marked this conversation as resolved.
Show resolved Hide resolved
const hasStaticPaths = !!(await mod.getStaticPaths)
const hasServerProps = !!(await mod.getServerSideProps)
const hasLegacyServerProps = !!(await mod.unstable_getServerProps)
const hasLegacyStaticProps = !!(await mod.unstable_getStaticProps)
const hasLegacyStaticPaths = !!(await mod.unstable_getStaticPaths)
const hasLegacyStaticParams = !!(await mod.unstable_getStaticParams)

if (hasLegacyStaticParams) {
throw new Error(
Expand Down Expand Up @@ -804,19 +804,20 @@ export async function isPageStatic(
}
}

export function hasCustomGetInitialProps(
export async function hasCustomGetInitialProps(
bundle: string,
runtimeEnvConfig: any,
checkingApp: boolean
): boolean {
): Promise<boolean> {
require('../next-server/lib/runtime-config').setConfig(runtimeEnvConfig)
let mod = require(bundle)

if (checkingApp) {
mod = mod._app || mod.default || mod
mod = (await mod._app) || mod.default || mod
} else {
mod = mod.default || mod
}
mod = await mod
return mod.getInitialProps !== mod.origGetInitialProps
}

Expand Down
50 changes: 36 additions & 14 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Expand Up @@ -339,7 +339,7 @@ const nextServerlessLoader: loader.Loader = function () {
: `{}`
}

const resolver = require('${absolutePagePath}')
const resolver = await require('${absolutePagePath}')
await apiResolver(
req,
res,
Expand Down Expand Up @@ -386,35 +386,57 @@ const nextServerlessLoader: loader.Loader = function () {
const {sendPayload} = require('next/dist/next-server/server/send-payload');
const buildManifest = require('${buildManifest}');
const reactLoadableManifest = require('${reactLoadableManifest}');
const Document = require('${absoluteDocumentPath}').default;
const Error = require('${absoluteErrorPath}').default;
const App = require('${absoluteAppPath}').default;

const appMod = require('${absoluteAppPath}')
let App = appMod.default || appMod.then && appMod.then(mod => mod.default);

${dynamicRouteImports}
${rewriteImports}

const ComponentInfo = require('${absolutePagePath}')
const compMod = require('${absolutePagePath}')

const Component = ComponentInfo.default
let Component = compMod.default || compMod.then && compMod.then(mod => mod.default)
export default Component
export const unstable_getStaticParams = ComponentInfo['unstable_getStaticParam' + 's']
export const getStaticProps = ComponentInfo['getStaticProp' + 's']
export const getStaticPaths = ComponentInfo['getStaticPath' + 's']
export const getServerSideProps = ComponentInfo['getServerSideProp' + 's']
export let getStaticProps = compMod['getStaticProp' + 's'] || compMod.then && compMod.then(mod => mod['getStaticProp' + 's'])
export let getStaticPaths = compMod['getStaticPath' + 's'] || compMod.then && compMod.then(mod => mod['getStaticPath' + 's'])
export let getServerSideProps = compMod['getServerSideProp' + 's'] || compMod.then && compMod.then(mod => mod['getServerSideProp' + 's'])

// kept for detecting legacy exports
export const unstable_getStaticProps = ComponentInfo['unstable_getStaticProp' + 's']
export const unstable_getStaticPaths = ComponentInfo['unstable_getStaticPath' + 's']
export const unstable_getServerProps = ComponentInfo['unstable_getServerProp' + 's']
export const unstable_getStaticParams = compMod['unstable_getStaticParam' + 's'] || compMod.then && compMod.then(mod => mod['unstable_getStaticParam' + 's'])
export const unstable_getStaticProps = compMod['unstable_getStaticProp' + 's'] || compMod.then && compMod.then(mod => mod['unstable_getStaticProp' + 's'])
export const unstable_getStaticPaths = compMod['unstable_getStaticPath' + 's'] || compMod.then && compMod.then(mod => mod['unstable_getStaticPath' + 's'])
export const unstable_getServerProps = compMod['unstable_getServerProp' + 's'] || compMod.then && compMod.then(mod => mod['unstable_getServerProp' + 's'])

${dynamicRouteMatcher}
${defaultRouteRegex}
${normalizeDynamicRouteParams}
${handleRewrites}

export const config = ComponentInfo['confi' + 'g'] || {}
export let config = compMod['confi' + 'g'] || (compMod.then && compMod.then(mod => mod['confi' + 'g'])) || {}
export const _app = App
export async function renderReqToHTML(req, res, renderMode, _renderOpts, _params) {
let Document
let Error
;[
getStaticProps,
getServerSideProps,
getStaticPaths,
Component,
App,
config,
{ default: Document },
{ default: Error }
] = await Promise.all([
getStaticProps,
getServerSideProps,
getStaticPaths,
Component,
App,
config,
require('${absoluteDocumentPath}'),
require('${absoluteErrorPath}')
])

const fromExport = renderMode === 'export' || renderMode === true;
const nextStartMode = renderMode === 'passthrough'

Expand Down
4 changes: 2 additions & 2 deletions packages/next/client/page-loader.ts
Expand Up @@ -340,9 +340,9 @@ export default class PageLoader {

// This method if called by the route code.
registerPage(route: string, regFn: () => any) {
const register = (styleSheets: StyleSheetTuple[]) => {
const register = async (styleSheets: StyleSheetTuple[]) => {
try {
const mod = regFn()
const mod = await regFn()
const pageData: PageCacheEntry = {
page: mod.default || mod,
mod,
Expand Down
17 changes: 12 additions & 5 deletions packages/next/next-server/server/load-components.ts
Expand Up @@ -41,20 +41,27 @@ export async function loadComponents(
): Promise<LoadComponentsReturnType> {
if (serverless) {
const Component = await requirePage(pathname, distDir, serverless)
const { getStaticProps, getStaticPaths, getServerSideProps } = Component
let { getStaticProps, getStaticPaths, getServerSideProps } = Component

getStaticProps = await getStaticProps
getStaticPaths = await getStaticPaths
getServerSideProps = await getServerSideProps
const pageConfig = (await Component.config) || {}

return {
Component,
pageConfig: Component.config || {},
pageConfig,
getStaticProps,
getStaticPaths,
getServerSideProps,
} as LoadComponentsReturnType
}

const DocumentMod = requirePage('/_document', distDir, serverless)
const AppMod = requirePage('/_app', distDir, serverless)
const ComponentMod = requirePage(pathname, distDir, serverless)
const [DocumentMod, AppMod, ComponentMod] = await Promise.all([
requirePage('/_document', distDir, serverless),
requirePage('/_app', distDir, serverless),
requirePage(pathname, distDir, serverless),
])

const [
buildManifest,
Expand Down
2 changes: 1 addition & 1 deletion packages/next/next-server/server/next-server.ts
Expand Up @@ -866,7 +866,7 @@ export default class Server {
throw err
}

const pageModule = require(builtPagePath)
const pageModule = await require(builtPagePath)
query = { ...query, ...params }

if (!this.renderOpts.dev && this._isLikeServerless) {
Expand Down
9 changes: 9 additions & 0 deletions test/integration/async-modules/next.config.js
@@ -0,0 +1,9 @@
module.exports = {
// target: 'experimental-serverless-trace',
webpack: (config, options) => {
config.experiments = {
topLevelAwait: true,
}
return config
},
}
5 changes: 5 additions & 0 deletions test/integration/async-modules/pages/404.jsx
@@ -0,0 +1,5 @@
const content = await Promise.resolve("hi y'all")

export default function Custom404() {
return <h1 id="content-404">{content}</h1>
}
5 changes: 5 additions & 0 deletions test/integration/async-modules/pages/_app.jsx
@@ -0,0 +1,5 @@
const appValue = await Promise.resolve('hello')

export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} appValue={appValue} />
}
25 changes: 25 additions & 0 deletions test/integration/async-modules/pages/_document.jsx
@@ -0,0 +1,25 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'

const docValue = await Promise.resolve('doc value')

class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps, docValue }
}

render() {
return (
<Html>
<Head />
<body>
<div id="doc-value">{this.props.docValue}</div>
<Main />
<NextScript />
</body>
</Html>
)
}
}

export default MyDocument
7 changes: 7 additions & 0 deletions test/integration/async-modules/pages/_error.jsx
@@ -0,0 +1,7 @@
const errorContent = await Promise.resolve('hello error')

function Error({ statusCode }) {
return <p id="content-error">{errorContent}</p>
}

export default Error
5 changes: 5 additions & 0 deletions test/integration/async-modules/pages/api/hello.js
@@ -0,0 +1,5 @@
const value = await Promise.resolve(42)

export default function (req, res) {
res.json({ value })
}
22 changes: 22 additions & 0 deletions test/integration/async-modules/pages/config.jsx
@@ -0,0 +1,22 @@
export const config = {
amp: true,
}

await Promise.resolve('tadaa')

export default function Config() {
const date = new Date()
return (
<div>
<amp-timeago
id="amp-timeago"
width="0"
height="15"
datetime={date.toJSON()}
layout="responsive"
>
fail
</amp-timeago>
</div>
)
}
15 changes: 15 additions & 0 deletions test/integration/async-modules/pages/gsp.jsx
@@ -0,0 +1,15 @@
const gspValue = await Promise.resolve(42)

export async function getStaticProps() {
return {
props: { gspValue },
}
}

export default function Index({ gspValue }) {
return (
<main>
<div id="gsp-value">{gspValue}</div>
</main>
)
}
15 changes: 15 additions & 0 deletions test/integration/async-modules/pages/gssp.jsx
@@ -0,0 +1,15 @@
const gsspValue = await Promise.resolve(42)

export async function getServerSideProps() {
return {
props: { gsspValue },
}
}

export default function Index({ gsspValue }) {
return (
<main>
<div id="gssp-value">{gsspValue}</div>
</main>
)
}
10 changes: 10 additions & 0 deletions test/integration/async-modules/pages/index.jsx
@@ -0,0 +1,10 @@
const value = await Promise.resolve(42)

export default function Index({ appValue }) {
return (
<main>
<div id="app-value">{appValue}</div>
<div id="page-value">{value}</div>
</main>
)
}
7 changes: 7 additions & 0 deletions test/integration/async-modules/pages/make-error.jsx
@@ -0,0 +1,7 @@
export async function getServerSideProps() {
throw new Error('BOOM')
}

export default function Page() {
return <div>hello</div>
}