Skip to content

Commit 42cbc45

Browse files
feat: auto detect route entry
1 parent ac81b5d commit 42cbc45

File tree

14 files changed

+155
-97
lines changed

14 files changed

+155
-97
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ const route = {
157157
entry: 'src/pages/nest/[b].tsx',
158158
// To determine which paths will be pre-rendered
159159
getStaticPaths: () => ['nest/b1', 'nest/b2'],
160-
},
160+
}
161161
```
162162

163163
## lazy

examples/lazy-pages/src/App.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,16 @@ export const routes: RouteRecord[] = [
1212
{
1313
path: 'a',
1414
lazy: () => import('./pages/a'),
15-
// Component: React.lazy(() => import('./pages/a')),
1615
},
1716
{
1817
index: true,
1918
lazy: () => defaultToComponent(import('./pages/index')),
20-
entry: 'src/pages/index.tsx',
2119
},
2220
{
2321
path: 'nest/:b',
2422
lazy: () => defaultToComponent(import('./pages/nest/[b]')),
2523
},
2624
],
27-
entry: 'src/Layout.tsx',
2825
},
2926
]
3027

examples/multiple-pages/src/App.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,26 @@ const Layout = React.lazy(() => import('./Layout'))
77
export const routes: RouteRecord[] = [
88
{
99
path: '/',
10-
element: <Layout />,
10+
Component: Layout,
1111
children: [
1212
{
1313
path: 'a',
1414
Component: React.lazy(() => import('./pages/a')),
15-
entry: 'src/pages/a.tsx',
1615
},
1716
{
1817
index: true,
1918
Component: React.lazy(() => import('./pages/index')),
20-
entry: 'src/pages/index.tsx',
2119
},
2220
{
2321
path: 'nest/*',
24-
Component: React.lazy(() => import('./pages/nest/[b]')),
25-
entry: 'src/pages/nest/[b].tsx',
22+
lazy: async () => {
23+
await import('./components/load-comp-1')
24+
return {
25+
Component: (await import('./pages/nest/[b]')).Component,
26+
}
27+
},
2628
getStaticPaths: () => ['nest/b1', 'nest/b2'],
2729
},
2830
],
29-
entry: 'src/Layout.tsx',
3031
},
3132
]

examples/multiple-pages/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default defineConfig({
99
// It will cause Hydration Failed
1010
// formatting: 'minify',
1111
mock: true,
12+
crittersOptions: false,
1213
},
1314
server: {
1415
headers: {

examples/styled-components/src/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,5 @@ export const routes: RouteRecord[] = [
3030
path: '/',
3131
element: <Layout />,
3232
children,
33-
entry: 'src/Layout.tsx',
3433
},
3534
]

examples/with-loader/src/main.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const routes: RouteRecord[] = [
77
{
88
index: true,
99
lazy: () => import('./pages'),
10-
entry: 'src/pages/index.tsx',
1110
},
1211
{
1312
id: 'doc?s',

src/node/assets.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { matchRoutes } from 'react-router-dom'
2+
import type { RouteRecord } from '../types'
3+
import type { Manifest, SSRManifest } from './build'
4+
5+
export const DYNAMIC_IMPORT_REGEX = /import\("([^)]+)"\)/g
6+
export enum AssetType {
7+
style = 'style',
8+
script = 'script',
9+
image = 'image',
10+
font = 'font',
11+
}
12+
13+
interface CollectAssetsOpts {
14+
routes: RouteRecord[]
15+
locationArg: string
16+
base: string
17+
serverManifest: Manifest
18+
manifest: Manifest
19+
ssrManifest: SSRManifest
20+
}
21+
22+
export function collectAssets({
23+
routes,
24+
locationArg,
25+
base,
26+
serverManifest,
27+
manifest,
28+
ssrManifest,
29+
}: CollectAssetsOpts) {
30+
const matches = matchRoutes([...routes], locationArg, base)
31+
const dynamicImports = new Set<string>()
32+
matches?.forEach(item => {
33+
let lazyStr = ''
34+
if (item.route.lazy) {
35+
lazyStr += item.route.lazy.toString()
36+
}
37+
// @ts-expect-error lazy
38+
if (item.route.Component?._payload?._result) {
39+
// @ts-expect-error lazy
40+
lazyStr += item.route.Component._payload._result.toString()
41+
}
42+
const match = lazyStr.matchAll(DYNAMIC_IMPORT_REGEX)
43+
for (const m of match) {
44+
dynamicImports.add(m[1].split('/').at(-1) ?? '')
45+
}
46+
})
47+
const entries = new Set<string>()
48+
const manifestEntries = [...Object.entries(serverManifest)]
49+
dynamicImports.forEach(name => {
50+
const result = manifestEntries.find(([_, value]) => value.file.endsWith(name))
51+
if (result) {
52+
entries.add(result[0])
53+
}
54+
})
55+
56+
const modules = collectModulesForEntries(manifest, entries)
57+
const assets = new Set<string>()
58+
Array.from(modules).forEach(id => {
59+
const files = ssrManifest[id] || []
60+
files.forEach(file => {
61+
assets.add(file)
62+
})
63+
})
64+
return assets
65+
}
66+
67+
function collectModulesForEntries(manifest: Manifest, entries: Set<string> | undefined) {
68+
const mods = new Set<string>()
69+
if (!entries)
70+
return mods
71+
72+
for (const entry of entries)
73+
collectModules(manifest, entry, mods)
74+
75+
return mods
76+
}
77+
78+
function collectModules(manifest: Manifest, entry: string | undefined, mods = new Set<string>()) {
79+
if (!entry)
80+
return mods
81+
82+
mods.add(entry)
83+
manifest[entry]?.dynamicImports?.forEach(item => {
84+
collectModules(manifest, item, mods)
85+
})
86+
87+
return mods
88+
}

src/node/build.ts

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import { getCritters } from './critial'
1616
import { render } from './server'
1717
import { SCRIPT_COMMENT_PLACEHOLDER, detectEntry, renderHTML } from './html'
1818
import { renderPreloadLinks } from './preload-links'
19+
import { collectAssets } from './assets'
1920

21+
const dotVitedir = Number.parseInt(viteVersion) >= 5 ? ['.vite'] : []
2022
export type SSRManifest = Record<string, string[]>
2123
export interface ManifestItem {
2224
css?: string[]
@@ -111,6 +113,7 @@ export async function build(ssgOptions: Partial<ViteReactSSGOptions> = {}, viteC
111113
await viteBuild(mergeConfig(viteConfig, {
112114
build: {
113115
ssr: ssrEntry,
116+
manifest: true,
114117
outDir: ssgOut,
115118
minify: false,
116119
cssCodeSplit: false,
@@ -133,6 +136,7 @@ export async function build(ssgOptions: Partial<ViteReactSSGOptions> = {}, viteC
133136
const prefix = (format === 'esm' && process.platform === 'win32') ? 'file://' : ''
134137
const ext = format === 'esm' ? '.mjs' : '.cjs'
135138
const serverEntry = join(prefix, ssgOut, parse(ssrEntry).name + ext)
139+
const serverManifest: Manifest = JSON.parse(await fs.readFile(join(ssgOut, ...dotVitedir, 'manifest.json'), 'utf-8'))
136140

137141
const _require = createRequire(import.meta.url)
138142

@@ -142,7 +146,7 @@ export async function build(ssgOptions: Partial<ViteReactSSGOptions> = {}, viteC
142146
const includedRoutes = serverEntryIncludedRoutes || configIncludedRoutes
143147
const { routes } = await createRoot(false)
144148

145-
const { paths, pathToEntry } = await routesToPaths(routes)
149+
const { paths } = await routesToPaths(routes)
146150

147151
let routesPaths = includeAllRoutes
148152
? paths
@@ -158,7 +162,6 @@ export async function build(ssgOptions: Partial<ViteReactSSGOptions> = {}, viteC
158162
if (critters)
159163
console.log(`${gray('[vite-react-ssg]')} ${blue('Critical CSS generation enabled via `critters`')}`)
160164

161-
const dotVitedir = Number.parseInt(viteVersion) >= 5 ? ['.vite'] : []
162165
const ssrManifest: SSRManifest = JSON.parse(await fs.readFile(join(out, ...dotVitedir, 'ssr-manifest.json'), 'utf-8'))
163166
const manifest: Manifest = JSON.parse(await fs.readFile(join(out, ...dotVitedir, 'manifest.json'), 'utf-8'))
164167
let indexHTML = await fs.readFile(join(out, 'index.html'), 'utf-8')
@@ -182,6 +185,8 @@ export async function build(ssgOptions: Partial<ViteReactSSGOptions> = {}, viteC
182185
const fetchUrl = `${withTrailingSlash(base)}${removeLeadingSlash(path)}`
183186
const request = createRequest(fetchUrl)
184187

188+
const assets = !app ? collectAssets({ routes: [...routes], locationArg: fetchUrl, base, serverManifest, manifest, ssrManifest }) : new Set<string>()
189+
185190
const { appHTML, bodyAttributes, htmlAttributes, metaAttributes, styleTag, routerContext } = await render(app ?? [...routes], request, styleCollector, base)
186191
staticLoaderDataManifest[path] = routerContext?.loaderData
187192

@@ -199,9 +204,7 @@ export async function build(ssgOptions: Partial<ViteReactSSGOptions> = {}, viteC
199204

200205
const jsdom = new JSDOM(renderedHTML)
201206

202-
const modules = collectModulesForEntrys(manifest, pathToEntry?.[path])
203-
204-
renderPreloadLinks(jsdom.window.document, modules, ssrManifest)
207+
renderPreloadLinks(jsdom.window.document, assets)
205208

206209
const html = jsdom.serialize()
207210
let transformed = (await onPageRendered?.(path, html, appCtx)) || html
@@ -298,26 +301,3 @@ async function formatHtml(html: string, formatting: ViteReactSSGOptions['formatt
298301
}
299302
return html
300303
}
301-
302-
function collectModulesForEntrys(manifest: Manifest, entrys: Set<string> | undefined) {
303-
const mods = new Set<string>()
304-
if (!entrys)
305-
return mods
306-
307-
for (const entry of entrys)
308-
collectModules(manifest, entry, mods)
309-
310-
return mods
311-
}
312-
313-
function collectModules(manifest: Manifest, entry: string | undefined, mods = new Set<string>()) {
314-
if (!entry)
315-
return mods
316-
317-
mods.add(entry)
318-
manifest[entry]?.dynamicImports?.forEach(item => {
319-
collectModules(manifest, item, mods)
320-
})
321-
322-
return mods
323-
}

src/node/dev.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export async function printServerInfo(server: ViteDevServer, onlyUrl = false) {
8888
{ clear: !server.config.logger.hasWarned },
8989
)
9090
info(
91-
`${cyan(`\n VITE v${viteVersion}`) + dim(ssrReadyMessage)}\n`,
91+
`${cyan(`\n VITE v${viteVersion}`) + dim(ssrReadyMessage)}\n`,
9292
)
9393

9494
info(

src/node/preload-links.ts

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,8 @@
1-
import type { SSRManifest } from './build'
2-
3-
export function renderPreloadLinks(document: Document, modules: Set<string>, ssrManifest: SSRManifest) {
1+
export function renderPreloadLinks(document: Document, assets: Set<string>) {
42
const seen = new Set()
53

6-
const preloadLinks: string[] = []
7-
8-
// preload modules
9-
Array.from(modules).forEach(id => {
10-
const files = ssrManifest[id] || []
11-
files.forEach(file => {
12-
if (!preloadLinks.includes(file))
13-
preloadLinks.push(file)
14-
})
15-
})
16-
17-
if (preloadLinks) {
18-
preloadLinks.forEach(file => {
4+
if (assets) {
5+
assets.forEach(file => {
196
if (!seen.has(file)) {
207
seen.add(file)
218
renderPreloadLink(document, file)
@@ -39,6 +26,23 @@ function renderPreloadLink(document: Document, file: string) {
3926
crossOrigin: '',
4027
})
4128
}
29+
else if (file.endsWith('.woff') || file.endsWith('.woff2') || file.endsWith('.ttf')) {
30+
appendLink(document, {
31+
rel: 'preload',
32+
as: 'font',
33+
type: 'font/woff2',
34+
href: file,
35+
crossOrigin: '',
36+
})
37+
}
38+
else if (file.endsWith('.png') || file.endsWith('.jpg') || file.endsWith('.jpeg') || file.endsWith('.webp') || file.endsWith('.gif') || file.endsWith('.ico') || file.endsWith('.svg')) {
39+
appendLink(document, {
40+
rel: 'preload',
41+
as: 'image',
42+
href: file,
43+
crossOrigin: '',
44+
})
45+
}
4246
}
4347

4448
function createLink(document: Document) {

0 commit comments

Comments
 (0)