one 1.2.22 → 1.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/build.cjs +10 -2
- package/dist/cjs/cli/build.js +13 -2
- package/dist/cjs/cli/build.js.map +1 -1
- package/dist/cjs/cli/build.native.js +12 -3
- package/dist/cjs/cli/build.native.js.map +1 -1
- package/dist/cjs/cli/buildPage.cjs +33 -12
- package/dist/cjs/cli/buildPage.js +31 -12
- package/dist/cjs/cli/buildPage.js.map +1 -1
- package/dist/cjs/cli/buildPage.native.js +57 -18
- package/dist/cjs/cli/buildPage.native.js.map +1 -1
- package/dist/cjs/createApp.cjs +3 -1
- package/dist/cjs/createApp.js +3 -2
- package/dist/cjs/createApp.js.map +1 -1
- package/dist/cjs/fork/useLinking.cjs +2 -2
- package/dist/cjs/fork/useLinking.js +2 -2
- package/dist/cjs/fork/useLinking.js.map +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.js +2 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/index.native.js +2 -0
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/router/router.cjs +22 -14
- package/dist/cjs/router/router.js +26 -18
- package/dist/cjs/router/router.js.map +1 -1
- package/dist/cjs/router/router.native.js +23 -15
- package/dist/cjs/router/router.native.js.map +1 -1
- package/dist/cjs/router/useScreens.cjs +5 -1
- package/dist/cjs/router/useScreens.js +3 -1
- package/dist/cjs/router/useScreens.js.map +1 -1
- package/dist/cjs/router/useScreens.native.js.map +1 -1
- package/dist/cjs/router/useViteRoutes.cjs +45 -0
- package/dist/cjs/router/useViteRoutes.js +43 -0
- package/dist/cjs/router/useViteRoutes.js.map +1 -1
- package/dist/cjs/router/useViteRoutes.native.js +64 -0
- package/dist/cjs/router/useViteRoutes.native.js.map +1 -1
- package/dist/cjs/server/oneServe.cjs +2 -1
- package/dist/cjs/server/oneServe.js +2 -1
- package/dist/cjs/server/oneServe.js.map +1 -1
- package/dist/cjs/server/oneServe.native.js +2 -1
- package/dist/cjs/server/oneServe.native.js.map +1 -1
- package/dist/cjs/useLoader.cjs +39 -40
- package/dist/cjs/useLoader.js +10 -7
- package/dist/cjs/useLoader.js.map +1 -1
- package/dist/cjs/useLoader.native.js +54 -54
- package/dist/cjs/useLoader.native.js.map +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +12 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.js +12 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.js.map +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +12 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
- package/dist/esm/cli/build.js +13 -2
- package/dist/esm/cli/build.js.map +1 -1
- package/dist/esm/cli/build.mjs +10 -2
- package/dist/esm/cli/build.mjs.map +1 -1
- package/dist/esm/cli/build.native.js +12 -3
- package/dist/esm/cli/build.native.js.map +1 -1
- package/dist/esm/cli/buildPage.js +31 -12
- package/dist/esm/cli/buildPage.js.map +1 -1
- package/dist/esm/cli/buildPage.mjs +33 -12
- package/dist/esm/cli/buildPage.mjs.map +1 -1
- package/dist/esm/cli/buildPage.native.js +57 -18
- package/dist/esm/cli/buildPage.native.js.map +1 -1
- package/dist/esm/createApp.js +3 -2
- package/dist/esm/createApp.js.map +1 -1
- package/dist/esm/createApp.mjs +3 -1
- package/dist/esm/createApp.mjs.map +1 -1
- package/dist/esm/fork/useLinking.js +2 -2
- package/dist/esm/fork/useLinking.js.map +1 -1
- package/dist/esm/fork/useLinking.mjs +2 -2
- package/dist/esm/fork/useLinking.mjs.map +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/index.mjs +2 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/index.native.js +2 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/router/router.js +32 -18
- package/dist/esm/router/router.js.map +1 -1
- package/dist/esm/router/router.mjs +23 -16
- package/dist/esm/router/router.mjs.map +1 -1
- package/dist/esm/router/router.native.js +24 -17
- package/dist/esm/router/router.native.js.map +1 -1
- package/dist/esm/router/useScreens.js +3 -1
- package/dist/esm/router/useScreens.js.map +1 -1
- package/dist/esm/router/useScreens.mjs +5 -1
- package/dist/esm/router/useScreens.mjs.map +1 -1
- package/dist/esm/router/useScreens.native.js.map +1 -1
- package/dist/esm/router/useViteRoutes.js +43 -0
- package/dist/esm/router/useViteRoutes.js.map +1 -1
- package/dist/esm/router/useViteRoutes.mjs +44 -1
- package/dist/esm/router/useViteRoutes.mjs.map +1 -1
- package/dist/esm/router/useViteRoutes.native.js +63 -1
- package/dist/esm/router/useViteRoutes.native.js.map +1 -1
- package/dist/esm/server/oneServe.js +2 -1
- package/dist/esm/server/oneServe.js.map +1 -1
- package/dist/esm/server/oneServe.mjs +2 -1
- package/dist/esm/server/oneServe.mjs.map +1 -1
- package/dist/esm/server/oneServe.native.js +2 -1
- package/dist/esm/server/oneServe.native.js.map +1 -1
- package/dist/esm/useLoader.js +10 -8
- package/dist/esm/useLoader.js.map +1 -1
- package/dist/esm/useLoader.mjs +40 -41
- package/dist/esm/useLoader.mjs.map +1 -1
- package/dist/esm/useLoader.native.js +55 -55
- package/dist/esm/useLoader.native.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.js +12 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +12 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +12 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
- package/package.json +11 -11
- package/src/cli/build.ts +18 -1
- package/src/cli/buildPage.ts +38 -6
- package/src/createApp.tsx +6 -3
- package/src/fork/useLinking.ts +10 -1
- package/src/index.ts +1 -0
- package/src/router/router.ts +47 -24
- package/src/router/useScreens.tsx +5 -3
- package/src/router/useViteRoutes.tsx +109 -1
- package/src/server/oneServe.ts +1 -0
- package/src/types.ts +2 -0
- package/src/useLoader.ts +21 -13
- package/src/vite/plugins/virtualEntryPlugin.ts +12 -1
- package/src/vite/types.ts +13 -0
- package/types/cli/build.d.ts.map +1 -1
- package/types/cli/buildPage.d.ts +1 -1
- package/types/cli/buildPage.d.ts.map +1 -1
- package/types/createApp.d.ts.map +1 -1
- package/types/fork/useLinking.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/router/router.d.ts +3 -2
- package/types/router/router.d.ts.map +1 -1
- package/types/router/useScreens.d.ts.map +1 -1
- package/types/router/useViteRoutes.d.ts +9 -0
- package/types/router/useViteRoutes.d.ts.map +1 -1
- package/types/server/oneServe.d.ts.map +1 -1
- package/types/types.d.ts +2 -0
- package/types/types.d.ts.map +1 -1
- package/types/useLoader.d.ts.map +1 -1
- package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
- package/types/vite/types.d.ts +12 -0
- package/types/vite/types.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "one",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.23",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"setup.mjs",
|
|
@@ -121,24 +121,24 @@
|
|
|
121
121
|
"@react-navigation/routers": "~7.5.1",
|
|
122
122
|
"@swc/core": "^1.14.0",
|
|
123
123
|
"@ungap/structured-clone": "^1.2.0",
|
|
124
|
-
"@vxrn/compiler": "1.2.
|
|
125
|
-
"@vxrn/resolve": "1.2.
|
|
126
|
-
"@vxrn/tslib-lite": "1.2.
|
|
127
|
-
"@vxrn/universal-color-scheme": "1.2.
|
|
128
|
-
"@vxrn/use-isomorphic-layout-effect": "1.2.
|
|
129
|
-
"@vxrn/vite-plugin-metro": "1.2.
|
|
124
|
+
"@vxrn/compiler": "1.2.23",
|
|
125
|
+
"@vxrn/resolve": "1.2.23",
|
|
126
|
+
"@vxrn/tslib-lite": "1.2.23",
|
|
127
|
+
"@vxrn/universal-color-scheme": "1.2.23",
|
|
128
|
+
"@vxrn/use-isomorphic-layout-effect": "1.2.23",
|
|
129
|
+
"@vxrn/vite-plugin-metro": "1.2.23",
|
|
130
130
|
"babel-dead-code-elimination": "^1.0.10",
|
|
131
131
|
"babel-plugin-module-resolver": "^5.0.2",
|
|
132
132
|
"citty": "^0.1.6",
|
|
133
133
|
"core-js": "^3.38.1",
|
|
134
|
-
"create-vxrn": "1.2.
|
|
134
|
+
"create-vxrn": "1.2.23",
|
|
135
135
|
"escape-string-regexp": "^5.0.0",
|
|
136
136
|
"expo-linking": "~8.0.8",
|
|
137
137
|
"expo-modules-core": "~3.0.24",
|
|
138
138
|
"fast-deep-equal": "^3.1.3",
|
|
139
139
|
"fast-glob": "^3.3.3",
|
|
140
140
|
"fs-extra": "^11.2.0",
|
|
141
|
-
"hono": "^4.
|
|
141
|
+
"hono": "^4.10.7",
|
|
142
142
|
"lightningcss": "^1.30.1",
|
|
143
143
|
"micromatch": "^4.0.7",
|
|
144
144
|
"nanoid": "^3.3.7",
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"vite": "^7.1.12",
|
|
158
158
|
"vite-plugin-barrel": "^0.4.1",
|
|
159
159
|
"vite-tsconfig-paths": "^5.1.4",
|
|
160
|
-
"vxrn": "1.2.
|
|
160
|
+
"vxrn": "1.2.23",
|
|
161
161
|
"ws": "^8.18.0",
|
|
162
162
|
"xxhashjs": "^0.2.2"
|
|
163
163
|
},
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
"devDependencies": {
|
|
176
176
|
"@react-navigation/core": "^7.13.0",
|
|
177
177
|
"@react-navigation/native": "~7.1.19",
|
|
178
|
-
"@tamagui/build": "^1.
|
|
178
|
+
"@tamagui/build": "^1.139.1",
|
|
179
179
|
"@types/node": "^24.10.0",
|
|
180
180
|
"@types/react-dom": "^19.2.2",
|
|
181
181
|
"@types/xxhashjs": "^0.2.4",
|
package/src/cli/build.ts
CHANGED
|
@@ -403,6 +403,22 @@ export async function build(args: {
|
|
|
403
403
|
// nested path pages need to reference root assets
|
|
404
404
|
.map((path) => `/${path}`)
|
|
405
405
|
|
|
406
|
+
// Read CSS file contents if inlineLayoutCSS is enabled
|
|
407
|
+
let allCSSContents: string[] | undefined
|
|
408
|
+
if (oneOptions.web?.inlineLayoutCSS) {
|
|
409
|
+
allCSSContents = await Promise.all(
|
|
410
|
+
allCSS.map(async (cssPath) => {
|
|
411
|
+
const filePath = join(clientDir, cssPath)
|
|
412
|
+
try {
|
|
413
|
+
return await FSExtra.readFile(filePath, 'utf-8')
|
|
414
|
+
} catch (err) {
|
|
415
|
+
console.warn(`[one] Warning: Could not read CSS file ${filePath}`)
|
|
416
|
+
return ''
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
|
|
406
422
|
if (process.env.DEBUG) {
|
|
407
423
|
console.info('[one] building routes', { foundRoute, layoutEntries, allEntries, allCSS })
|
|
408
424
|
}
|
|
@@ -468,7 +484,8 @@ export async function build(args: {
|
|
|
468
484
|
serverJsPath,
|
|
469
485
|
preloads,
|
|
470
486
|
allCSS,
|
|
471
|
-
routePreloads
|
|
487
|
+
routePreloads,
|
|
488
|
+
allCSSContents
|
|
472
489
|
)
|
|
473
490
|
})
|
|
474
491
|
|
package/src/cli/buildPage.ts
CHANGED
|
@@ -22,7 +22,8 @@ export async function buildPage(
|
|
|
22
22
|
serverJsPath: string,
|
|
23
23
|
preloads: string[],
|
|
24
24
|
allCSS: string[],
|
|
25
|
-
routePreloads: Record<string, string
|
|
25
|
+
routePreloads: Record<string, string>,
|
|
26
|
+
allCSSContents?: string[]
|
|
26
27
|
): Promise<One.RouteBuildInfo> {
|
|
27
28
|
const render = await getRender(serverEntry)
|
|
28
29
|
const htmlPath = `${path.endsWith('/') ? `${removeTrailingSlash(path)}/index` : path}.html`
|
|
@@ -35,12 +36,33 @@ export async function buildPage(
|
|
|
35
36
|
let loaderData = {}
|
|
36
37
|
|
|
37
38
|
try {
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// generate preload file with route module registration
|
|
40
|
+
const routeImports: string[] = []
|
|
41
|
+
const routeRegistrations: string[] = []
|
|
42
|
+
let routeIndex = 0
|
|
43
|
+
|
|
44
|
+
for (const [routeKey, bundlePath] of Object.entries(routePreloads)) {
|
|
45
|
+
const varName = `_r${routeIndex++}`
|
|
46
|
+
routeImports.push(`import * as ${varName} from "${bundlePath}"`)
|
|
47
|
+
routeRegistrations.push(`registerPreloadedRoute("${routeKey}", ${varName})`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Use window global for registration since ES module exports get tree-shaken
|
|
51
|
+
const registrationCalls = routeRegistrations.map((call) =>
|
|
52
|
+
call.replace('registerPreloadedRoute(', 'window.__oneRegisterPreloadedRoute(')
|
|
42
53
|
)
|
|
43
54
|
|
|
55
|
+
const preloadContent = [
|
|
56
|
+
// import all route modules
|
|
57
|
+
...routeImports,
|
|
58
|
+
// static imports for cache warming (original behavior)
|
|
59
|
+
...preloads.map((preload) => `import "${preload}"`),
|
|
60
|
+
// register all route modules using window global
|
|
61
|
+
...registrationCalls,
|
|
62
|
+
].join('\n')
|
|
63
|
+
|
|
64
|
+
await FSExtra.writeFile(join(clientDir, preloadPath), preloadContent)
|
|
65
|
+
|
|
44
66
|
const exported = await import(toAbsolute(serverJsPath))
|
|
45
67
|
|
|
46
68
|
if (exported.loader) {
|
|
@@ -73,11 +95,20 @@ if (typeof document === 'undefined') globalThis.document = {}
|
|
|
73
95
|
loaderProps,
|
|
74
96
|
loaderData,
|
|
75
97
|
css: allCSS,
|
|
98
|
+
cssContents: allCSSContents,
|
|
76
99
|
mode: 'ssg',
|
|
77
100
|
routePreloads,
|
|
78
101
|
})
|
|
79
102
|
await outputFile(htmlOutPath, html)
|
|
80
103
|
} else if (foundRoute.type === 'spa') {
|
|
104
|
+
// Generate CSS - either inline styles or link tags
|
|
105
|
+
const cssOutput = allCSSContents
|
|
106
|
+
? allCSSContents
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
.map((content) => ` <style>${content}</style>`)
|
|
109
|
+
.join('\n')
|
|
110
|
+
: allCSS.map((file) => ` <link rel="stylesheet" href=${file} />`).join('\n')
|
|
111
|
+
|
|
81
112
|
await outputFile(
|
|
82
113
|
htmlOutPath,
|
|
83
114
|
`<html><head>
|
|
@@ -85,7 +116,7 @@ if (typeof document === 'undefined') globalThis.document = {}
|
|
|
85
116
|
${preloads
|
|
86
117
|
.map((preload) => ` <script type="module" src="${preload}"></script>`)
|
|
87
118
|
.join('\n')}
|
|
88
|
-
${
|
|
119
|
+
${cssOutput}
|
|
89
120
|
</head></html>`
|
|
90
121
|
)
|
|
91
122
|
}
|
|
@@ -112,6 +143,7 @@ params:\n\n${JSON.stringify(params || null, null, 2)}`
|
|
|
112
143
|
return {
|
|
113
144
|
type: foundRoute.type,
|
|
114
145
|
css: allCSS,
|
|
146
|
+
cssContents: allCSSContents,
|
|
115
147
|
routeFile: foundRoute.file,
|
|
116
148
|
middlewares,
|
|
117
149
|
cleanPath,
|
package/src/createApp.tsx
CHANGED
|
@@ -26,7 +26,8 @@ export function createApp(options: CreateAppProps) {
|
|
|
26
26
|
return {
|
|
27
27
|
options,
|
|
28
28
|
render: async (props: RenderAppProps) => {
|
|
29
|
-
let { loaderData, loaderProps, css, mode, loaderServerData, routePreloads } =
|
|
29
|
+
let { loaderData, loaderProps, css, cssContents, mode, loaderServerData, routePreloads } =
|
|
30
|
+
props
|
|
30
31
|
|
|
31
32
|
setServerContext({
|
|
32
33
|
postRenderData: loaderServerData,
|
|
@@ -34,6 +35,7 @@ export function createApp(options: CreateAppProps) {
|
|
|
34
35
|
loaderProps,
|
|
35
36
|
mode,
|
|
36
37
|
css,
|
|
38
|
+
cssContents,
|
|
37
39
|
routePreloads,
|
|
38
40
|
})
|
|
39
41
|
|
|
@@ -121,14 +123,15 @@ export function createApp(options: CreateAppProps) {
|
|
|
121
123
|
const serverContext = getServerContext() || {}
|
|
122
124
|
const routePreloads = serverContext.routePreloads
|
|
123
125
|
|
|
124
|
-
// preload routes using build-time mapping (production
|
|
126
|
+
// preload routes using build-time mapping (production SSG)
|
|
127
|
+
// for SPA/dev mode, fall back to importing root layout directly
|
|
125
128
|
const preloadPromises = routePreloads
|
|
126
129
|
? Object.entries(routePreloads).map(async ([routeKey, bundlePath]) => {
|
|
127
130
|
const mod = await import(/* @vite-ignore */ bundlePath)
|
|
128
131
|
registerPreloadedRoute(routeKey, mod)
|
|
129
132
|
return mod
|
|
130
133
|
})
|
|
131
|
-
: []
|
|
134
|
+
: [options.routes[`/${options.routerRoot}/_layout.tsx`]?.()]
|
|
132
135
|
|
|
133
136
|
return Promise.all(preloadPromises)
|
|
134
137
|
.then(() => {
|
package/src/fork/useLinking.ts
CHANGED
|
@@ -157,9 +157,18 @@ export function useLinking(
|
|
|
157
157
|
(state: ResultState) => {
|
|
158
158
|
const navigation = ref.current
|
|
159
159
|
const rootState = navigation?.getRootState()
|
|
160
|
+
// @modified - start
|
|
161
|
+
// Fix for back/forward button navigation: if routeNames is undefined (stale state),
|
|
162
|
+
// don't reject the navigation. This can happen during browser back/forward.
|
|
163
|
+
// See: https://github.com/expo/expo/pull/37747
|
|
164
|
+
const routeNames = rootState?.routeNames
|
|
165
|
+
if (!routeNames) {
|
|
166
|
+
return false // Don't reject navigation if we can't validate
|
|
167
|
+
}
|
|
168
|
+
// @modified - end
|
|
160
169
|
// Make sure that the routes in the state exist in the root navigator
|
|
161
170
|
// Otherwise there's an error in the linking configuration
|
|
162
|
-
return state?.routes.some((r) => !
|
|
171
|
+
return state?.routes.some((r) => !routeNames.includes(r.name))
|
|
163
172
|
},
|
|
164
173
|
[ref]
|
|
165
174
|
)
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export { createRoute, route } from './router/createRoute'
|
|
|
66
66
|
export { router } from './router/imperative-api'
|
|
67
67
|
export * as routerStore from './router/router'
|
|
68
68
|
export { useNavigation } from './router/useNavigation'
|
|
69
|
+
export { registerPreloadedRoute } from './router/useViteRoutes'
|
|
69
70
|
export type { Endpoint, LoaderProps } from './types'
|
|
70
71
|
// React Navigation
|
|
71
72
|
export { useFocusEffect } from './useFocusEffect'
|
package/src/router/router.ts
CHANGED
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import { type NavigationContainerRefWithCurrent, StackActions } from '@react-navigation/native'
|
|
8
8
|
import * as Linking from 'expo-linking'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type ComponentType,
|
|
11
|
+
Fragment,
|
|
12
|
+
startTransition,
|
|
13
|
+
useDeferredValue,
|
|
14
|
+
useSyncExternalStore,
|
|
15
|
+
} from 'react'
|
|
10
16
|
import { Platform } from 'react-native'
|
|
11
17
|
import type { OneRouter } from '../interfaces/router'
|
|
12
18
|
import { resolveHref } from '../link/href'
|
|
@@ -24,6 +30,7 @@ import { getLinking, resetLinking, setupLinking } from './linkingConfig'
|
|
|
24
30
|
import type { RouteNode } from './Route'
|
|
25
31
|
import { sortRoutes } from './sortRoutes'
|
|
26
32
|
import { getQualifiedRouteComponent } from './useScreens'
|
|
33
|
+
import { preloadRouteModules } from './useViteRoutes'
|
|
27
34
|
import { getNavigateAction } from './utils/getNavigateAction'
|
|
28
35
|
|
|
29
36
|
// Module-scoped variables
|
|
@@ -334,7 +341,9 @@ export function routeInfoSnapshot() {
|
|
|
334
341
|
|
|
335
342
|
// Hook functions
|
|
336
343
|
export function useOneRouter() {
|
|
337
|
-
|
|
344
|
+
const state = useSyncExternalStore(subscribeToStore, snapshot, snapshot)
|
|
345
|
+
// useDeferredValue makes the transition concurrent, preventing main thread blocking
|
|
346
|
+
return useDeferredValue(state)
|
|
338
347
|
}
|
|
339
348
|
|
|
340
349
|
function syncStoreRootState() {
|
|
@@ -351,12 +360,14 @@ function syncStoreRootState() {
|
|
|
351
360
|
|
|
352
361
|
export function useStoreRootState() {
|
|
353
362
|
syncStoreRootState()
|
|
354
|
-
|
|
363
|
+
const state = useSyncExternalStore(subscribeToRootState, rootStateSnapshot, rootStateSnapshot)
|
|
364
|
+
return useDeferredValue(state)
|
|
355
365
|
}
|
|
356
366
|
|
|
357
367
|
export function useStoreRouteInfo() {
|
|
358
368
|
syncStoreRootState()
|
|
359
|
-
|
|
369
|
+
const state = useSyncExternalStore(subscribeToRootState, routeInfoSnapshot, routeInfoSnapshot)
|
|
370
|
+
return useDeferredValue(state)
|
|
360
371
|
}
|
|
361
372
|
|
|
362
373
|
// Cleanup function
|
|
@@ -367,38 +378,49 @@ export function cleanup() {
|
|
|
367
378
|
}
|
|
368
379
|
|
|
369
380
|
// TODO
|
|
370
|
-
export const preloadingLoader = {}
|
|
371
|
-
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
console.error(`Error preloading loader: ${err}`)
|
|
381
|
+
export const preloadingLoader: Record<string, Promise<any> | undefined> = {}
|
|
382
|
+
|
|
383
|
+
async function doPreload(href: string) {
|
|
384
|
+
const preloadPath = getPreloadPath(href)
|
|
385
|
+
const loaderPath = getLoaderPath(href)
|
|
386
|
+
try {
|
|
387
|
+
const [_preload, loader] = await Promise.all([
|
|
388
|
+
dynamicImport(preloadPath),
|
|
389
|
+
dynamicImport(loaderPath),
|
|
390
|
+
preloadRouteModules(href),
|
|
391
|
+
])
|
|
392
|
+
|
|
393
|
+
if (!loader?.loader) {
|
|
384
394
|
return null
|
|
385
395
|
}
|
|
396
|
+
|
|
397
|
+
const result = await loader.loader()
|
|
398
|
+
return result ?? null
|
|
399
|
+
} catch (err) {
|
|
400
|
+
console.error(`[one] preload error for ${href}:`, err)
|
|
401
|
+
return null
|
|
386
402
|
}
|
|
387
403
|
}
|
|
388
404
|
|
|
389
|
-
|
|
405
|
+
// Store resolved preload data separately from promises
|
|
406
|
+
export const preloadedLoaderData: Record<string, any> = {}
|
|
407
|
+
|
|
408
|
+
export function preloadRoute(href: string): Promise<any> | undefined {
|
|
390
409
|
if (process.env.TAMAGUI_TARGET === 'native') {
|
|
391
|
-
// not enabled for now
|
|
392
410
|
return
|
|
393
411
|
}
|
|
394
412
|
if (process.env.NODE_ENV === 'development') {
|
|
395
413
|
return
|
|
396
414
|
}
|
|
397
415
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
416
|
+
if (!preloadingLoader[href]) {
|
|
417
|
+
preloadingLoader[href] = doPreload(href).then((data) => {
|
|
418
|
+
// Store the resolved data for synchronous access
|
|
419
|
+
preloadedLoaderData[href] = data
|
|
420
|
+
return data
|
|
421
|
+
})
|
|
401
422
|
}
|
|
423
|
+
return preloadingLoader[href]
|
|
402
424
|
}
|
|
403
425
|
|
|
404
426
|
export async function linkTo(href: string, event?: string, options?: OneRouter.LinkToOptions) {
|
|
@@ -477,7 +499,8 @@ export async function linkTo(href: string, event?: string, options?: OneRouter.L
|
|
|
477
499
|
|
|
478
500
|
setLoadingState('loading')
|
|
479
501
|
|
|
480
|
-
|
|
502
|
+
// await preload on web to ensure route modules are loaded before navigating
|
|
503
|
+
await preloadRoute(href)
|
|
481
504
|
|
|
482
505
|
const rootState = navigationRef.getRootState()
|
|
483
506
|
|
|
@@ -205,9 +205,11 @@ export function getQualifiedRouteComponent(value: RouteNode) {
|
|
|
205
205
|
__html: `globalThis['global'] = globalThis`,
|
|
206
206
|
}}
|
|
207
207
|
/>
|
|
208
|
-
{serverContext?.
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
{serverContext?.cssContents
|
|
209
|
+
? serverContext.cssContents.map((content, i) =>
|
|
210
|
+
content ? <style key={i} dangerouslySetInnerHTML={{ __html: content }} /> : null
|
|
211
|
+
)
|
|
212
|
+
: serverContext?.css?.map((file) => <link key={file} rel="stylesheet" href={file} />)}
|
|
211
213
|
<ServerContextScript />
|
|
212
214
|
{headChildren}
|
|
213
215
|
</head>
|
|
@@ -37,6 +37,114 @@ export function getPreloadedModule(key: string): any {
|
|
|
37
37
|
return preloadedModules[key]
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
export function getPreloadedModuleKeys(): string[] {
|
|
41
|
+
return Object.keys(preloadedModules)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Checks if a dynamic route pattern matches an actual path.
|
|
46
|
+
* Used to preload route modules for dynamic routes like [slug].
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* matchDynamicRoute("docs/[slug]", "docs/getting-started") // true
|
|
50
|
+
* matchDynamicRoute("[...slug]", "a/b/c") // true (catch-all)
|
|
51
|
+
*/
|
|
52
|
+
function matchDynamicRoute(routePattern: string, actualPath: string): boolean {
|
|
53
|
+
const routeSegments = routePattern.split('/')
|
|
54
|
+
const pathSegments = actualPath.split('/')
|
|
55
|
+
|
|
56
|
+
// handle catch-all routes like [...slug]
|
|
57
|
+
const hasCatchAll = routeSegments.some((s) => s.startsWith('[...'))
|
|
58
|
+
if (hasCatchAll) {
|
|
59
|
+
// find the catch-all segment position
|
|
60
|
+
const catchAllIdx = routeSegments.findIndex((s) => s.startsWith('[...'))
|
|
61
|
+
// all segments before catch-all must match exactly (or be dynamic)
|
|
62
|
+
for (let i = 0; i < catchAllIdx; i++) {
|
|
63
|
+
if (!routeSegments[i]) continue
|
|
64
|
+
if (routeSegments[i].startsWith('[')) continue // dynamic segment matches anything
|
|
65
|
+
if (routeSegments[i] !== pathSegments[i]) return false
|
|
66
|
+
}
|
|
67
|
+
// catch-all matches any remaining segments
|
|
68
|
+
return pathSegments.length >= catchAllIdx
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// for non-catch-all, segment count should match
|
|
72
|
+
if (routeSegments.length !== pathSegments.length) return false
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < routeSegments.length; i++) {
|
|
75
|
+
const routeSeg = routeSegments[i]
|
|
76
|
+
const pathSeg = pathSegments[i]
|
|
77
|
+
|
|
78
|
+
// dynamic segment [param] matches any value
|
|
79
|
+
if (routeSeg.startsWith('[') && routeSeg.endsWith(']')) {
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// static segment must match exactly
|
|
84
|
+
if (routeSeg !== pathSeg) return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Preloads route modules for a given URL path (production only).
|
|
92
|
+
* This ensures route components are loaded before navigation completes,
|
|
93
|
+
* preventing Suspense boundaries from triggering and causing flicker.
|
|
94
|
+
*
|
|
95
|
+
* Called during `linkTo()` to preload routes before client-side navigation.
|
|
96
|
+
*/
|
|
97
|
+
export async function preloadRouteModules(href: string): Promise<void> {
|
|
98
|
+
const globbed = globalThis['__importMetaGlobbed']
|
|
99
|
+
if (!globbed) return
|
|
100
|
+
|
|
101
|
+
// normalize href to match route keys - /docs -> docs
|
|
102
|
+
const normalizedHref = href === '/' ? '' : href.replace(/^\//, '').replace(/\/$/, '')
|
|
103
|
+
|
|
104
|
+
const promises: Promise<any>[] = []
|
|
105
|
+
|
|
106
|
+
for (const key of Object.keys(globbed)) {
|
|
107
|
+
// key looks like "/app/(site)/docs/_layout.tsx" or "/app/(site)/docs/index+ssg.tsx"
|
|
108
|
+
// strip the /app/ prefix first
|
|
109
|
+
let routePath = key.replace(/^\/[^/]+\//, '')
|
|
110
|
+
|
|
111
|
+
// strip route groups like (site), (app) etc
|
|
112
|
+
routePath = routePath.replace(/\([^)]+\)\//g, '')
|
|
113
|
+
|
|
114
|
+
// strip file suffixes but keep the path structure
|
|
115
|
+
routePath = routePath
|
|
116
|
+
.replace(/\/_layout\.tsx$/, '')
|
|
117
|
+
.replace(/\/index(\+[a-z]+)?\.tsx$/, '')
|
|
118
|
+
.replace(/(\+[a-z]+)?\.tsx$/, '')
|
|
119
|
+
|
|
120
|
+
// remove leading slash if any
|
|
121
|
+
routePath = routePath.replace(/^\//, '')
|
|
122
|
+
|
|
123
|
+
// check if this route is part of the target path
|
|
124
|
+
const isStaticMatch =
|
|
125
|
+
routePath === normalizedHref || // exact match
|
|
126
|
+
routePath.startsWith(normalizedHref + '/') || // child route
|
|
127
|
+
normalizedHref.startsWith(routePath + '/') || // parent layout
|
|
128
|
+
routePath === '' || // root layout
|
|
129
|
+
(normalizedHref !== '' && routePath === normalizedHref.split('/')[0]) // top-level match
|
|
130
|
+
|
|
131
|
+
// also check dynamic route patterns like docs/[slug]
|
|
132
|
+
const isDynamicMatch = routePath.includes('[') && matchDynamicRoute(routePath, normalizedHref)
|
|
133
|
+
|
|
134
|
+
if ((isStaticMatch || isDynamicMatch) && typeof globbed[key] === 'function') {
|
|
135
|
+
promises.push(
|
|
136
|
+
globbed[key]()
|
|
137
|
+
.then((mod: any) => {
|
|
138
|
+
preloadedModules[key] = mod
|
|
139
|
+
})
|
|
140
|
+
.catch(() => {})
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await Promise.all(promises)
|
|
146
|
+
}
|
|
147
|
+
|
|
40
148
|
export function loadRoutes(
|
|
41
149
|
paths: GlobbedRouteImports,
|
|
42
150
|
routerRoot: string,
|
|
@@ -88,7 +196,7 @@ export function globbedRoutesToRouteContext(
|
|
|
88
196
|
return loadedRoutes[id]
|
|
89
197
|
}
|
|
90
198
|
|
|
91
|
-
// check if this route was preloaded
|
|
199
|
+
// check if this route was preloaded (via preload file or hydration)
|
|
92
200
|
const preloadKey = id.replace('./', `/${routerRoot}/`)
|
|
93
201
|
const preloaded = getPreloadedModule(preloadKey)
|
|
94
202
|
if (preloaded) {
|
package/src/server/oneServe.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type RenderAppProps = {
|
|
|
21
21
|
path: string
|
|
22
22
|
preloads?: string[]
|
|
23
23
|
css?: string[]
|
|
24
|
+
/** When inlineLayoutCSS is enabled, this contains the actual CSS content to inline */
|
|
25
|
+
cssContents?: string[]
|
|
24
26
|
loaderServerData?: any
|
|
25
27
|
loaderData?: any
|
|
26
28
|
loaderProps?: LoaderProps
|
package/src/useLoader.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { useCallback, useSyncExternalStore } from 'react'
|
|
1
|
+
import { useCallback, useDeferredValue, useSyncExternalStore } from 'react'
|
|
2
2
|
import { useParams, usePathname } from './hooks'
|
|
3
|
-
import {
|
|
4
|
-
import { preloadingLoader } from './router/router'
|
|
3
|
+
import { preloadedLoaderData, preloadingLoader } from './router/router'
|
|
5
4
|
import { getLoaderPath } from './utils/cleanUrl'
|
|
6
5
|
import { dynamicImport } from './utils/dynamicImport'
|
|
7
6
|
import { weakKey } from './utils/weakKey'
|
|
@@ -89,7 +88,9 @@ export function useLoaderState<
|
|
|
89
88
|
|
|
90
89
|
const params = useParams()
|
|
91
90
|
const pathname = usePathname()
|
|
92
|
-
|
|
91
|
+
// use just the pathname for matching, don't use resolveHref which adds params as query string
|
|
92
|
+
// (the pathname is already resolved like /docs/getting-started, not /docs/[slug])
|
|
93
|
+
const currentPath = pathname.replace(/\/index$/, '').replace(/\/$/, '') || '/'
|
|
93
94
|
|
|
94
95
|
// server-side
|
|
95
96
|
if (typeof window === 'undefined' && loader) {
|
|
@@ -103,9 +104,9 @@ export function useLoaderState<
|
|
|
103
104
|
return { data: serverData, refetch: async () => {}, state: 'idle' } as any
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
// preloaded data from SSR
|
|
107
|
-
const
|
|
108
|
-
|
|
107
|
+
// preloaded data from SSR/SSG - only use if server context path matches current path
|
|
108
|
+
const serverContextPath = loaderPropsFromServerContext?.path
|
|
109
|
+
const preloadedData = serverContextPath === currentPath ? loaderDataFromServerContext : undefined
|
|
109
110
|
|
|
110
111
|
const loaderStateEntry = useSyncExternalStore(
|
|
111
112
|
subscribe,
|
|
@@ -130,14 +131,21 @@ export function useLoaderState<
|
|
|
130
131
|
!loaderStateEntry.hasLoadedOnce &&
|
|
131
132
|
loader
|
|
132
133
|
) {
|
|
133
|
-
// check for
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
// check for already-resolved preloaded data first (synchronous)
|
|
135
|
+
const resolvedPreloadData = preloadedLoaderData[currentPath]
|
|
136
|
+
if (resolvedPreloadData !== undefined) {
|
|
137
|
+
// Data was preloaded and already resolved - use it directly
|
|
138
|
+
delete preloadedLoaderData[currentPath]
|
|
139
|
+
delete preloadingLoader[currentPath]
|
|
140
|
+
loaderStateEntry.data = resolvedPreloadData
|
|
141
|
+
loaderStateEntry.hasLoadedOnce = true
|
|
142
|
+
} else if (preloadingLoader[currentPath]) {
|
|
143
|
+
// Preload is in progress - wait for it
|
|
144
|
+
const preloadPromise = preloadingLoader[currentPath]!
|
|
145
|
+
const promise = preloadPromise
|
|
139
146
|
.then((val: any) => {
|
|
140
147
|
delete preloadingLoader[currentPath]
|
|
148
|
+
delete preloadedLoaderData[currentPath]
|
|
141
149
|
updateState(currentPath, {
|
|
142
150
|
data: val,
|
|
143
151
|
hasLoadedOnce: true,
|
|
@@ -66,7 +66,18 @@ export function createVirtualEntry(options: {
|
|
|
66
66
|
return `
|
|
67
67
|
${prependCode}
|
|
68
68
|
|
|
69
|
-
import { createApp } from 'one'
|
|
69
|
+
import { createApp, registerPreloadedRoute as _registerPreloadedRoute } from 'one'
|
|
70
|
+
|
|
71
|
+
// Export registerPreloadedRoute so preload files can import it from this bundle
|
|
72
|
+
// Named export that wraps the original function
|
|
73
|
+
export function registerPreloadedRoute(key, module) {
|
|
74
|
+
return _registerPreloadedRoute(key, module)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Also expose on window for debugging and to prevent tree-shaking
|
|
78
|
+
if (typeof window !== 'undefined') {
|
|
79
|
+
window.__oneRegisterPreloadedRoute = registerPreloadedRoute
|
|
80
|
+
}
|
|
70
81
|
|
|
71
82
|
// globbing ${JSON.stringify(routeGlobs)}
|
|
72
83
|
export default createApp({
|
package/src/vite/types.ts
CHANGED
|
@@ -333,6 +333,15 @@ export namespace One {
|
|
|
333
333
|
* @default node
|
|
334
334
|
*/
|
|
335
335
|
deploy?: 'vercel' | 'node'
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @experimental
|
|
339
|
+
* When true, inlines the CSS content directly into the HTML instead of using <link> tags.
|
|
340
|
+
* This can improve performance by eliminating an extra network request for CSS.
|
|
341
|
+
*
|
|
342
|
+
* @default false
|
|
343
|
+
*/
|
|
344
|
+
inlineLayoutCSS?: boolean
|
|
336
345
|
}
|
|
337
346
|
|
|
338
347
|
server?: VXRNOptions['server']
|
|
@@ -411,10 +420,14 @@ export namespace One {
|
|
|
411
420
|
loaderData: any
|
|
412
421
|
preloads: string[]
|
|
413
422
|
css: string[]
|
|
423
|
+
/** When inlineLayoutCSS is enabled, contains the actual CSS content */
|
|
424
|
+
cssContents?: string[]
|
|
414
425
|
}
|
|
415
426
|
|
|
416
427
|
export type ServerContext = {
|
|
417
428
|
css?: string[]
|
|
429
|
+
/** When inlineLayoutCSS is enabled, this contains the actual CSS content to inline */
|
|
430
|
+
cssContents?: string[]
|
|
418
431
|
postRenderData?: any
|
|
419
432
|
loaderData?: any
|
|
420
433
|
loaderProps?: any
|
package/types/cli/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAmCA,wBAAsB,KAAK,CAAC,IAAI,EAAE;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAA;CACrC,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAmCA,wBAAsB,KAAK,CAAC,IAAI,EAAE;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAA;CACrC,iBAokBA"}
|
package/types/cli/buildPage.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { One, RouteInfo } from '../vite/types';
|
|
2
|
-
export declare function buildPage(serverEntry: string, path: string, relativeId: string, params: any, foundRoute: RouteInfo<string>, clientManifestEntry: any, staticDir: string, clientDir: string, builtMiddlewares: Record<string, string>, serverJsPath: string, preloads: string[], allCSS: string[], routePreloads: Record<string, string
|
|
2
|
+
export declare function buildPage(serverEntry: string, path: string, relativeId: string, params: any, foundRoute: RouteInfo<string>, clientManifestEntry: any, staticDir: string, clientDir: string, builtMiddlewares: Record<string, string>, serverJsPath: string, preloads: string[], allCSS: string[], routePreloads: Record<string, string>, allCSSContents?: string[]): Promise<One.RouteBuildInfo>;
|
|
3
3
|
//# sourceMappingURL=buildPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildPage.d.ts","sourceRoot":"","sources":["../../src/cli/buildPage.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAInD,wBAAsB,SAAS,CAC7B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,GAAG,EACX,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,EAC7B,mBAAmB,EAAE,GAAG,EACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACxC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"buildPage.d.ts","sourceRoot":"","sources":["../../src/cli/buildPage.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAInD,wBAAsB,SAAS,CAC7B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,GAAG,EACX,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,EAC7B,mBAAmB,EAAE,GAAG,EACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACxC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,cAAc,CAAC,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAqI7B"}
|