Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions packages/one/src/createHandleRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,76 @@ describe('createHandleRequest', () => {
})
})

describe('loader responses for ?platform=native requests', () => {
// in dev, useLoader on native fetches loaders with ?platform=native and
// runs the response body as CJS on hermes - a web ESM body
// ("export function loader()...") is a parse error there, which the
// client swallows (the route silently loses its loader data)

it('should serve loader bodies as CJS for platform=native', async () => {
const mockHandlersWithLoader = {
handlePage: vi.fn().mockResolvedValue('<html></html>'),
handleLoader: vi
.fn()
.mockResolvedValue('export function loader() { return {"a":1} }'),
}
const { handler } = createHandleRequest(mockHandlersWithLoader, {
routerRoot: '/app',
})

const response = (await handler(
createRequest('/assets/profile_123_vxrn_loader.js?platform=native')
)) as Response | null

expect(response).not.toBeNull()
const body = await response!.text()
expect(body).toContain('exports.loader=')
expect(body).not.toContain('export function loader')
})

it('should serve redirect loader bodies as CJS for platform=native', async () => {
const mockHandlersWithLoader = {
handlePage: vi.fn().mockResolvedValue('<html></html>'),
handleLoader: vi.fn().mockRejectedValue(
new Response(null, {
status: 302,
headers: { location: '/login' },
})
),
}
const { handler } = createHandleRequest(mockHandlersWithLoader, {
routerRoot: '/app',
})

const response = (await handler(
createRequest('/assets/profile_123_vxrn_loader.js?platform=native')
)) as Response | null

expect(response).not.toBeNull()
const body = await response!.text()
expect(body).toContain('exports.loader=')
expect(body).toContain('__oneRedirect')
})

it('should keep serving ESM loader bodies to web requests', async () => {
const esmBody = 'export function loader() { return {"a":1} }'
const mockHandlersWithLoader = {
handlePage: vi.fn().mockResolvedValue('<html></html>'),
handleLoader: vi.fn().mockResolvedValue(esmBody),
}
const { handler } = createHandleRequest(mockHandlersWithLoader, {
routerRoot: '/app',
})

const response = (await handler(
createRequest('/assets/profile_123_vxrn_loader.js')
)) as Response | null

expect(response).not.toBeNull()
expect(await response!.text()).toBe(esmBody)
})
})

describe('dynamic route matching', () => {
it('should match dynamic routes for regular paths without extensions', async () => {
const { handler } = createHandleRequest(mockHandlers, { routerRoot: '/app' })
Expand Down
3 changes: 2 additions & 1 deletion packages/one/src/createHandleRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ export async function resolveLoaderRoute(

const isNativeRequest =
url.searchParams.get('platform') === 'ios' ||
url.searchParams.get('platform') === 'android'
url.searchParams.get('platform') === 'android' ||
url.searchParams.get('platform') === 'native'

const response = await runMiddlewares(handlers, request, route, async () => {
return await resolveResponse(async () => {
Expand Down
19 changes: 14 additions & 5 deletions packages/one/src/vite/plugins/fileSystemRouterPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -485,11 +485,22 @@ export function createFileSystemRouterPlugin(
throw new Error(`No transformed js returned`)
}

const platform = url.searchParams.get('platform')
const isNativeRequest =
platform === 'ios' || platform === 'android' || platform === 'native'

// the client tree-shake plugin replaces loader exports with stubs
// like "export function loader()". if no stub exists, this route has
// no loader - skip the SSR module import to avoid evaluating modules
// with potentially SSR-incompatible deps (e.g. tamagui in SSR)
if (!/export function loader\(\)/.test(transformedJS)) {
// with potentially SSR-incompatible deps (e.g. tamagui in SSR).
// never take this shortcut for native requests: native runs loader
// responses as CJS on hermes, so returning the vite-transformed web
// ESM (import statements + HMR preamble) is unparseable there and
// silently drops the route's loader data. the stub can also be
// legitimately absent for loader shapes the tree-shaker doesn't
// rewrite (e.g. `export { x as loader } from './loaders'`), which the
// module runner import below resolves fine.
if (!isNativeRequest && !/export function loader\(\)/.test(transformedJS)) {
return transformedJS
}

Expand Down Expand Up @@ -562,9 +573,7 @@ export function createFileSystemRouterPlugin(
})
}

const platform = url.searchParams.get('platform')

if (platform === 'ios' || platform === 'android' || platform === 'native') {
if (isNativeRequest) {
// Need to transpile to CommonJS for React Native

const environment =
Expand Down
Loading