-
Notifications
You must be signed in to change notification settings - Fork 26.7k
/
next-app-loader.ts
144 lines (123 loc) · 4.4 KB
/
next-app-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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import type webpack from 'webpack'
import { NODE_RESOLVE_OPTIONS } from '../../webpack-config'
import { getModuleBuildInfo } from './get-module-build-info'
async function createTreeCodeFromPath({
pagePath,
resolve,
removeExt,
}: {
pagePath: string
resolve: (pathname: string) => Promise<string | undefined>
removeExt: (pathToRemoveExtensions: string) => string
}) {
let tree: undefined | string
const splittedPath = pagePath.split(/[\\/]/)
const appDirPrefix = splittedPath[0]
const segments = ['', ...splittedPath.slice(1)]
// 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('/')
// First item in the list is the page which can't have layouts by itself
if (i === segments.length - 1) {
const resolvedPagePath = await resolve(pagePath)
// 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 = `['', {}, {filePath: ${JSON.stringify(
resolvedPagePath
)}, page: () => require(${JSON.stringify(resolvedPagePath)})}]`
continue
}
// 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)
// 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},` : ''
}
}, {
filePath: '${resolvedLayoutPath}',
${
resolvedLayoutPath
? `layout: () => require(${JSON.stringify(resolvedLayoutPath)}),`
: ''
}
${
resolvedLoadingPath
? `loading: () => require(${JSON.stringify(resolvedLoadingPath)}),`
: ''
}
}]`
}
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<{
name: string
pagePath: string
appDir: string
pageExtensions: string[]
}> = async function nextAppLoader() {
const { name, appDir, pagePath, pageExtensions } = this.getOptions() || {}
const buildInfo = getModuleBuildInfo((this as any)._module)
buildInfo.route = {
page: name.replace(/^app/, ''),
absolutePagePath: createAbsolutePath(appDir, pagePath),
}
const extensions = pageExtensions.map((extension) => `.${extension}`)
const resolveOptions: any = {
...NODE_RESOLVE_OPTIONS,
extensions,
}
const resolve = this.getResolve(resolveOptions)
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) {
const absolutePathWithExtension = `${absolutePath}${ext}`
this.addMissingDependency(absolutePathWithExtension)
}
if (err.message.includes("Can't resolve")) {
return undefined
}
throw err
}
}
const treeCode = await createTreeCodeFromPath({
pagePath,
resolve: resolver,
removeExt: (p) => removeExtensions(extensions, p),
})
const result = `
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 HotReloader = ${
// Disable HotReloader component in production
this.mode === 'development'
? `require('next/dist/client/components/hot-reloader.client.js').default`
: 'null'
}
export const __next_app_webpack_require__ = __webpack_require__
`
return result
}
export default nextAppLoader