-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add function for creating server component (#1)
* feat: add server component types * feat: add function to create server component * feat: add axios as dependency * feat: module exports
- Loading branch information
1 parent
350b89c
commit 0d30ee4
Showing
7 changed files
with
274 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export type RSCConfig = { | ||
readonly global?: any; | ||
}; | ||
|
||
export type RSCSource = | ||
| { | ||
readonly uri: string; | ||
} | ||
| string; | ||
|
||
export type RSCActions = 'NAVIGATE' | 'IO' | 'STATE_CHANGE'; | ||
|
||
export type RSCProps = { | ||
readonly source: RSCSource; | ||
readonly fallbackComponent?: () => JSX.Element; | ||
readonly loadingComponent?: () => JSX.Element; | ||
readonly errorComponent?: () => JSX.Element; | ||
readonly onError?: (error: Error) => void; | ||
readonly navigationRef?: React.Ref<any>; | ||
readonly onAction?: ( | ||
action: RSCActions, | ||
payload: Record<string, any> | ||
) => void; | ||
readonly openRSC?: (source: RSCSource) => Promise<React.Component>; | ||
}; | ||
|
||
export type RSCPromise<T> = { | ||
readonly resolve: (result: T) => void; | ||
readonly reject: (error: Error) => void; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import * as React from 'react'; | ||
import { RSCProps } from '../@types'; | ||
|
||
export default function RSC({ | ||
source, | ||
openRSC, | ||
fallbackComponent, | ||
loadingComponent, | ||
errorComponent, | ||
...extras | ||
}: RSCProps): JSX.Element { | ||
const [ServerComponent, setServerComponent] = | ||
React.useState<React.Component | null>(null); | ||
|
||
const [error, setError] = React.useState<Error | null>(null); | ||
|
||
React.useEffect(() => { | ||
(async () => { | ||
try { | ||
if (typeof openRSC === 'function') { | ||
const rsc = await openRSC(source); | ||
return setServerComponent(() => rsc); | ||
} | ||
throw new Error(`[ServerComponent]: typeof openRSC should be function`); | ||
} catch (e) { | ||
setServerComponent(() => null); | ||
setError(e); | ||
} | ||
})(); | ||
}, [source, openRSC]); | ||
|
||
const FallbackComponent = React.useCallback((): JSX.Element => { | ||
if (fallbackComponent) { | ||
return fallbackComponent(); | ||
} | ||
return <></>; | ||
}, [fallbackComponent]); | ||
|
||
if (typeof ServerComponent === 'function') { | ||
return ( | ||
<React.Fragment> | ||
<React.Suspense fallback={<FallbackComponent />} /> | ||
{/* @ts-ignore */} | ||
<ServerComponent {...extras} /> | ||
</React.Fragment> | ||
); | ||
} else if (error) { | ||
return errorComponent(); | ||
} | ||
return loadingComponent(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* eslint-disable no-new-func */ | ||
import * as React from 'react'; | ||
import type { RSCPromise, RSCConfig, RSCProps, RSCSource } from '../@types'; | ||
import RSC from './ServerComponent'; | ||
import axios, { type AxiosRequestConfig } from 'axios'; | ||
|
||
const defaultGlobal = Object.freeze({ | ||
require: (moduleId: string) => { | ||
if (moduleId === 'react') { | ||
// @ts-ignore | ||
return require('react'); | ||
} else if (moduleId === 'react-native') { | ||
// @ts-ignore | ||
return require('react-native'); | ||
} | ||
return null; | ||
}, | ||
}); | ||
|
||
const createComponent = | ||
(global: any) => | ||
async (src: string): Promise<React.Component> => { | ||
const globalName = '__SERVER_COMPONENT__'; | ||
const Component = await new Function( | ||
globalName, | ||
`${Object.keys(global) | ||
.map((key) => `var ${key} = ${globalName}.${key};`) | ||
.join('\n')}; const exports = {}; ${src}; return exports.default` | ||
)(global); | ||
|
||
return Component; | ||
}; | ||
|
||
const axiosRequest = (config: AxiosRequestConfig) => axios(config); | ||
|
||
const buildRSC = | ||
({ | ||
openURI, | ||
}: { | ||
readonly openURI: ( | ||
uri: string, | ||
callback: RSCPromise<React.Component> | ||
) => void; | ||
}) => | ||
async (source: RSCSource): Promise<React.Component> => { | ||
// TODO handle string source | ||
if (source && typeof source === 'object') { | ||
const { uri } = source; | ||
// TODO handle uri validation | ||
if (typeof uri === 'string') { | ||
return new Promise<React.Component>((resolve, reject) => | ||
openURI(uri, { resolve, reject }) | ||
); | ||
} | ||
} | ||
}; | ||
|
||
const buildRequest = | ||
({ | ||
component, | ||
}: { | ||
readonly component: (src: string) => Promise<React.Component>; | ||
}) => | ||
async (uri: string) => { | ||
//const handler = completionHandler(); | ||
|
||
try { | ||
const result = await axiosRequest({ url: uri, method: 'get' }); | ||
const { data } = result; | ||
if (typeof data !== 'string') { | ||
throw new Error( | ||
`[ServerComponent]: Expected string data, encountered ${typeof data}` | ||
); | ||
} | ||
|
||
component(data); | ||
} catch (e) { | ||
console.log(`[ServerComponent]: Build Request caught error ${e}`); | ||
} | ||
}; | ||
|
||
const buildURIForRSC = | ||
({ uriRequest }: { readonly uriRequest: (uri: string) => void }) => | ||
(uri: string, callback: RSCPromise<React.Component>): void => { | ||
const { resolve, reject } = callback; | ||
Check failure on line 85 in src/component/createServerComponent.tsx GitHub Actions / lint
|
||
// TODO: handle caching and queueing here | ||
return uriRequest(uri); | ||
}; | ||
|
||
export default function createServerComponent({ | ||
global = defaultGlobal, | ||
}: RSCConfig) { | ||
//const handler = completionHandler(); | ||
|
||
const component = createComponent(global); | ||
|
||
const uriRequest = buildRequest({ component }); | ||
|
||
const openURI = buildURIForRSC({ uriRequest }); | ||
|
||
const openRSC = buildRSC({ openURI }); | ||
|
||
const ServerComponent = (props: RSCProps) => ( | ||
<RSC {...props} openRSC={openRSC} /> | ||
); | ||
|
||
return Object.freeze({ | ||
ServerComponent, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as createServerComponent } from './createServerComponent'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,25 @@ | ||
import { NativeModules, Platform } from 'react-native'; | ||
// import { NativeModules, Platform } from 'react-native'; | ||
|
||
const LINKING_ERROR = | ||
`The package 'react-native-server-component' doesn't seem to be linked. Make sure: \n\n` + | ||
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + | ||
'- You rebuilt the app after installing the package\n' + | ||
'- You are not using Expo Go\n'; | ||
// const LINKING_ERROR = | ||
// `The package 'react-native-server-component' doesn't seem to be linked. Make sure: \n\n` + | ||
// Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + | ||
// '- You rebuilt the app after installing the package\n' + | ||
// '- You are not using Expo Go\n'; | ||
|
||
const ServerComponent = NativeModules.ServerComponent | ||
? NativeModules.ServerComponent | ||
: new Proxy( | ||
{}, | ||
{ | ||
get() { | ||
throw new Error(LINKING_ERROR); | ||
}, | ||
} | ||
); | ||
// const ServerComponent = NativeModules.ServerComponent | ||
// ? NativeModules.ServerComponent | ||
// : new Proxy( | ||
// {}, | ||
// { | ||
// get() { | ||
// throw new Error(LINKING_ERROR); | ||
// }, | ||
// } | ||
// ); | ||
|
||
export function multiply(a: number, b: number): Promise<number> { | ||
return ServerComponent.multiply(a, b); | ||
} | ||
// export function multiply(a: number, b: number): Promise<number> { | ||
// return ServerComponent.multiply(a, b); | ||
// } | ||
|
||
export * from './@types'; | ||
export * from './component'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters