rsnext/packages/next/build/webpack/loaders/next-app-loader.ts

171 lines
4.8 KiB
TypeScript
Raw Normal View History

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) {
2022-05-25 11:46:26 +02:00
let urlPath = pathname.replace(/^private-next-app-dir/, '')
2022-05-25 11:46:26 +02:00
// For `app/layout.js`
if (urlPath === '') {
urlPath = '/'
}
return urlPath
}
2022-06-02 12:02:05 +02:00
async function resolvePathsByPage({
name,
pagePath,
resolve,
}: {
2022-06-02 12:02:05 +02:00
name: 'layout' | 'loading'
pagePath: string
resolve: (pathname: string) => Promise<string | undefined>
}) {
2022-06-02 12:02:05 +02:00
const paths = new Map<string, string | undefined>()
const parts = pagePath.split('/')
const isNewRootLayout =
parts[1]?.length > 2 && parts[1]?.startsWith('(') && parts[1]?.endsWith(')')
for (let i = parts.length; i >= 0; i--) {
const pathWithoutSlashLayout = parts.slice(0, i).join('/')
if (!pathWithoutSlashLayout) {
continue
}
2022-06-02 12:02:05 +02:00
const layoutPath = `${pathWithoutSlashLayout}/${name}`
let resolvedLayoutPath = await resolve(layoutPath)
let urlPath = pathToUrlPath(pathWithoutSlashLayout)
2022-05-25 11:46:26 +02:00
// 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
2022-06-02 12:02:05 +02:00
const isCustomRootLayout = name === 'layout' && isNewRootLayout && i === 2
2022-06-02 12:02:05 +02:00
if (
name === 'layout' &&
(isCustomRootLayout || i === 1) &&
!resolvedLayoutPath
) {
2022-05-25 11:46:26 +02:00
resolvedLayoutPath = await resolve('next/dist/lib/app-layout')
}
2022-06-02 12:02:05 +02:00
paths.set(urlPath, resolvedLayoutPath)
2022-05-25 11:46:26 +02:00
// if we're in a new root layout don't add the top-level app/layout
if (isCustomRootLayout) {
break
}
}
2022-06-02 12:02:05 +02:00
return paths
}
2022-05-25 11:46:26 +02:00
const nextAppLoader: webpack.LoaderDefinitionFunction<{
name: string
pagePath: string
2022-05-25 11:46:26 +02:00
appDir: string
pageExtensions: string[]
2022-05-25 11:46:26 +02:00
}> = async function nextAppLoader() {
const { name, appDir, pagePath, pageExtensions } = this.getOptions() || {}
const buildInfo = getModuleBuildInfo((this as any)._module)
buildInfo.route = {
2022-05-25 11:46:26 +02:00
page: name.replace(/^app/, ''),
absolutePagePath: appDir + pagePath.replace(/^private-next-app-dir/, ''),
}
const extensions = pageExtensions.map((extension) => `.${extension}`)
const resolveOptions: any = {
...NODE_RESOLVE_OPTIONS,
extensions,
}
const resolve = this.getResolve(resolveOptions)
2022-06-02 12:02:05 +02:00
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 {
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 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) {
2022-05-25 11:46:26 +02:00
this.addMissingDependency(path.join(appDir, layoutPath, `layout${ext}`))
}
}
}
// 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 result = `
export const components = {
${componentsCode.join(',\n')}
};
2022-06-02 12:02:05 +02:00
export const loadingComponents = {
${loadingComponentsCode.join(',\n')}
};
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
2022-05-25 11:46:26 +02:00
export const __next_app_webpack_require__ = __webpack_require__
`
return result
}
2022-05-25 11:46:26 +02:00
export default nextAppLoader