How it works
This guide is optional and provides a high-level explanation of how to render a remote component using ReChunk. There are two main parts to this library - the client and the command-line interface (CLI). The CLI is the precursor to actually being able to render a remote component via the client.
Command-line Interface
The CLI has 5 commands currently: dev-server
, init
, list
, publish
and unpublish
. The commands allow you to actively integrate with a remote ReChunk service to bundle and cache your components. In this guide only 3 commands will be discuused: dev-server
init
and publish
- as this guide is more high-level and explaining how this library works.
Initialization
The ReChunk library relies almost completely on a configuration file to ease the integration process. This configuration is denoted by the rechunk.json
file. In the example app of this repo there is already a rechunk.json
configuration file generated for anybody to test out this library without a remote ReChunk service. To generate a rechunk.json
configuration file utilize the init
command - this will generate a new ReChunk project to publish components to.
yarn rechunk init --host http://localhost:3000 --username rechunk --password aC00Lpr0ject
npm run rechunk init --host http://localhost:3000 --username rechunk --password aC00Lpr0ject
pnpm rechunk init --host http://localhost:3000 --username rechunk --password aC00Lpr0ject
bun run rechunk init --host http://localhost:3000 --username rechunk --password aC00Lpr0ject
In the above example command usage there are three required options: host
, username
and password
. The host
is where the ReChunk service is being hosted, the username
and password
are the basic authentication credentials that the project creation endpoint abides by. Once the command is complete a rechunk.json
configuration file will be generated in the root of your React Native project - it will look similar to the following file.
{ "$schema": "https://crherman7.github.io/rechunk/schema.json", "project": "prototypical-pigs-interlay", "readKey": "read-5456fcad-99bd-4328-bfc4-0cb597ffbc4d", "writeKey": "write-9bd47fc2-323e-4f74-9082-2abfb86582b4", "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArk92wo3nc7AShy2c860h\nojRACKTnYGbB1NPRezyZ40Lk9p/myZ02ZpmVDGlMtWgqgIOoufUwPTa9LkmjFUgg\nYkhDHZzokiJjZ9PseRXr4+63p8MSvOmQvoQMqSO2mBEuXTks4tTNx/AySWqV9GDC\nMeM+VFKtQULuxgQSNJ1LDIu8ofpeKDB2bivV8phrLzRSM4TWuCu9u9gWxsUbTihR\nLyicJvmx44NBmdr9N0WIOh/15vAYieiY8MsZE7B8St0x5jgDT4taUzTd8R0RyDw/\nOtpCeOIDGwmz7h0Rx2r0Q+WOuRL4MSbGBJuT3TJoWkOPCrhfH3Sc6a61NyoPeiDZ\nXQIDAQAB\n-----END PUBLIC KEY-----\n", "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCuT3bCjedzsBKH\nLZzzrSGiNEAIpOdgZsHU09F7PJnjQuT2n+bJnTZmmZUMaUy1aCqAg6i59TA9Nr0u\nSaMVSCBiSEMdnOiSImNn0+x5Fevj7renwxK86ZC+hAypI7aYES5dOSzi1M3H8DJJ\napX0YMIx4z5UUq1BQu7GBBI0nUsMi7yh+l4oMHZuK9XymGsvNFIzhNa4K7272BbG\nxRtOKFEvKJwm+bHjg0GZ2v03RYg6H/Xm8BiJ6JjwyxkTsHxK3THmOANPi1pTNN3x\nHRHIPD862kJ44gMbCbPuHRHHavRD5Y65EvgxJsYEm5PdMmhaQ48KuF8fdJzprrU3\nKg96INldAgMBAAECggEAQfVhzAbkYRtsN2ikLnUB+B3raRn1T74ydHVenFJ3LM7g\nUw54xyvNLQ/KnbKuyypKguzPCOblxRQUjBJjOqdbUbVkaC06uCl5Eg3YOo14AH0R\nU9L2ITQEiILxQ4N3EZ3WvIHpIUBDSffzH2LMsXd3pGcH6+KJGtvX/GgH947Zmjoa\nXOiu3hw+E1qZaUTjWgGYmT+ph+8gHW34ner5JS4m8tqkNtP0U2DJIVD7MR5i/jp3\nzv19/foEUew8wvs4R4GcvS3xGETebg4H6DonxJ79HtXe3jzigreOLYybjyw4zRFv\nYHDh9qRCL20Qydq7Ogg963Yur1Ecys2faeehNp8pxQKBgQDUNreN8wMYU4/qyLmi\n7wHQ9rr/pqqcNsLsBRSlwXKsK7ffwSjvx+aUOzucnlHUrLpJtllT551aWw8EY4Rx\nGI7iTwCPCKK7s6laZeXSrSKWgXibTuCn/1FJ8dZbFHKhWaXQW2QFSSNRzqp41FhB\ncZXs9cS0Yf6issvtiP7hAX7N/wKBgQDSRqpdl/eX2t4vMYbdfHvvtzy01MccKrQ2\nhADX/PgVAe/8n1/vxftK5cKAUapq0gpS8/1mRuTeRZ9a7HAZvpNuYEKhlRsSOzNg\nYYqNmq9F33kxRjsdm6RrYMMyk8v8pIuyFTaZY+MdIRDt306a2VK4EAQxS5G+wTr6\nOgOM4fxQowKBgGawZ5gf7PJ7lRu2r+XBJC7bBdgp7UsUULRnLPucpYHc0ehLjySg\npdo643jBP7XbM3Xx8D3iyUjs4VJtWaxkhtfKBOox8pVDtgKRZmnQ7/jGg/cbbyi8\nKrjOdryyI1oiiFNPthG8t4OhruozTtW0QildMCddeBuAy5+Q+E0nLRY/AoGAFxXR\nAGN9uDs8J1w9nTVcee3ZCVVO4sXBcTa+zPel2NCUo1xv8OgAdbGRz/qnRgD3RzIo\nQMFJwSlNnHLWv8zPbM++oPS4uyCqvEsZJgC0e2xUZtd6B/8dZviBlZzqSTtbZtqz\nWtW/imQl8qeQfqmbTj/b5fmQ76tRKUWTPDVXORcCgYEArx0LII/G1luLj/z2bvhz\nA5OXyUL7ka2wJqmpb0XdFP+PPIsud5GXf2uG9GTkhBHnwuakXW8UdB/4pwEtAYRS\nGndITg+KPDwTvuz6P51MQM6wd1922UKjTl3BY+7CzaWCuBGV/yfFjpsWtoSQJvSY\nDCwpHpF7v/6fhQU/Y2ZnI84=\n-----END PRIVATE KEY-----\n", "entry": {}, "external": [], "host": "http://localhost:3000"}
This configuration file contains both public and private information, it’s very important that the writeKey
and privateKey
are not leaked outside of your repository. The writeKey
is responsible for manipulating your cached components and the privateKey
is responsible for generating a digital signature.
At this point a component can now be bundled to be cached via the ReChunk service. Update the entry
value with a key and path relative to the root of the project.
{ "$schema": "https://crherman7.github.io/rechunk/schema.json", "project": "prototypical-pigs-interlay", "readKey": "read-5456fcad-99bd-4328-bfc4-0cb597ffbc4d", "writeKey": "write-9bd47fc2-323e-4f74-9082-2abfb86582b4", "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArk92wo3nc7AShy2c860h\nojRACKTnYGbB1NPRezyZ40Lk9p/myZ02ZpmVDGlMtWgqgIOoufUwPTa9LkmjFUgg\nYkhDHZzokiJjZ9PseRXr4+63p8MSvOmQvoQMqSO2mBEuXTks4tTNx/AySWqV9GDC\nMeM+VFKtQULuxgQSNJ1LDIu8ofpeKDB2bivV8phrLzRSM4TWuCu9u9gWxsUbTihR\nLyicJvmx44NBmdr9N0WIOh/15vAYieiY8MsZE7B8St0x5jgDT4taUzTd8R0RyDw/\nOtpCeOIDGwmz7h0Rx2r0Q+WOuRL4MSbGBJuT3TJoWkOPCrhfH3Sc6a61NyoPeiDZ\nXQIDAQAB\n-----END PUBLIC KEY-----\n", "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCuT3bCjedzsBKH\nLZzzrSGiNEAIpOdgZsHU09F7PJnjQuT2n+bJnTZmmZUMaUy1aCqAg6i59TA9Nr0u\nSaMVSCBiSEMdnOiSImNn0+x5Fevj7renwxK86ZC+hAypI7aYES5dOSzi1M3H8DJJ\napX0YMIx4z5UUq1BQu7GBBI0nUsMi7yh+l4oMHZuK9XymGsvNFIzhNa4K7272BbG\nxRtOKFEvKJwm+bHjg0GZ2v03RYg6H/Xm8BiJ6JjwyxkTsHxK3THmOANPi1pTNN3x\nHRHIPD862kJ44gMbCbPuHRHHavRD5Y65EvgxJsYEm5PdMmhaQ48KuF8fdJzprrU3\nKg96INldAgMBAAECggEAQfVhzAbkYRtsN2ikLnUB+B3raRn1T74ydHVenFJ3LM7g\nUw54xyvNLQ/KnbKuyypKguzPCOblxRQUjBJjOqdbUbVkaC06uCl5Eg3YOo14AH0R\nU9L2ITQEiILxQ4N3EZ3WvIHpIUBDSffzH2LMsXd3pGcH6+KJGtvX/GgH947Zmjoa\nXOiu3hw+E1qZaUTjWgGYmT+ph+8gHW34ner5JS4m8tqkNtP0U2DJIVD7MR5i/jp3\nzv19/foEUew8wvs4R4GcvS3xGETebg4H6DonxJ79HtXe3jzigreOLYybjyw4zRFv\nYHDh9qRCL20Qydq7Ogg963Yur1Ecys2faeehNp8pxQKBgQDUNreN8wMYU4/qyLmi\n7wHQ9rr/pqqcNsLsBRSlwXKsK7ffwSjvx+aUOzucnlHUrLpJtllT551aWw8EY4Rx\nGI7iTwCPCKK7s6laZeXSrSKWgXibTuCn/1FJ8dZbFHKhWaXQW2QFSSNRzqp41FhB\ncZXs9cS0Yf6issvtiP7hAX7N/wKBgQDSRqpdl/eX2t4vMYbdfHvvtzy01MccKrQ2\nhADX/PgVAe/8n1/vxftK5cKAUapq0gpS8/1mRuTeRZ9a7HAZvpNuYEKhlRsSOzNg\nYYqNmq9F33kxRjsdm6RrYMMyk8v8pIuyFTaZY+MdIRDt306a2VK4EAQxS5G+wTr6\nOgOM4fxQowKBgGawZ5gf7PJ7lRu2r+XBJC7bBdgp7UsUULRnLPucpYHc0ehLjySg\npdo643jBP7XbM3Xx8D3iyUjs4VJtWaxkhtfKBOox8pVDtgKRZmnQ7/jGg/cbbyi8\nKrjOdryyI1oiiFNPthG8t4OhruozTtW0QildMCddeBuAy5+Q+E0nLRY/AoGAFxXR\nAGN9uDs8J1w9nTVcee3ZCVVO4sXBcTa+zPel2NCUo1xv8OgAdbGRz/qnRgD3RzIo\nQMFJwSlNnHLWv8zPbM++oPS4uyCqvEsZJgC0e2xUZtd6B/8dZviBlZzqSTtbZtqz\nWtW/imQl8qeQfqmbTj/b5fmQ76tRKUWTPDVXORcCgYEArx0LII/G1luLj/z2bvhz\nA5OXyUL7ka2wJqmpb0XdFP+PPIsud5GXf2uG9GTkhBHnwuakXW8UdB/4pwEtAYRS\nGndITg+KPDwTvuz6P51MQM6wd1922UKjTl3BY+7CzaWCuBGV/yfFjpsWtoSQJvSY\nDCwpHpF7v/6fhQU/Y2ZnI84=\n-----END PRIVATE KEY-----\n", "entry": {"foo": "./src/shared/remote-ui/Remote.tsx"}, "external": [], "host": "http://localhost:3000"}
The publish
command can now be utilized to bundle this component to the ReChunk service.
yarn rechunk publish --chunk foo
npm run rechunk publish --chunk foo
pnpm rechunk publish --chunk foo
bun run rechunk publish --chunk foo
Once the command has finished the bundle is ready to be consumed by the client. If you are developing bundles and need to test them out before publishing them, that can be done via the dev-server
command.
yarn rechunk dev-server
npm run rechunk dev-server
pnpm rechunk dev-server
bun run rechunk dev-server
Once you start the React Native app, the client will now connect to the development server instead of the ReChunk service. Components will be bundled on the fly from the file-system when the client attempts to consume a bundle.
Client
The client library handles the process of consuming a bundled component, verifying data integrity and converting the bundle into a renderable component.
Bundle Consumption
After installing, configuration babel, generating a ReChunk project and publishing a component - the published component can now be consumed by the client. The out-of-the-box experience provided by the client does not require any runtime configuration (not to be mistaken by compile time configuration via the rechunk.json). The only reason to need a runtime configuration is if there is a necessity to override the resolver, global require function, public key or verification conditions. Otherwise, the babel configuration takes care of all code-generation to run the client without runtime configuration.
In order to consume the ReChunk bundle the default resolver will fetch the bundle from the host denoted in the rechunk.json
configuration.
export async function(chunkId: string) { try { const response = await fetch( `${process.env.RECHUNK_HOST}/chunk/${chunkId}`, { method: 'GET', headers: { Authorization: `Basic ${btoa( `${process.env.RECHUNK_PROJECT}:${process.env.RECHUNK_READ_KEY}`, )}`, }, }, );
if (!response.ok) { throw new Error(`Failed to fetch chunk with ID ${chunkId}`); }
return response.json(); } catch (error: any) { throw new Error(`Failed to fetch chunk: ${error.message}`); }}
Once the bundle is consumed the client will then attempt to verify the data and render the component. If there are any errors, they will be hoisted to the application and will need to be handled - the recommended approache for this is via an error boundary.
Data Verification
Ensuring data integrity is crucial because this library executes JavaScript; when fetching a chunk, there’s a risk of tampering to execute malicious code, underscoring the need for data integrity verification. This library employs digital signature verification for this purpose.
Digital signatures are mathematical tools used to confirm the authenticity of digital messages or documents. A valid digital signature assures the recipient that the sender is trusted. This library uses encoded data, hashed data, and a public key to verify authenticity. If verification succeeds, the component is rendered; otherwise, an error occurs.
Since there are very limited, if any, JavaScript libraries that provide digital signature verification - the verification occurs on the native iOS and Android platforms via a React Native bridge. Verification is enabled by default - if for some reason you choose to disable verification, highly unrecommended, this can be done via the ReChunk runtime configuration.
import ReChunk from '@crherman7/rechunk';
ReChunk.addConfiguration({verify: false});
Component Rendering
To render a component from a string, we leverage the new Function
JavaScript instance. This approach involves passing in a custom require
function, which is generated using Babel during compile time. The custom require
function is designed to inject all necessary dependencies.
The string component and custom require
function are then combined within the new Function
instance and returned as the default object. This technique enables efficient lazy loading of components.
import {importChunk} from '@crherman7/rechunk';
const Foo = React.lazy(() => importChunk('foo'));
export function App(): React.JSX.Element { return ( <ErrorBoundary FallbackComponent={Error404}> <Suspense fallback={<ActivityIndicator style={styles.container} />}> <Foo /> </Suspense> </ErrorBoundary> );}
The importChunk
method is the consumption, verification and component generation occurs. The importChunk
method is meant to work the same way i.e. function signature, as import
works with React.lazy
.