@backtickjs/react-native-client 0.1.8 → 0.1.10

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/README.md CHANGED
@@ -12,7 +12,7 @@ npm install @backtickjs/react-native-client
12
12
 
13
13
  ## Usage
14
14
 
15
- Give `evaluate` the modules a bundle may require, then fetch a screen and draw it. Hold the request, a promise, in state in a component that doesn't suspend, so it outlives the render that waits for it, and draw it with React's `use` under `<Suspense>`:
15
+ Give `evaluate` the module map: the modules a bundle may require, imported as the app starts. Then fetch a screen and draw it. Hold the request, a promise, in state in a component that doesn't suspend, so it outlives the render that waits for it, and draw it with React's `use` under `<Suspense>`:
16
16
 
17
17
  ```tsx
18
18
  import { evaluate } from "@backtickjs/react-native-client";
@@ -20,9 +20,10 @@ import * as React from "react";
20
20
  import { Suspense, use, useState } from "react";
21
21
  import * as JSXRuntime from "react/jsx-runtime";
22
22
  import * as ReactNative from "react-native";
23
+ import { ActivityIndicator } from "react-native";
23
24
 
24
25
  // What a screen may require: the packages this app was built with.
25
- const modules = {
26
+ const moduleMap = {
26
27
  react: React,
27
28
  "react/jsx-runtime": JSXRuntime,
28
29
  "react-native": ReactNative,
@@ -33,15 +34,13 @@ async function fetchScreen(url: string) {
33
34
  if (!response.ok) {
34
35
  throw new Error(`${url} answered ${response.status}.`);
35
36
  }
36
- return evaluate(await response.text(), modules) as React.ReactNode;
37
+ return evaluate(await response.text(), moduleMap) as React.ReactNode;
37
38
  }
38
39
 
39
40
  export default function App() {
40
- const [screen] = useState(() =>
41
- fetchScreen("https://api.example.com/screens/home"),
42
- );
41
+ const [screen] = useState(() => fetchScreen("https://api.example.com/"));
43
42
  return (
44
- <Suspense fallback={<ReactNative.ActivityIndicator />}>
43
+ <Suspense fallback={<ActivityIndicator />}>
45
44
  <Screen screen={screen} />
46
45
  </Suspense>
47
46
  );
@@ -54,13 +53,13 @@ function Screen({ screen }: { screen: Promise<React.ReactNode> }) {
54
53
 
55
54
  Wrap the `<Suspense>` in an error boundary to show a screen that couldn't be fetched or run. To fetch again, set a new promise in state. The React Native template of [create-backtick-app](https://github.com/backtickjs/backtick/tree/main/packages/create-backtick-app) has the full version, with the boundary and a refetch whenever the development server restarts.
56
55
 
57
- The server bundles for the versions the app runs: it passes them to `bundler.build` as `packageVersions`, so they should match the packages you put in `modules`.
56
+ The server bundles for the versions the app runs: it passes them to `bundler.build` as `modules`, so they should match the packages in your module map.
58
57
 
59
58
  ## API
60
59
 
61
- ### `evaluate(code: string, modules: Modules): unknown`
60
+ ### `evaluate(code: string, moduleMap: ModuleMap): unknown`
62
61
 
63
- Runs `code` as a CommonJS module, with `require` answered from `modules`, and returns what it exports: for a Backtick screen, the element to draw. A bundle that requires a specifier missing from `modules` throws:
62
+ Runs `code` as a CommonJS module, with `require` answered from `moduleMap`, and returns what it exports: for a Backtick screen, the element to draw. A bundle that requires a specifier missing from `moduleMap` throws:
64
63
 
65
64
  ```
66
65
  The bundle requires "expo-haptics", which this app doesn't provide.
@@ -68,13 +67,27 @@ The bundle requires "expo-haptics", which this app doesn't provide.
68
67
 
69
68
  Errors thrown while the bundle runs propagate to the caller.
70
69
 
71
- ### `Modules`
70
+ ### `ModuleMap`
71
+
72
+ ```ts
73
+ type ModuleMap = Readonly<Record<string, unknown>>;
74
+ ```
75
+
76
+ What a bundle may require, by specifier, each as the module the app imported for it. It does for an app what a web page's import map does, with modules for values instead of URLs. At least `react`, `react/jsx-runtime` and `react-native`; add any other package the app chooses to offer its screens. Imported at the top of a file, the modules load as the app starts, so one that fails to load fails then, rather than on the first screen that needs it.
77
+
78
+ ### `buildIdFrom(extra: unknown, platform: string): string`
79
+
80
+ This build's id on `platform`, read from the app config's `extra.buildIds`, which an `app.config.ts` fills with [`@backtickjs/builds`](https://github.com/backtickjs/backtick/tree/main/packages/builds). The id names the modules the build carries, so the server bundles only screens it can run. Send it to the server however the app reaches it, as a header or a GraphQL variable:
72
81
 
73
82
  ```ts
74
- type Modules = Readonly<Record<string, unknown>>;
83
+ import { buildIdFrom } from "@backtickjs/react-native-client";
84
+ import Constants from "expo-constants";
85
+ import { Platform } from "react-native";
86
+
87
+ const buildId = buildIdFrom(Constants.expoConfig?.extra, Platform.OS);
75
88
  ```
76
89
 
77
- What a bundle may require, by specifier, each as its module namespace. At least `react`, `react/jsx-runtime` and `react-native`; add any other package the app chooses to offer its screens.
90
+ It throws when the config has no build id for the platform, as the app starts: no screen could be drawn for a build without one.
78
91
 
79
92
  ## Security
80
93
 
@@ -84,7 +97,7 @@ What a bundle may require, by specifier, each as its module namespace. At least
84
97
 
85
98
  - [`@backtickjs/core`](https://github.com/backtickjs/backtick/tree/main/packages/core): `cs` and the types for writing screens
86
99
  - [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): builds the bundles this runs
87
- - [`@backtickjs/react-native`](https://github.com/backtickjs/backtick/tree/main/packages/react-native): React Native's components and APIs, to splice into scripts
100
+ - [`@backtickjs/react-native`](https://github.com/backtickjs/backtick/tree/main/adapters/react-native): React Native's components and APIs, to splice into scripts
88
101
  - [`create-backtick-app`](https://github.com/backtickjs/backtick/tree/main/packages/create-backtick-app): an Expo app with a Backtick server beside it
89
102
  - [How it works](https://backtickjs.com/docs/how-it-works)
90
103
  - [Errors](https://backtickjs.com/docs/errors)
package/dist/index.d.ts CHANGED
@@ -1,24 +1,39 @@
1
1
  /**
2
- * What a bundle may require, by specifier: the packages the app was built
3
- * with, each as its module. At least React, React Native and React's JSX
2
+ * What a bundle may import, by specifier: each the module the app imported
3
+ * for it. What a web page's import map does, for an app, with modules for
4
+ * values instead of URLs. At least React, React Native and React's JSX
4
5
  * runtime:
5
6
  *
6
7
  * import * as React from "react";
7
8
  * import * as JSXRuntime from "react/jsx-runtime";
8
9
  * import * as ReactNative from "react-native";
9
10
  *
10
- * const modules = {
11
- * "react": React,
11
+ * export const moduleMap = {
12
+ * react: React,
12
13
  * "react/jsx-runtime": JSXRuntime,
13
14
  * "react-native": ReactNative,
14
- * };
15
+ * } satisfies ModuleMap;
16
+ *
17
+ * Imported as the app starts, so a module that fails to load fails then, not
18
+ * on the first screen that needs it.
15
19
  */
16
- export type Modules = Readonly<Record<string, unknown>>;
20
+ export type ModuleMap = Readonly<Record<string, unknown>>;
17
21
  /**
18
- * A bundle's code, run as CommonJS with `require` answered from `modules`:
22
+ * A bundle's code, run as CommonJS with `require` answered from `moduleMap`:
19
23
  * what it exports, the screen to draw. What a web page's import map and
20
24
  * script tag do for a browser, this does for an app; fetching the bundle, and
21
25
  * when, is the app's. A bundle requiring a module the app doesn't provide
22
26
  * throws.
23
27
  */
24
- export declare function evaluate(code: string, modules: Modules): unknown;
28
+ export declare function evaluate(code: string, moduleMap: ModuleMap): unknown;
29
+ /**
30
+ * This build's id on `platform`, from the app config's `extra`, where an
31
+ * `app.config.ts` puts `buildIds` from `@backtickjs/builds`: it names the
32
+ * modules the build carries, for the server to bundle screens it can run.
33
+ * Send it however the app reaches its server: a header, a GraphQL variable.
34
+ * Throws for an app config without it, as the app starts: no screen could be
35
+ * drawn for a build without an id.
36
+ *
37
+ * const buildId = buildIdFrom(Constants.expoConfig?.extra, Platform.OS);
38
+ */
39
+ export declare function buildIdFrom(extra: unknown, platform: string): string;
package/dist/index.js CHANGED
@@ -1,19 +1,38 @@
1
1
  /**
2
- * A bundle's code, run as CommonJS with `require` answered from `modules`:
2
+ * A bundle's code, run as CommonJS with `require` answered from `moduleMap`:
3
3
  * what it exports, the screen to draw. What a web page's import map and
4
4
  * script tag do for a browser, this does for an app; fetching the bundle, and
5
5
  * when, is the app's. A bundle requiring a module the app doesn't provide
6
6
  * throws.
7
7
  */
8
- export function evaluate(code, modules) {
9
- const module = { exports: {} };
8
+ export function evaluate(code, moduleMap) {
10
9
  const require = (specifier) => {
11
- if (!Object.hasOwn(modules, specifier)) {
10
+ if (!Object.hasOwn(moduleMap, specifier)) {
12
11
  throw new Error(`The bundle requires "${specifier}", which this app doesn't provide.`);
13
12
  }
14
- return modules[specifier];
13
+ return moduleMap[specifier];
15
14
  };
15
+ const module = { exports: {} };
16
16
  new Function("module", "exports", "require", code)(module, module.exports, require);
17
17
  return module.exports;
18
18
  }
19
+ /**
20
+ * This build's id on `platform`, from the app config's `extra`, where an
21
+ * `app.config.ts` puts `buildIds` from `@backtickjs/builds`: it names the
22
+ * modules the build carries, for the server to bundle screens it can run.
23
+ * Send it however the app reaches its server: a header, a GraphQL variable.
24
+ * Throws for an app config without it, as the app starts: no screen could be
25
+ * drawn for a build without an id.
26
+ *
27
+ * const buildId = buildIdFrom(Constants.expoConfig?.extra, Platform.OS);
28
+ */
29
+ export function buildIdFrom(extra, platform) {
30
+ const ids = extra
31
+ ?.buildIds;
32
+ const id = ids?.[platform];
33
+ if (typeof id !== "string") {
34
+ throw new Error(`No build id for ${platform} in the app config: is app.config.ts adding buildIds from @backtickjs/builds?`);
35
+ }
36
+ return id;
37
+ }
19
38
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAgB;IACrD,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,EAAa,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAW,EAAE;QAC7C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CACb,wBAAwB,SAAS,oCAAoC,CACtE,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5B,CAAC,CAAC;IACF,IAAI,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAChD,MAAM,EACN,MAAM,CAAC,OAAO,EACd,OAAO,CACR,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,SAAoB;IACzD,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAW,EAAE;QAC7C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,KAAK,CACb,wBAAwB,SAAS,oCAAoC,CACtE,CAAC;QACJ,CAAC;QACD,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,EAAa,EAAE,CAAC;IAC1C,IAAI,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAChD,MAAM,EACN,MAAM,CAAC,OAAO,EACd,OAAO,CACR,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,QAAgB;IAC1D,MAAM,GAAG,GAAI,KAA4D;QACvE,EAAE,QAAQ,CAAC;IACb,MAAM,EAAE,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3B,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,mBAAmB,QAAQ,+EAA+E,CAC3G,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtickjs/react-native-client",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Runs a Backtick server's screens in a React Native app, with the app's own packages",
5
5
  "keywords": [
6
6
  "backtick",
package/src/index.ts CHANGED
@@ -1,37 +1,41 @@
1
1
  /**
2
- * What a bundle may require, by specifier: the packages the app was built
3
- * with, each as its module. At least React, React Native and React's JSX
2
+ * What a bundle may import, by specifier: each the module the app imported
3
+ * for it. What a web page's import map does, for an app, with modules for
4
+ * values instead of URLs. At least React, React Native and React's JSX
4
5
  * runtime:
5
6
  *
6
7
  * import * as React from "react";
7
8
  * import * as JSXRuntime from "react/jsx-runtime";
8
9
  * import * as ReactNative from "react-native";
9
10
  *
10
- * const modules = {
11
- * "react": React,
11
+ * export const moduleMap = {
12
+ * react: React,
12
13
  * "react/jsx-runtime": JSXRuntime,
13
14
  * "react-native": ReactNative,
14
- * };
15
+ * } satisfies ModuleMap;
16
+ *
17
+ * Imported as the app starts, so a module that fails to load fails then, not
18
+ * on the first screen that needs it.
15
19
  */
16
- export type Modules = Readonly<Record<string, unknown>>;
20
+ export type ModuleMap = Readonly<Record<string, unknown>>;
17
21
 
18
22
  /**
19
- * A bundle's code, run as CommonJS with `require` answered from `modules`:
23
+ * A bundle's code, run as CommonJS with `require` answered from `moduleMap`:
20
24
  * what it exports, the screen to draw. What a web page's import map and
21
25
  * script tag do for a browser, this does for an app; fetching the bundle, and
22
26
  * when, is the app's. A bundle requiring a module the app doesn't provide
23
27
  * throws.
24
28
  */
25
- export function evaluate(code: string, modules: Modules): unknown {
26
- const module = { exports: {} as unknown };
29
+ export function evaluate(code: string, moduleMap: ModuleMap): unknown {
27
30
  const require = (specifier: string): unknown => {
28
- if (!Object.hasOwn(modules, specifier)) {
31
+ if (!Object.hasOwn(moduleMap, specifier)) {
29
32
  throw new Error(
30
33
  `The bundle requires "${specifier}", which this app doesn't provide.`,
31
34
  );
32
35
  }
33
- return modules[specifier];
36
+ return moduleMap[specifier];
34
37
  };
38
+ const module = { exports: {} as unknown };
35
39
  new Function("module", "exports", "require", code)(
36
40
  module,
37
41
  module.exports,
@@ -39,3 +43,25 @@ export function evaluate(code: string, modules: Modules): unknown {
39
43
  );
40
44
  return module.exports;
41
45
  }
46
+
47
+ /**
48
+ * This build's id on `platform`, from the app config's `extra`, where an
49
+ * `app.config.ts` puts `buildIds` from `@backtickjs/builds`: it names the
50
+ * modules the build carries, for the server to bundle screens it can run.
51
+ * Send it however the app reaches its server: a header, a GraphQL variable.
52
+ * Throws for an app config without it, as the app starts: no screen could be
53
+ * drawn for a build without an id.
54
+ *
55
+ * const buildId = buildIdFrom(Constants.expoConfig?.extra, Platform.OS);
56
+ */
57
+ export function buildIdFrom(extra: unknown, platform: string): string {
58
+ const ids = (extra as { buildIds?: Record<string, unknown> } | undefined)
59
+ ?.buildIds;
60
+ const id = ids?.[platform];
61
+ if (typeof id !== "string") {
62
+ throw new Error(
63
+ `No build id for ${platform} in the app config: is app.config.ts adding buildIds from @backtickjs/builds?`,
64
+ );
65
+ }
66
+ return id;
67
+ }