one 1.24.9 → 1.25.3
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/router/web/WebStackView.native.js.map +1 -1
- package/dist/cjs/vite/one.cjs +77 -46
- package/dist/cjs/vite/one.native.js +129 -92
- package/dist/cjs/vite/one.native.js.map +1 -1
- package/dist/cjs/vite/plugins/bundledDevPlugin.cjs +16 -7
- package/dist/cjs/vite/plugins/bundledDevPlugin.native.js +16 -7
- package/dist/cjs/vite/plugins/bundledDevPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/criticalCSSPlugin.cjs +15 -11
- package/dist/cjs/vite/plugins/criticalCSSPlugin.native.js +15 -11
- package/dist/cjs/vite/plugins/criticalCSSPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/criticalCSSPlugin.test.cjs +9 -25
- package/dist/cjs/vite/plugins/criticalCSSPlugin.test.native.js +9 -25
- package/dist/cjs/vite/plugins/criticalCSSPlugin.test.native.js.map +1 -1
- package/dist/cjs/vite/plugins/environmentGuardPlugin.cjs +22 -7
- package/dist/cjs/vite/plugins/environmentGuardPlugin.native.js +23 -8
- package/dist/cjs/vite/plugins/environmentGuardPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +1 -1
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +1 -1
- package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/imageDataPlugin.cjs +42 -32
- package/dist/cjs/vite/plugins/imageDataPlugin.native.js +42 -32
- package/dist/cjs/vite/plugins/imageDataPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/imageDataPlugin.test.cjs +18 -38
- package/dist/cjs/vite/plugins/imageDataPlugin.test.native.js +18 -38
- package/dist/cjs/vite/plugins/imageDataPlugin.test.native.js.map +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +58 -43
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +93 -76
- package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.test.cjs +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.test.native.js +1 -1
- package/dist/cjs/vite/plugins/virtualEntryPlugin.test.native.js.map +1 -1
- package/dist/cjs/vite/plugins/warmRoutesPlugin.cjs +31 -7
- package/dist/cjs/vite/plugins/warmRoutesPlugin.native.js +35 -10
- package/dist/cjs/vite/plugins/warmRoutesPlugin.native.js.map +1 -1
- package/dist/esm/router/web/WebStackView.mjs.map +1 -1
- package/dist/esm/router/web/WebStackView.native.js.map +1 -1
- package/dist/esm/useLoader.native.js +0 -66
- package/dist/esm/useLoader.native.js.map +1 -1
- package/dist/esm/vite/one.mjs +77 -46
- package/dist/esm/vite/one.mjs.map +1 -1
- package/dist/esm/vite/one.native.js +129 -92
- package/dist/esm/vite/one.native.js.map +1 -1
- package/dist/esm/vite/plugins/bundledDevPlugin.mjs +16 -7
- package/dist/esm/vite/plugins/bundledDevPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/bundledDevPlugin.native.js +16 -7
- package/dist/esm/vite/plugins/bundledDevPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.mjs +15 -11
- package/dist/esm/vite/plugins/criticalCSSPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.native.js +15 -11
- package/dist/esm/vite/plugins/criticalCSSPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.mjs +9 -25
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.mjs.map +1 -1
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.native.js +9 -25
- package/dist/esm/vite/plugins/criticalCSSPlugin.test.native.js.map +1 -1
- package/dist/esm/vite/plugins/environmentGuardPlugin.mjs +22 -7
- package/dist/esm/vite/plugins/environmentGuardPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/environmentGuardPlugin.native.js +23 -8
- package/dist/esm/vite/plugins/environmentGuardPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +1 -1
- package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.mjs +42 -32
- package/dist/esm/vite/plugins/imageDataPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.native.js +42 -32
- package/dist/esm/vite/plugins/imageDataPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.test.mjs +18 -38
- package/dist/esm/vite/plugins/imageDataPlugin.test.mjs.map +1 -1
- package/dist/esm/vite/plugins/imageDataPlugin.test.native.js +18 -38
- package/dist/esm/vite/plugins/imageDataPlugin.test.native.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +58 -43
- package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +93 -76
- package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.mjs +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.mjs.map +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.native.js +1 -1
- package/dist/esm/vite/plugins/virtualEntryPlugin.test.native.js.map +1 -1
- package/dist/esm/vite/plugins/warmRoutesPlugin.mjs +31 -7
- package/dist/esm/vite/plugins/warmRoutesPlugin.mjs.map +1 -1
- package/dist/esm/vite/plugins/warmRoutesPlugin.native.js +35 -10
- package/dist/esm/vite/plugins/warmRoutesPlugin.native.js.map +1 -1
- package/package.json +12 -12
- package/src/router/web/WebStackView.tsx +4 -1
- package/src/vite/one.ts +121 -66
- package/src/vite/plugins/bundledDevPlugin.ts +17 -7
- package/src/vite/plugins/criticalCSSPlugin.test.ts +11 -27
- package/src/vite/plugins/criticalCSSPlugin.ts +15 -11
- package/src/vite/plugins/environmentGuardPlugin.ts +20 -7
- package/src/vite/plugins/fileSystemRouterPlugin.tsx +7 -3
- package/src/vite/plugins/imageDataPlugin.test.ts +20 -39
- package/src/vite/plugins/imageDataPlugin.ts +49 -44
- package/src/vite/plugins/virtualEntryPlugin.test.ts +1 -1
- package/src/vite/plugins/virtualEntryPlugin.ts +139 -122
- package/src/vite/plugins/warmRoutesPlugin.ts +36 -10
- package/src/vite/types.ts +6 -1
- package/types/layouts/Drawer.d.ts +50 -50
- package/types/layouts/Stack.d.ts +42 -42
- package/types/layouts/Tabs.d.ts +42 -42
- package/types/router/web/WebStackView.d.ts.map +1 -1
- package/types/ui/TabContext.d.ts +6 -6
- package/types/ui/TabContext.d.ts.map +1 -1
- package/types/ui/Tabs.shared.d.ts +12 -12
- package/types/vite/one.d.ts.map +1 -1
- package/types/vite/plugins/bundledDevPlugin.d.ts.map +1 -1
- package/types/vite/plugins/criticalCSSPlugin.d.ts.map +1 -1
- package/types/vite/plugins/environmentGuardPlugin.d.ts.map +1 -1
- package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
- package/types/vite/plugins/imageDataPlugin.d.ts.map +1 -1
- package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
- package/types/vite/plugins/warmRoutesPlugin.d.ts.map +1 -1
- package/types/vite/types.d.ts +6 -1
- package/types/vite/types.d.ts.map +1 -1
|
@@ -35,29 +35,17 @@ describe('imageDataPlugin', () => {
|
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
describe('resolveId', () => {
|
|
38
|
-
|
|
38
|
+
// these ids are now rejected by the rust-side hook filter, so the handler
|
|
39
|
+
// never runs for them. assert the filter itself does that.
|
|
40
|
+
it('filters out ids without a trailing ?imagedata', async () => {
|
|
39
41
|
const { imageDataPlugin } = await import('./imageDataPlugin')
|
|
40
42
|
const plugin = imageDataPlugin()
|
|
43
|
+
const filter = (plugin.resolveId as any).filter.id as RegExp
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
;(plugin.configResolved as any)(mockConfig())
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const result = await (plugin.resolveId as any)('./image.jpg', undefined)
|
|
47
|
-
expect(result).toBeNull()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('should return null for imports with imagedata not as suffix', async () => {
|
|
51
|
-
const { imageDataPlugin } = await import('./imageDataPlugin')
|
|
52
|
-
const plugin = imageDataPlugin()
|
|
53
|
-
|
|
54
|
-
if (plugin.configResolved) {
|
|
55
|
-
;(plugin.configResolved as any)(mockConfig())
|
|
56
|
-
}
|
|
57
|
-
|
|
45
|
+
expect(filter.test('./image.jpg')).toBe(false)
|
|
58
46
|
// ?imagedata in the middle should not match
|
|
59
|
-
|
|
60
|
-
expect(
|
|
47
|
+
expect(filter.test('./image?imagedata.jpg')).toBe(false)
|
|
48
|
+
expect(filter.test('./image.jpg?imagedata')).toBe(true)
|
|
61
49
|
})
|
|
62
50
|
|
|
63
51
|
it('should resolve public dir paths starting with /', async () => {
|
|
@@ -68,7 +56,7 @@ describe('imageDataPlugin', () => {
|
|
|
68
56
|
;(plugin.configResolved as any)(mockConfig())
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
const result = await (plugin.resolveId as any)(
|
|
59
|
+
const result = await (plugin.resolveId as any).handler(
|
|
72
60
|
'/test-image.jpg?imagedata',
|
|
73
61
|
undefined
|
|
74
62
|
)
|
|
@@ -83,7 +71,7 @@ describe('imageDataPlugin', () => {
|
|
|
83
71
|
;(plugin.configResolved as any)(mockConfig())
|
|
84
72
|
}
|
|
85
73
|
|
|
86
|
-
const result = await (plugin.resolveId as any)(
|
|
74
|
+
const result = await (plugin.resolveId as any).handler(
|
|
87
75
|
'./test-image.jpg?imagedata',
|
|
88
76
|
HERO_FILE
|
|
89
77
|
)
|
|
@@ -98,7 +86,7 @@ describe('imageDataPlugin', () => {
|
|
|
98
86
|
;(plugin.configResolved as any)(mockConfig())
|
|
99
87
|
}
|
|
100
88
|
|
|
101
|
-
const result = await (plugin.resolveId as any)(
|
|
89
|
+
const result = await (plugin.resolveId as any).handler(
|
|
102
90
|
'/nonexistent.jpg?imagedata',
|
|
103
91
|
undefined
|
|
104
92
|
)
|
|
@@ -115,7 +103,7 @@ describe('imageDataPlugin', () => {
|
|
|
115
103
|
;(plugin.configResolved as any)(mockConfig())
|
|
116
104
|
}
|
|
117
105
|
|
|
118
|
-
const result = await (plugin.resolveId as any)(
|
|
106
|
+
const result = await (plugin.resolveId as any).handler(
|
|
119
107
|
'/../../../etc/passwd?imagedata',
|
|
120
108
|
undefined
|
|
121
109
|
)
|
|
@@ -130,7 +118,7 @@ describe('imageDataPlugin', () => {
|
|
|
130
118
|
;(plugin.configResolved as any)(mockConfig())
|
|
131
119
|
}
|
|
132
120
|
|
|
133
|
-
const result = await (plugin.resolveId as any)(
|
|
121
|
+
const result = await (plugin.resolveId as any).handler(
|
|
134
122
|
'../../../../etc/passwd?imagedata',
|
|
135
123
|
HERO_FILE
|
|
136
124
|
)
|
|
@@ -145,7 +133,7 @@ describe('imageDataPlugin', () => {
|
|
|
145
133
|
;(plugin.configResolved as any)(mockConfig())
|
|
146
134
|
}
|
|
147
135
|
|
|
148
|
-
const result = await (plugin.resolveId as any)(
|
|
136
|
+
const result = await (plugin.resolveId as any).handler(
|
|
149
137
|
'../../../etc/passwd?imagedata',
|
|
150
138
|
undefined
|
|
151
139
|
)
|
|
@@ -162,7 +150,7 @@ describe('imageDataPlugin', () => {
|
|
|
162
150
|
|
|
163
151
|
// Going up and back down should still work if within bounds
|
|
164
152
|
// From /project/src/components, ../.. goes to /project, then src/test-image.jpg
|
|
165
|
-
const result = await (plugin.resolveId as any)(
|
|
153
|
+
const result = await (plugin.resolveId as any).handler(
|
|
166
154
|
'../../src/test-image.jpg?imagedata',
|
|
167
155
|
HERO_FILE
|
|
168
156
|
)
|
|
@@ -171,20 +159,13 @@ describe('imageDataPlugin', () => {
|
|
|
171
159
|
})
|
|
172
160
|
|
|
173
161
|
describe('load', () => {
|
|
174
|
-
it('
|
|
162
|
+
it('filters out non-virtual modules before the handler runs', async () => {
|
|
175
163
|
const { imageDataPlugin } = await import('./imageDataPlugin')
|
|
176
164
|
const plugin = imageDataPlugin()
|
|
165
|
+
const filter = (plugin.load as any).filter.id as RegExp
|
|
177
166
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const mockContext = {
|
|
183
|
-
addWatchFile: vi.fn(),
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const result = await (plugin.load as any).call(mockContext, './image.jpg')
|
|
187
|
-
expect(result).toBeNull()
|
|
167
|
+
expect(filter.test('./image.jpg')).toBe(false)
|
|
168
|
+
expect(filter.test('\0imagedata:/abs/path/image.jpg')).toBe(true)
|
|
188
169
|
})
|
|
189
170
|
|
|
190
171
|
it('should return fallback data when sharp fails', async () => {
|
|
@@ -203,7 +184,7 @@ describe('imageDataPlugin', () => {
|
|
|
203
184
|
|
|
204
185
|
// This will fail because the file doesn't actually exist
|
|
205
186
|
// But it should gracefully fallback
|
|
206
|
-
const result = await (plugin.load as any).call(
|
|
187
|
+
const result = await (plugin.load as any).handler.call(
|
|
207
188
|
mockContext,
|
|
208
189
|
'\0imagedata:' + testFilePath
|
|
209
190
|
)
|
|
@@ -248,7 +229,7 @@ describe('imageDataPlugin output format', () => {
|
|
|
248
229
|
|
|
249
230
|
const testFilePath = resolve(PUBLIC_DIR, 'test-image.jpg')
|
|
250
231
|
|
|
251
|
-
const result = await (plugin.load as any).call(
|
|
232
|
+
const result = await (plugin.load as any).handler.call(
|
|
252
233
|
mockContext,
|
|
253
234
|
'\0imagedata:' + testFilePath
|
|
254
235
|
)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { dirname, relative, resolve, sep } from 'node:path'
|
|
3
|
+
import { prefixRegex } from 'rolldown/filter'
|
|
3
4
|
import type { Plugin, ResolvedConfig } from 'vite'
|
|
4
5
|
import { normalizePath } from 'vite'
|
|
5
6
|
import { processImageMeta } from '../../image/getImageData'
|
|
6
7
|
|
|
7
8
|
const IMAGEDATA_SUFFIX = '?imagedata'
|
|
8
9
|
const VIRTUAL_PREFIX = '\0imagedata:'
|
|
10
|
+
// rust-side hook filter, exactly equivalent to the endsWith guard it replaced
|
|
11
|
+
const IMAGEDATA_SUFFIX_RE = /\?imagedata$/
|
|
9
12
|
|
|
10
13
|
export function imageDataPlugin(): Plugin {
|
|
11
14
|
let publicDir: string
|
|
@@ -36,55 +39,57 @@ export function imageDataPlugin(): Plugin {
|
|
|
36
39
|
root = resolvedConfig.root
|
|
37
40
|
},
|
|
38
41
|
|
|
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
|
-
|
|
42
|
+
resolveId: {
|
|
43
|
+
filter: { id: IMAGEDATA_SUFFIX_RE },
|
|
44
|
+
async handler(id, importer) {
|
|
45
|
+
const cleanId = id.slice(0, -IMAGEDATA_SUFFIX.length)
|
|
46
|
+
let filePath: string
|
|
47
|
+
let allowedDir: string
|
|
48
|
+
|
|
49
|
+
// Handle public dir paths (starting with /)
|
|
50
|
+
if (cleanId.startsWith('/')) {
|
|
51
|
+
filePath = resolve(publicDir, cleanId.slice(1))
|
|
52
|
+
allowedDir = publicDir
|
|
53
|
+
} else if (importer) {
|
|
54
|
+
// Handle relative imports
|
|
55
|
+
filePath = resolve(dirname(importer.replace(VIRTUAL_PREFIX, '')), cleanId)
|
|
56
|
+
allowedDir = root
|
|
57
|
+
} else {
|
|
58
|
+
filePath = resolve(root, cleanId)
|
|
59
|
+
allowedDir = root
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Security: prevent path traversal outside allowed directory
|
|
63
|
+
if (!isPathWithinBounds(filePath, allowedDir)) {
|
|
64
|
+
console.warn(`[one] ?imagedata: Path traversal blocked: ${cleanId}`)
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!existsSync(filePath)) {
|
|
69
|
+
console.warn(`[one] ?imagedata: File not found: ${filePath}`)
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return VIRTUAL_PREFIX + filePath
|
|
74
|
+
},
|
|
71
75
|
},
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
load: {
|
|
78
|
+
filter: { id: prefixRegex(VIRTUAL_PREFIX) },
|
|
79
|
+
async handler(id) {
|
|
80
|
+
const filePath = id.slice(VIRTUAL_PREFIX.length)
|
|
81
|
+
const src = getSrcPath(filePath)
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
// Track file for rebuild on change
|
|
84
|
+
this.addWatchFile(filePath)
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
const meta = await processImageMeta(filePath)
|
|
87
|
+
if (!meta) {
|
|
88
|
+
return createImageDataExport(src)
|
|
89
|
+
}
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return createImageDataExport(src)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return createImageDataExport(src, meta.width, meta.height, meta.blurDataURL)
|
|
91
|
+
return createImageDataExport(src, meta.width, meta.height, meta.blurDataURL)
|
|
92
|
+
},
|
|
88
93
|
},
|
|
89
94
|
}
|
|
90
95
|
}
|
|
@@ -20,6 +20,15 @@ import {
|
|
|
20
20
|
virtualEntryIdNative,
|
|
21
21
|
} from './virtualEntryConstants'
|
|
22
22
|
|
|
23
|
+
// rust-side hook filters, derived from the ids so they can't drift
|
|
24
|
+
const escapeRe = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
25
|
+
const VIRTUAL_ENTRY_SOURCE_RE = new RegExp(
|
|
26
|
+
`^(?:${escapeRe(virtualEntryId)}|${escapeRe(virtualEntryIdNative)})$`
|
|
27
|
+
)
|
|
28
|
+
const VIRTUAL_ENTRY_RESOLVED_RE = new RegExp(
|
|
29
|
+
`^(?:${escapeRe(resolvedVirtualEntryId)}|${escapeRe(resolvedVirtualEntryIdNative)})$`
|
|
30
|
+
)
|
|
31
|
+
|
|
23
32
|
type NormalizedSetupFiles = {
|
|
24
33
|
client?: string
|
|
25
34
|
server?: string
|
|
@@ -142,128 +151,135 @@ export function createVirtualEntry(options: {
|
|
|
142
151
|
isDevMode = config.command === 'serve'
|
|
143
152
|
},
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
// only the two virtual entry ids reach js; every other import in the graph
|
|
155
|
+
// is rejected rust-side
|
|
156
|
+
resolveId: {
|
|
157
|
+
filter: { id: VIRTUAL_ENTRY_SOURCE_RE },
|
|
158
|
+
handler(id) {
|
|
159
|
+
if (id === virtualEntryId) {
|
|
160
|
+
return resolvedVirtualEntryId
|
|
161
|
+
}
|
|
162
|
+
if (id === virtualEntryIdNative) {
|
|
163
|
+
return resolvedVirtualEntryIdNative
|
|
164
|
+
}
|
|
165
|
+
},
|
|
152
166
|
},
|
|
153
167
|
|
|
154
|
-
load
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
(file) =>
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
filename.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
load: {
|
|
169
|
+
filter: { id: VIRTUAL_ENTRY_RESOLVED_RE },
|
|
170
|
+
handler(id) {
|
|
171
|
+
if (id === resolvedVirtualEntryId) {
|
|
172
|
+
const isNative = isNativeEnvironment(this.environment)
|
|
173
|
+
const isSSR = this.environment.name === 'ssr'
|
|
174
|
+
const serverSpaRouteFiles = isSSR
|
|
175
|
+
? options.routeIndex
|
|
176
|
+
.getPaths()
|
|
177
|
+
.map((file) => file.replace(/^\.\//, ''))
|
|
178
|
+
.filter((file) => /\.tsx?$/.test(file))
|
|
179
|
+
.filter(
|
|
180
|
+
(file) => !micromatch.isMatch(file, ROUTE_WEB_EXCLUSION_GLOB_PATTERNS)
|
|
181
|
+
)
|
|
182
|
+
.filter((file) => {
|
|
183
|
+
const parts = file.split('/')
|
|
184
|
+
const filename = parts.at(-1) || ''
|
|
185
|
+
if (
|
|
186
|
+
filename.startsWith('_layout') ||
|
|
187
|
+
filename.startsWith('_middleware')
|
|
188
|
+
) {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
175
191
|
|
|
176
|
-
|
|
177
|
-
|
|
192
|
+
const fileMode = matchFileRenderMode(filename)
|
|
193
|
+
if (fileMode) return fileMode === 'spa'
|
|
178
194
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
let parentMode: One.RouteRenderMode | 'api' | undefined
|
|
196
|
+
for (const directory of parts.slice(0, -1)) {
|
|
197
|
+
parentMode =
|
|
198
|
+
matchDirectoryRenderMode(directory)?.renderMode ?? parentMode
|
|
199
|
+
}
|
|
200
|
+
return parentMode === 'spa'
|
|
201
|
+
})
|
|
202
|
+
: []
|
|
203
|
+
const serverSpaRouteStubs = serverSpaRouteFiles
|
|
204
|
+
.map(
|
|
205
|
+
(file) =>
|
|
206
|
+
`${JSON.stringify(`/${options.root}/${file}`)}: () => Promise.resolve({})`
|
|
207
|
+
)
|
|
208
|
+
.join(',\n ')
|
|
209
|
+
const serverSpaBuildGlobs = serverSpaRouteFiles.map(
|
|
210
|
+
(file) => `/${options.root}/${file}`
|
|
191
211
|
)
|
|
192
|
-
.
|
|
193
|
-
|
|
194
|
-
(file) => `/${options.root}/${file}`
|
|
195
|
-
)
|
|
196
|
-
const serverSpaBuildRoutes = serverSpaBuildGlobs.length
|
|
197
|
-
? `{
|
|
212
|
+
const serverSpaBuildRoutes = serverSpaBuildGlobs.length
|
|
213
|
+
? `{
|
|
198
214
|
loader: import.meta.glob(${JSON.stringify(serverSpaBuildGlobs)}, { exhaustive: true, import: 'loader', query: '?one-spa-build' }),
|
|
199
215
|
generateStaticParams: import.meta.glob(${JSON.stringify(serverSpaBuildGlobs)}, { exhaustive: true, import: 'generateStaticParams', query: '?one-spa-build' }),
|
|
200
216
|
sitemap: import.meta.glob(${JSON.stringify(serverSpaBuildGlobs)}, { exhaustive: true, import: 'sitemap', query: '?one-spa-build' })
|
|
201
217
|
}`
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
: '{}'
|
|
219
|
+
const webRouteGlobs = [
|
|
220
|
+
...routeGlobs,
|
|
221
|
+
...ROUTE_WEB_EXCLUSION_GLOB_PATTERNS.map((pattern) => `!${pattern}`),
|
|
222
|
+
...serverSpaRouteFiles.map((file) => `!/${options.root}/${file}`),
|
|
223
|
+
]
|
|
224
|
+
// native always needs static import. SSR in dev mode uses static import
|
|
225
|
+
// so Vite crawls the setupFile's dep tree (including react) and pre-bundles
|
|
226
|
+
// them before the first request. SSR in build mode uses lazy import so the
|
|
227
|
+
// setupFile doesn't execute during the build phase.
|
|
228
|
+
const useStaticImport = isNative || (isSSR && isDevMode)
|
|
229
|
+
const setupResult = getSetupFileImport(
|
|
230
|
+
this.environment.name,
|
|
231
|
+
setupFiles,
|
|
232
|
+
useStaticImport,
|
|
233
|
+
viteRoot
|
|
234
|
+
)
|
|
235
|
+
// When nativewind is enabled, import the components module to register Text, View, etc. with cssInterop
|
|
236
|
+
const nativewindImport = configuration.enableNativewind
|
|
237
|
+
? `import 'react-native-css-interop/dist/runtime/components'`
|
|
238
|
+
: ''
|
|
239
|
+
// For web/SSR, pass getSetupPromise to createApp so it can call it at runtime
|
|
240
|
+
const setupPromiseArg = setupResult.promiseVarName
|
|
241
|
+
? `getSetupPromise: ${setupResult.promiseVarName},`
|
|
242
|
+
: ''
|
|
243
|
+
const linkingArg = options.router?.linking
|
|
244
|
+
? `linking: ${JSON.stringify(options.router.linking)},`
|
|
245
|
+
: ''
|
|
230
246
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
247
|
+
// the react-refresh preamble has to be installed before the first
|
|
248
|
+
// compiler-wrapped module evaluates, or that module throws "React refresh
|
|
249
|
+
// preamble was not loaded" and client init aborts. /@one/dev.js installs
|
|
250
|
+
// it, but it cannot be relied on to win: it is a DEFERRED module script,
|
|
251
|
+
// so it waits for the document to finish parsing, while the client entry
|
|
252
|
+
// is async and runs the moment it arrives. that race is decided by
|
|
253
|
+
// document size. a SPA shell is a few hundred bytes and dev.js wins; a
|
|
254
|
+
// prerendered +ssg/ssr page is hundreds of KB and the entry regularly
|
|
255
|
+
// wins, throws on the first route module, and leaves the page unhydrated
|
|
256
|
+
// and unthemed on the very same server. bundled dev has no working
|
|
257
|
+
// /@one/dev.js at all, since it imports /@vite/client, which does not
|
|
258
|
+
// exist there.
|
|
259
|
+
// so install it from the entry, whose body is the one point guaranteed to
|
|
260
|
+
// run before the lazily-globbed route modules in every dev client. the
|
|
261
|
+
// !$RefreshReg$ guard makes this a no-op when dev.js already won, and
|
|
262
|
+
// dev.js keeps its own install because injectIntoGlobalHook wants to run
|
|
263
|
+
// before react evaluates. ('one' and /@react-refresh are node_modules /
|
|
264
|
+
// the runtime, so neither is refresh-wrapped, and on the client the setup
|
|
265
|
+
// file is imported lazily rather than statically.)
|
|
266
|
+
// this must stay gated to a dev CLIENT. `isBundled` is true for EVERY
|
|
267
|
+
// environment during build, so it cannot gate this on its own, and a prod
|
|
268
|
+
// build emitting an import of /@react-refresh would reference a module
|
|
269
|
+
// that only exists while serving.
|
|
270
|
+
const isDevClient =
|
|
271
|
+
this.environment.mode === 'dev' && this.environment.name === 'client'
|
|
272
|
+
const refreshPreambleImport = isDevClient
|
|
273
|
+
? `import { injectIntoGlobalHook as __oneInjectRefresh } from '/@react-refresh'`
|
|
274
|
+
: ''
|
|
275
|
+
const refreshPreambleSetup = isDevClient
|
|
276
|
+
? `if (typeof window !== 'undefined' && !window.$RefreshReg$) {
|
|
261
277
|
__oneInjectRefresh(window)
|
|
262
278
|
window.$RefreshReg$ = () => {}
|
|
263
279
|
window.$RefreshSig$ = () => (type) => type
|
|
264
280
|
}`
|
|
265
|
-
|
|
266
|
-
|
|
281
|
+
: ''
|
|
282
|
+
return `
|
|
267
283
|
${setupResult.promiseDeclaration}
|
|
268
284
|
${nativewindImport}
|
|
269
285
|
${refreshPreambleImport}
|
|
@@ -299,20 +315,20 @@ export default createApp({
|
|
|
299
315
|
${linkingArg}
|
|
300
316
|
})
|
|
301
317
|
`
|
|
302
|
-
|
|
318
|
+
}
|
|
303
319
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
320
|
+
if (id === resolvedVirtualEntryIdNative) {
|
|
321
|
+
const isNative = isNativeEnvironment(this.environment)
|
|
322
|
+
const setupResult = getSetupFileImport(
|
|
323
|
+
this.environment.name,
|
|
324
|
+
setupFiles,
|
|
325
|
+
isNative,
|
|
326
|
+
viteRoot
|
|
327
|
+
)
|
|
328
|
+
const linkingArg = options.router?.linking
|
|
329
|
+
? `linking: ${JSON.stringify(options.router.linking)},`
|
|
330
|
+
: ''
|
|
331
|
+
return `
|
|
316
332
|
import { createApp } from 'one'
|
|
317
333
|
${setupResult.importStatement}
|
|
318
334
|
|
|
@@ -324,7 +340,8 @@ export default createApp({
|
|
|
324
340
|
${linkingArg}
|
|
325
341
|
})
|
|
326
342
|
`
|
|
327
|
-
|
|
343
|
+
}
|
|
344
|
+
},
|
|
328
345
|
},
|
|
329
346
|
}
|
|
330
347
|
}
|
|
@@ -53,22 +53,48 @@ export function autoWarmPlugin(persistPath?: string): Plugin {
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
|
|
56
|
-
configResolved(config: ResolvedConfig) {
|
|
57
|
-
// build the exclude set from the resolved config
|
|
56
|
+
async configResolved(config: ResolvedConfig) {
|
|
58
57
|
excludeSet = new Set(config.optimizeDeps.exclude || [])
|
|
59
58
|
|
|
60
|
-
if (cachedDeps.length > 0
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
if (cachedDeps.length > 0) {
|
|
60
|
+
const resolve = config.createResolver()
|
|
61
|
+
const staleDeps = new Set<string>()
|
|
62
|
+
|
|
63
|
+
await Promise.all(
|
|
64
|
+
cachedDeps.map(async (dep) => {
|
|
65
|
+
if (excludeSet.has(dep)) {
|
|
66
|
+
staleDeps.add(dep)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
if (!(await resolve(dep))) staleDeps.add(dep)
|
|
72
|
+
} catch {
|
|
73
|
+
staleDeps.add(dep)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (staleDeps.size > 0) {
|
|
79
|
+
cachedDeps = cachedDeps.filter((dep) => !staleDeps.has(dep))
|
|
80
|
+
console.info(`[one] pruned ${staleDeps.size} stale deps from warm cache`)
|
|
65
81
|
|
|
66
|
-
// mutate the resolved include to remove conflicts
|
|
67
82
|
if (config.optimizeDeps.include) {
|
|
68
|
-
|
|
69
|
-
(
|
|
83
|
+
config.optimizeDeps.include = config.optimizeDeps.include.filter(
|
|
84
|
+
(dep) => !staleDeps.has(dep)
|
|
70
85
|
)
|
|
71
86
|
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const nextRaw = JSON.stringify({ deps: [...cachedDeps].sort() }, null, 2)
|
|
90
|
+
if (!existsSync(cacheFile) || readFileSync(cacheFile, 'utf-8') !== nextRaw) {
|
|
91
|
+
const dir = dirname(cacheFile)
|
|
92
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
93
|
+
writeFileSync(cacheFile, nextRaw)
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// a future snapshot can retry the cache write
|
|
97
|
+
}
|
|
72
98
|
}
|
|
73
99
|
}
|
|
74
100
|
},
|
package/src/vite/types.ts
CHANGED
|
@@ -546,11 +546,16 @@ export namespace One {
|
|
|
546
546
|
suspendRoutes?: boolean
|
|
547
547
|
|
|
548
548
|
/**
|
|
549
|
-
* Experimental: serve the web client through Vite
|
|
549
|
+
* Experimental: serve the web client through Vite's bundled dev mode
|
|
550
550
|
* (rolldown-powered FullBundleDevEnvironment) instead of unbundled ESM dev.
|
|
551
551
|
* Fewer requests / faster parse, especially for apps with large icon/util
|
|
552
552
|
* imports. Dev-only; ignored for builds.
|
|
553
553
|
*
|
|
554
|
+
* Covered by tests/test-bundled-dev. It is easy to break without noticing,
|
|
555
|
+
* because a broken client bundle still serves a 200 with correct SSR html
|
|
556
|
+
* and only fails to hydrate, so that test asserts interactivity rather
|
|
557
|
+
* than response status.
|
|
558
|
+
*
|
|
554
559
|
* @experimental
|
|
555
560
|
* @default false
|
|
556
561
|
*/
|