forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 1
/
next-view-loader.ts
128 lines (110 loc) · 3.62 KB
/
next-view-loader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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-views-dir/, '')
// For `views/layout.js`
if (urlPath === '') {
urlPath = '/'
}
return urlPath
}
async function resolveLayoutPathsByPage({
pagePath,
resolve,
}: {
pagePath: string
resolve: (pathname: string) => Promise<string | undefined>
}) {
const layoutPaths = 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
}
const layoutPath = `${pathWithoutSlashLayout}/layout`
let resolvedLayoutPath = await resolve(layoutPath)
let urlPath = pathToUrlPath(pathWithoutSlashLayout)
// if we are in a new root views/(root) and a custom root layout was
// not provided or a root layout views/layout is not present, we use
// a default root layout to provide the html/body tags
const isCustomRootLayout = isNewRootLayout && i === 2
if ((isCustomRootLayout || i === 1) && !resolvedLayoutPath) {
resolvedLayoutPath = await resolve('next/dist/lib/views-layout')
}
layoutPaths.set(urlPath, resolvedLayoutPath)
// if we're in a new root layout don't add the top-level view/layout
if (isCustomRootLayout) {
break
}
}
return layoutPaths
}
const nextViewLoader: webpack.LoaderDefinitionFunction<{
name: string
pagePath: string
viewsDir: string
pageExtensions: string[]
}> = async function nextViewLoader() {
const { name, viewsDir, pagePath, pageExtensions } = this.getOptions() || {}
const buildInfo = getModuleBuildInfo((this as any)._module)
buildInfo.route = {
page: name.replace(/^views/, ''),
absolutePagePath:
viewsDir + pagePath.replace(/^private-next-views-dir/, ''),
}
const extensions = pageExtensions.map((extension) => `.${extension}`)
const resolveOptions: any = {
...NODE_RESOLVE_OPTIONS,
extensions,
}
const resolve = this.getResolve(resolveOptions)
const layoutPaths = await resolveLayoutPathsByPage({
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) {
this.addMissingDependency(
path.join(viewsDir, layoutPath, `layout${ext}`)
)
}
}
}
// Add page itself to the list of components
componentsCode.push(
`'${pathToUrlPath(pagePath).replace(
new RegExp(`/page+(${extensions.join('|')})$`),
''
// use require so that we can bust the require cache
)}': () => require('${pagePath}')`
)
const result = `
export const components = {
${componentsCode.join(',\n')}
};
export const __next_view_webpack_require__ = __webpack_require__
`
return result
}
export default nextViewLoader