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

Implement new client-side router #37551

Merged
merged 108 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from 107 commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
fee9593
Remove comment
timneutkens Jun 7, 2022
dbcfc3c
Add current rendered layouts to history
timneutkens Jun 8, 2022
bf47abf
Comment out unused code
timneutkens Jun 8, 2022
faec101
Change rendering server-side to tree format
timneutkens Jun 8, 2022
06a6138
Remove console.log
timneutkens Jun 8, 2022
c9cd9a6
Filter what to render by flightRouterPath
timneutkens Jun 8, 2022
4b8ab1a
Throw when layout segment does not match up with tree
timneutkens Jun 9, 2022
7b14a24
Remove console.log
timneutkens Jun 9, 2022
ef60d46
Move loading to layout router and pass initial tree
timneutkens Jun 10, 2022
2919687
Pass segment tree to browser
timneutkens Jun 10, 2022
bf3d3ba
Add layout to app-rendering
timneutkens Jun 11, 2022
fc61955
Merge branch 'canary' of github.com:vercel/next.js into add/back-nav-…
timneutkens Jun 11, 2022
0e1abb2
Add loading for segments without layout
timneutkens Jun 11, 2022
ac48af5
Don't pass children to page
timneutkens Jun 11, 2022
40f6f92
Remove todo
timneutkens Jun 11, 2022
79f13c6
Fix loading segment
timneutkens Jun 13, 2022
397d1ec
Add test for page with loading
timneutkens Jun 13, 2022
ce9d26f
Add test for layout with loading
timneutkens Jun 13, 2022
06d4fcc
Add test for double loading
timneutkens Jun 13, 2022
32c8dd3
Refactor layout router
timneutkens Jun 19, 2022
35709da
Merge branch 'canary' of github.com:vercel/next.js into add/back-nav-…
timneutkens Jun 19, 2022
1764470
Disable tests that fail
timneutkens Jun 20, 2022
46e7803
Merge branch 'canary' into add/back-nav-optimization-2
timneutkens Jun 20, 2022
5bd8381
Skip failing test
timneutkens Jun 21, 2022
2f4966f
Merge branch 'canary' into add/back-nav-optimization-2
kodiakhq[bot] Jun 21, 2022
78d15ee
Support rewrites
timneutkens Jun 21, 2022
f8c0b33
Merge branch 'add/back-nav-optimization-2' of github.com:timneutkens/…
timneutkens Jun 21, 2022
26098d6
Update todo
timneutkens Jun 21, 2022
272e84b
Use same promise across renders
timneutkens Jun 21, 2022
7975986
Merge branch 'canary' into add/back-nav-optimization-2
kodiakhq[bot] Jun 21, 2022
7e6e793
Create smaller tree, add types, update tree based on data from server
timneutkens Jun 22, 2022
a2c3d4e
Add data type
timneutkens Jun 22, 2022
a896086
Start fetch eagerly for push/replace
timneutkens Jun 23, 2022
f15bc0a
WIP
timneutkens Jun 23, 2022
dc55d23
Refactor optimistic tree
timneutkens Jun 24, 2022
002d6c5
WIP
timneutkens Jun 24, 2022
e7d71cc
Refactor to useReducer
timneutkens Jun 25, 2022
7e0499c
Remove log
timneutkens Jun 25, 2022
39c19bd
Add segmentPath
timneutkens Jun 26, 2022
86e903c
Merge branch 'add/back-nav-optimization-2' of github.com:timneutkens/…
timneutkens Jun 26, 2022
07a2b59
Merge branch 'canary' of github.com:vercel/next.js into add/back-nav-…
timneutkens Jun 26, 2022
7511afb
replaceAll -> replace
huozhi Jun 27, 2022
dc67b53
fallback to chunkId when chunk name is undefined
huozhi Jun 27, 2022
fdfcb16
WIP
timneutkens Jun 27, 2022
a280d8a
Remove compiled files
timneutkens Jun 27, 2022
a099c6d
Merge branch 'add/back-nav-optimization-2' of github.com:timneutkens/…
timneutkens Jun 28, 2022
caab172
Handle server response with new path
timneutkens Jun 28, 2022
879ba2b
Put correct optimistic tree into history
timneutkens Jun 28, 2022
e0793c0
Add refetch when fetching during render
timneutkens Jun 28, 2022
f991fa4
Remove console.log
timneutkens Jun 28, 2022
53717fd
Remove todos
timneutkens Jun 28, 2022
c45475e
WIP
timneutkens Jun 29, 2022
b0a80e5
Add test for dynamic parameters in layouts
timneutkens Jun 29, 2022
7810c87
Path without dynamic params does not get `params` passed in
timneutkens Jun 29, 2022
971d415
Interpolate single-value segments
timneutkens Jun 29, 2022
2ffa0fd
WIP
timneutkens Jun 30, 2022
fbda67b
WIP
timneutkens Jun 30, 2022
87af3e4
WIP
timneutkens Jul 1, 2022
4c65b17
Merge branch 'canary' of github.com:vercel/next.js into add/back-nav-…
timneutkens Jul 1, 2022
98a5b57
WIP
timneutkens Jul 1, 2022
80ce305
WIP
timneutkens Jul 1, 2022
31c200e
Update error
timneutkens Jul 1, 2022
6c1a33b
Fix refetch
timneutkens Jul 1, 2022
63b42fa
Change page key to ''
timneutkens Jul 1, 2022
0106f1f
Ensure app/page.server.js matches correctly
ijjk Jul 1, 2022
49b1b9b
WIP
timneutkens Jul 1, 2022
4d6ff20
Merge branch 'fix/app-index' of github.com:ijjk/next.js into add/back…
timneutkens Jul 1, 2022
ad37db0
WIP
timneutkens Jul 1, 2022
fbd90d8
WIP
timneutkens Jul 1, 2022
e2a95fa
WIP
timneutkens Jul 1, 2022
388c13c
Add mutable
timneutkens Jul 1, 2022
0c3cd96
Bring back cache null
timneutkens Jul 1, 2022
fdfd0b3
WIP
timneutkens Jul 2, 2022
b444935
WIP
timneutkens Jul 2, 2022
ae171cc
WIP
timneutkens Jul 2, 2022
3fd0206
WIP
timneutkens Jul 2, 2022
36da7dc
Merge branch 'canary' of github.com:vercel/next.js into add/back-nav-…
timneutkens Jul 2, 2022
8704a7c
Fix tests and typescript
timneutkens Jul 2, 2022
64c3e96
WIP
timneutkens Jul 2, 2022
c14a7ab
Handle app -> page navigation
timneutkens Jul 3, 2022
61ed2bd
WIP
timneutkens Jul 3, 2022
c35a8f0
Add middleware
timneutkens Jul 3, 2022
1516cc3
Handle replace
timneutkens Jul 3, 2022
004d9e9
Add full page navigation between page<->app
timneutkens Jul 4, 2022
3f4ad7a
Remove auto import
timneutkens Jul 4, 2022
cd7be12
Ensure refetch is added for render case
timneutkens Jul 4, 2022
9835331
Remove todo
timneutkens Jul 4, 2022
e088028
Throw error when using getStaticProps/getServerSideProps in layout/page
timneutkens Jul 5, 2022
9c9f9d2
add todo
timneutkens Jul 5, 2022
229b2ac
Update props passed to getServerSideProps
timneutkens Jul 5, 2022
70390fb
WIP
timneutkens Jul 5, 2022
81022c6
WIP
timneutkens Jul 6, 2022
39fbd91
WIP
timneutkens Jul 6, 2022
811d012
WIP
timneutkens Jul 6, 2022
bb73172
Merge branch 'canary' of github.com:vercel/next.js into add/back-nav-…
timneutkens Jul 6, 2022
e02bee4
WIP
timneutkens Jul 6, 2022
4567a98
WIP
timneutkens Jul 6, 2022
455b5c5
Remove custom styles
timneutkens Jul 6, 2022
63cfae7
WIP
timneutkens Jul 6, 2022
5c1695b
Remove body color
timneutkens Jul 6, 2022
f43ed0b
Fix infinite suspend
timneutkens Jul 6, 2022
dd22e22
Clean up initial cache
timneutkens Jul 6, 2022
f369b1a
Handle ssr
timneutkens Jul 6, 2022
e764828
Use react@18.2.0 for general tests
timneutkens Jul 6, 2022
91d21b9
Disable previous tests
timneutkens Jul 6, 2022
9bf17c2
Remove debug logs
timneutkens Jul 6, 2022
d03d90d
Merge branch 'canary' into add/back-nav-optimization-2
ijjk Jul 6, 2022
8725339
Merge branch 'canary' into add/back-nav-optimization-2
ijjk Jul 6, 2022
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jest": "24.3.5",
"eslint-plugin-react": "7.23.2",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-plugin-react-hooks": "4.5.0",
"event-stream": "4.0.1",
"execa": "2.0.3",
"express": "4.17.0",
Expand Down
191 changes: 86 additions & 105 deletions packages/next/build/webpack/loaders/next-app-loader.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,93 @@
import path from 'path'
import type webpack from 'webpack5'
import { NODE_RESOLVE_OPTIONS } from '../../webpack-config'
import { getModuleBuildInfo } from './get-module-build-info'

function pathToUrlPath(pathname: string) {
let urlPath = pathname.replace(/^private-next-app-dir/, '')

// For `app/layout.js`
if (urlPath === '') {
urlPath = '/'
}

return urlPath
}

async function resolvePathsByPage({
name,
async function createTreeCodeFromPath({
pagePath,
resolve,
removeExt,
}: {
name: 'layout' | 'loading'
pagePath: string
resolve: (pathname: string) => Promise<string | undefined>
removeExt: (pathToRemoveExtensions: string) => string
}) {
const paths = new Map<string, string | undefined>()
const parts = pagePath.split('/')
let tree: undefined | string
const splittedPath = pagePath.split('/')
const appDirPrefix = splittedPath[0]

const segments = ['', ...splittedPath.slice(1)]
const isNewRootLayout =
parts[1]?.length > 2 && parts[1]?.startsWith('(') && parts[1]?.endsWith(')')
segments[0]?.length > 2 &&
segments[0]?.startsWith('(') &&
segments[0]?.endsWith(')')

let isCustomRootLayout = false

for (let i = parts.length; i >= 0; i--) {
const pathWithoutSlashLayout = parts.slice(0, i).join('/')
// segment.length - 1 because arrays start at 0 and we're decrementing
for (let i = segments.length - 1; i >= 0; i--) {
const segment = removeExt(segments[i])
const segmentPath = segments.slice(0, i + 1).join('/')

if (!pathWithoutSlashLayout) {
// First item in the list is the page which can't have layouts by itself
if (i === segments.length - 1) {
// Use '' for segment as it's the page. There can't be a segment called '' so this is the safest way to add it.
tree = `['', {}, {page: () => require('${pagePath}')}]`
continue
}
const layoutPath = `${pathWithoutSlashLayout}/${name}`
let resolvedLayoutPath = await resolve(layoutPath)
let urlPath = pathToUrlPath(pathWithoutSlashLayout)

// For segmentPath === '' avoid double `/`
const layoutPath = `${appDirPrefix}${segmentPath}/layout`
// For segmentPath === '' avoid double `/`
const loadingPath = `${appDirPrefix}${segmentPath}/loading`

const resolvedLayoutPath = await resolve(layoutPath)
const resolvedLoadingPath = await resolve(loadingPath)

// if we are in a new root app/(root) and a custom root layout was
// not provided or a root layout app/layout is not present, we use
// a default root layout to provide the html/body tags
const isCustomRootLayout = name === 'layout' && isNewRootLayout && i === 2

if (
name === 'layout' &&
(isCustomRootLayout || i === 1) &&
!resolvedLayoutPath
) {
resolvedLayoutPath = await resolve('next/dist/lib/app-layout')
}
paths.set(urlPath, resolvedLayoutPath)
isCustomRootLayout = isNewRootLayout && i === 1

// Existing tree are the children of the current segment
const children = tree

tree = `['${segment}', {
${
// When there are no children the current index is the page component
children ? `children: ${children},` : ''
}
}, {
${
resolvedLayoutPath
? `layout: () => require('${resolvedLayoutPath}'),`
: ''
}
${
resolvedLoadingPath
? `loading: () => require('${resolvedLoadingPath}'),`
: ''
}
}]`

// if we're in a new root layout don't add the top-level app/layout
if (isCustomRootLayout) {
break
}
}
return paths

return `const tree = ${tree};`
}

function createAbsolutePath(appDir: string, pathToTurnAbsolute: string) {
return pathToTurnAbsolute.replace(/^private-next-app-dir/, appDir)
}

function removeExtensions(
extensions: string[],
pathToRemoveExtensions: string
) {
const regex = new RegExp(`(${extensions.join('|')})$`.replace(/\./g, '\\.'))
return pathToRemoveExtensions.replace(regex, '')
}

const nextAppLoader: webpack.LoaderDefinitionFunction<{
Expand All @@ -71,7 +101,7 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
const buildInfo = getModuleBuildInfo((this as any)._module)
buildInfo.route = {
page: name.replace(/^app/, ''),
absolutePagePath: appDir + pagePath.replace(/^private-next-app-dir/, ''),
absolutePagePath: createAbsolutePath(appDir, pagePath),
}

const extensions = pageExtensions.map((extension) => `.${extension}`)
Expand All @@ -81,89 +111,40 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{
}
const resolve = this.getResolve(resolveOptions)

const loadingPaths = await resolvePathsByPage({
name: 'loading',
pagePath: pagePath,
resolve: async (pathname) => {
try {
return await resolve(this.rootContext, pathname)
} catch (err: any) {
if (err.message.includes("Can't resolve")) {
return undefined
}
throw err
}
},
})

const loadingComponentsCode = []
for (const [loadingPath, resolvedLoadingPath] of loadingPaths) {
if (resolvedLoadingPath) {
this.addDependency(resolvedLoadingPath)
// use require so that we can bust the require cache
const codeLine = `'${loadingPath}': () => require('${resolvedLoadingPath}')`
loadingComponentsCode.push(codeLine)
} else {
const resolver = async (pathname: string) => {
try {
const resolved = await resolve(this.rootContext, pathname)
this.addDependency(resolved)
return resolved
} catch (err: any) {
const absolutePath = createAbsolutePath(appDir, pathname)
for (const ext of extensions) {
this.addMissingDependency(
path.join(appDir, loadingPath, `layout${ext}`)
)
}
}
}

const layoutPaths = await resolvePathsByPage({
name: 'layout',
pagePath: pagePath,
resolve: async (pathname) => {
try {
return await resolve(this.rootContext, pathname)
} catch (err: any) {
if (err.message.includes("Can't resolve")) {
return undefined
}
throw err
const absolutePathWithExtension = `${absolutePath}${ext}`
this.addMissingDependency(absolutePathWithExtension)
}
},
})

const componentsCode = []
for (const [layoutPath, resolvedLayoutPath] of layoutPaths) {
if (resolvedLayoutPath) {
this.addDependency(resolvedLayoutPath)
// use require so that we can bust the require cache
const codeLine = `'${layoutPath}': () => require('${resolvedLayoutPath}')`
componentsCode.push(codeLine)
} else {
for (const ext of extensions) {
this.addMissingDependency(path.join(appDir, layoutPath, `layout${ext}`))
if (err.message.includes("Can't resolve")) {
return undefined
}
throw err
}
}

// Add page itself to the list of components
componentsCode.push(
`'${pathToUrlPath(pagePath).replace(
new RegExp(`(${extensions.join('|')})$`),
''
// use require so that we can bust the require cache
)}': () => require('${pagePath}')`
)
const treeCode = await createTreeCodeFromPath({
pagePath,
resolve: resolver,
removeExt: (p) => removeExtensions(extensions, p),
})

const result = `
export const components = {
${componentsCode.join(',\n')}
};

export const loadingComponents = {
${loadingComponentsCode.join(',\n')}
};
export ${treeCode}

export const AppRouter = require('next/dist/client/components/app-router.client.js').default
export const LayoutRouter = require('next/dist/client/components/layout-router.client.js').default
export const hooksClientContext = require('next/dist/client/components/hooks-client-context.js')

export const __next_app_webpack_require__ = __webpack_require__
`

return result
}

Expand Down
7 changes: 0 additions & 7 deletions packages/next/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,6 @@ import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-we

export const version = process.env.__NEXT_VERSION

// History replace has to happen on bootup to ensure `state` is always populated in popstate event
window.history.replaceState(
{ url: window.location.toString() },
'',
window.location.toString()
)

const appElement: HTMLElement | Document | null = document

let reactRoot: any = null
Expand Down