create-backtick-app 0.1.10 → 0.2.0

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.
Files changed (44) hide show
  1. package/README.md +11 -11
  2. package/dist/index.js +14 -2
  3. package/package.json +1 -1
  4. package/templates/react/.mcp.json +8 -0
  5. package/templates/react/AGENTS.md +7 -0
  6. package/templates/react/CLAUDE.md +1 -0
  7. package/templates/react/package.json +20 -8
  8. package/templates/react/server/HelloWave.tsx +1 -1
  9. package/templates/react/server/Home.tsx +2 -2
  10. package/templates/react/server/html.ts +4 -20
  11. package/templates/react/server/index.tsx +1 -8
  12. package/templates/react/tsconfig.json +1 -1
  13. package/templates/react-native/.mcp.json +8 -0
  14. package/templates/react-native/AGENTS.md +9 -0
  15. package/templates/react-native/CLAUDE.md +1 -0
  16. package/templates/react-native/app.json +2 -1
  17. package/templates/react-native/client/App.tsx +69 -0
  18. package/templates/react-native/{moduleMap.ts → client/moduleMap.ts} +1 -2
  19. package/templates/react-native/package.json +28 -15
  20. package/templates/react-native/scripts/reload-expo.mjs +37 -0
  21. package/templates/react-native/scripts/reset-project.mjs +1 -1
  22. package/templates/react-native/server/HelloWave.tsx +4 -2
  23. package/templates/react-native/server/Home.tsx +4 -4
  24. package/templates/react-native/server/index.tsx +0 -9
  25. package/templates/react-native/server/screen.tsx +7 -17
  26. package/templates/react-native/server/test/drawScreen.ts +4 -4
  27. package/templates/react-native/server/tsconfig.json +2 -3
  28. package/templates/react-native/tsconfig.json +2 -1
  29. package/templates/solid-js/.mcp.json +8 -0
  30. package/templates/solid-js/AGENTS.md +7 -0
  31. package/templates/solid-js/CLAUDE.md +1 -0
  32. package/templates/solid-js/package.json +17 -8
  33. package/templates/solid-js/server/HelloWave.tsx +1 -1
  34. package/templates/solid-js/server/Home.tsx +2 -2
  35. package/templates/solid-js/server/html.ts +4 -20
  36. package/templates/solid-js/server/index.tsx +1 -8
  37. package/templates/solid-js/tsconfig.json +1 -1
  38. package/templates/react/scripts/start.mjs +0 -26
  39. package/templates/react-native/App.tsx +0 -114
  40. package/templates/react-native/app.config.ts +0 -10
  41. package/templates/react-native/scripts/start.mjs +0 -66
  42. package/templates/react-native/server/AppUpdateRequired.tsx +0 -15
  43. package/templates/solid-js/scripts/start.mjs +0 -26
  44. /package/templates/react-native/{index.ts → client/index.ts} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-backtick-app
2
2
 
3
- Creates an app whose screens come from your server, with [Backtick](https://backtickjs.com): an Expo app with a Backtick server beside it, or with `--template react` or `--template solid-js`, a web page served by one. Each starts with a welcome screen written as a server component, a small client component, and a development setup that redraws the screen when you save.
3
+ Creates an app whose screens come from your server, with [Backtick](https://backtickjs.com): an Expo app with a Backtick server beside it, or with `--template react` or `--template solid-js`, a web page served by one. Each starts with a welcome screen written as a server component, a small client component, and a development setup that restarts the server when you save.
4
4
 
5
5
  ## Usage
6
6
 
@@ -42,18 +42,19 @@ Every template has:
42
42
  - `server/Home.tsx`: the welcome screen, a server component. It reads values on the server and splices them into a client script.
43
43
  - `server/HelloWave.tsx`: a client component, a waving hand animated where the screen runs. Tap or click it.
44
44
  - `server/index.tsx`: the Backtick server on http://localhost:3000, a [Hono](https://hono.dev) app that runs on Node or Bun. Add a route there for each screen or page; a path can take parameters, as `/stories/:id`.
45
- - `scripts/start.mjs`: starts the server in development, restarting it whenever you save. The client polls `/live`, which exists only in development, and redraws when the server restarts.
45
+ - `npm start`: starts the server in development with [nodemon](https://nodemon.io), restarting it whenever you save a file under `server/`. In `react-native`, Expo starts beside it.
46
46
  - `npm run reset-project`: replaces `server/Home.tsx` with a blank screen and removes `HelloWave.tsx`.
47
47
  - `npm run typecheck`: runs `backtick-tsc` over the server, scripts and splices included. In `react-native`, it also runs `tsc` over the Expo app.
48
+ - `AGENTS.md`, which `CLAUDE.md` points to: how a coding agent sees the app while it works, with [agent-device](https://github.com/callstackincubator/agent-device)'s MCP tools from `.mcp.json`. In `react-native` they use the app on a phone `adb devices` lists; in `react` and `solid-js`, the page in a browser.
48
49
  - Prettier with `@backtickjs/prettier-plugin`, which formats client scripts too, and VS Code recommendations for the Backtick and Prettier extensions.
49
50
 
50
51
  ### `react-native`
51
52
 
52
- An Expo app (SDK 57) with the Backtick server beside it in `server/`. `App.tsx` fetches the screen at `/` and runs it with [`@backtickjs/react-native-client`](https://github.com/backtickjs/backtick/tree/main/packages/react-native-client), offering screens what `moduleMap.ts` lists.
53
+ An Expo app (SDK 57) with the Backtick server beside it in `server/`. `client/App.tsx` fetches the screen at `/` and runs it with `execRoot` from [`@backtickjs/core/cjs`](https://github.com/backtickjs/backtick/tree/main/packages/core), offering screens what `client/moduleMap.ts` lists.
53
54
 
54
- Each build of the app sends an id that names the modules it carries, which `app.config.ts` works out from `moduleMap.ts`. The server bundles each screen for that build's modules, with [`@backtickjs/builds`](https://github.com/backtickjs/backtick/tree/main/packages/builds), so a build in users' hands keeps working as the app moves on:
55
+ Each build of the app sends an id that names the modules it carries, which the `@backtickjs/builds/expo` plugin in `app.json` works out from `client/moduleMap.ts`. The server bundles each screen for that build's modules, with [`@backtickjs/builds`](https://github.com/backtickjs/backtick/tree/main/packages/builds), so a build in users' hands keeps working as the app moves on:
55
56
 
56
- - `npm run register-build` records this code's builds in `builds/`. Run it before a build for the stores, and commit what it writes.
57
+ - The server draws only registered builds, one file each in `builds/`. `npm start` registers this code's builds as Expo starts; `npm run register-build` does it without Expo, as before a build for the stores. Commit what they write.
57
58
  - On EAS, `eas-build-post-install` fails a preview or production build that isn't registered.
58
59
 
59
60
  ```sh
@@ -61,9 +62,9 @@ npm start # then scan the QR code with your phone, in Expo Go
61
62
  npm run ios # or android, or web: a simulator, an emulator or a browser
62
63
  ```
63
64
 
64
- Each starts your Backtick server alongside Expo, in one terminal. Edit `server/Home.tsx` and save: the app fetches the screen again, without rebuilding. Where Expo can't show its QR code, as when an agent runs it, `npm start` prints the `exp://` address to open in Expo Go instead.
65
+ Each starts your Backtick server alongside Expo, in one terminal. Edit `server/Home.tsx` and save: the server restarts, and `scripts/reload-expo.mjs`, which nodemon runs each time it starts the server, reloads the app once the server listens. The app fetches the new screen without rebuilding.
65
66
 
66
- `npm test` draws `server/Home.tsx` as the app would, in `server/Home.test.tsx`; [Testing screens](https://backtickjs.com/docs/testing) explains the test.
67
+ `npm test` draws `server/Home.tsx` as the app would, in `server/Home.test.tsx`; [Testing screens](https://backtickjs.com/docs/testing/) explains the test.
67
68
 
68
69
  ### `react` and `solid-js`
69
70
 
@@ -73,15 +74,14 @@ A web page: the server bundles `server/Home.tsx` into an HTML page at `/`, with
73
74
  npm start
74
75
  ```
75
76
 
76
- Then open http://localhost:3000. Edit `server/Home.tsx` and save to see the page change.
77
+ Then open http://localhost:3000. Edit `server/Home.tsx`, save, and refresh the page to see it change.
77
78
 
78
79
  ## Related
79
80
 
80
81
  - [`@backtickjs/core`](https://github.com/backtickjs/backtick/tree/main/packages/core): the `cs` tag
81
82
  - [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): runs server components and writes the bundle
82
- - [`@backtickjs/react-native-client`](https://github.com/backtickjs/backtick/tree/main/packages/react-native-client): runs a bundle in a React Native app
83
- - [Quick start](https://backtickjs.com/docs)
84
- - [Tutorial](https://backtickjs.com/docs/tutorial)
83
+ - [Quick start](https://backtickjs.com/docs/)
84
+ - [Tutorial](https://backtickjs.com/docs/tutorial/)
85
85
 
86
86
  ## License
87
87
 
package/dist/index.js CHANGED
@@ -140,14 +140,26 @@ cpSync(templateFolder, target, {
140
140
  renameSync(path.join(target, "gitignore"), path.join(target, ".gitignore"));
141
141
  rewriteJson(path.join(target, "package.json"), (json) => {
142
142
  json.name = slug;
143
- // The templates ship Node's loader; another runtime gets its own, which is
144
- // how `scripts/start.mjs` knows what to run.
143
+ // The templates ship Node's loader and scripts; another runtime gets its own.
145
144
  const loader = RUNTIMES[runtimeName].loader;
146
145
  if (!(loader in json.dependencies)) {
147
146
  const version = json.dependencies["@backtickjs/node-plugin"];
148
147
  delete json.dependencies["@backtickjs/node-plugin"];
149
148
  json.dependencies = Object.fromEntries(Object.entries({ ...json.dependencies, [loader]: version }).sort());
150
149
  }
150
+ if (runtimeName === "bun") {
151
+ const bun = (command) => command
152
+ .replace("node --enable-source-maps --import @backtickjs/node-plugin", `bun --preload ${loader}`)
153
+ .replace("node scripts/", "bun scripts/")
154
+ .replace("npm:server", "bun:server");
155
+ for (const [name, command] of Object.entries(json.scripts)) {
156
+ json.scripts[name] = bun(command);
157
+ }
158
+ const events = json.nodemonConfig.events ?? {};
159
+ for (const [name, command] of Object.entries(events)) {
160
+ events[name] = bun(command);
161
+ }
162
+ }
151
163
  });
152
164
  if (existsSync(path.join(target, "app.json"))) {
153
165
  rewriteJson(path.join(target, "app.json"), (json) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backtick-app",
3
- "version": "0.1.10",
3
+ "version": "0.2.0",
4
4
  "description": "Creates a Backtick app: React Native, React or Solid, with screens from your server on Node or Bun",
5
5
  "keywords": [
6
6
  "backtick",
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "agent-device": {
4
+ "command": "npx",
5
+ "args": ["--no", "agent-device", "mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ # Seeing the page while you work
2
+
3
+ `npm start` runs the server on http://localhost:3000, which serves the page. The `agent-device` MCP tools, from `.mcp.json`, see and use it in a browser, once `npx agent-device web setup` has installed one; they need Node 24 or later.
4
+
5
+ The server's output is in the terminal `npm start` runs in.
6
+
7
+ Saving a file under `server/` restarts the server: open the page again to see the change. What the server keeps in memory starts over.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -4,30 +4,42 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "start": "node scripts/start.mjs",
7
+ "start": "nodemon --exec \"node --enable-source-maps --import @backtickjs/node-plugin server/index.tsx\"",
8
8
  "reset-project": "node scripts/reset-project.mjs",
9
9
  "typecheck": "backtick-tsc --noEmit",
10
10
  "format": "prettier --write --ignore-unknown .",
11
11
  "format:check": "prettier --check --ignore-unknown ."
12
12
  },
13
13
  "dependencies": {
14
- "@backtickjs/bundler": "^0.1.10",
15
- "@backtickjs/core": "^0.1.10",
16
- "@backtickjs/node-plugin": "^0.1.10",
17
- "@backtickjs/react": "^19.2.3",
14
+ "@backtickjs/bundler": "^0.2.0",
15
+ "@backtickjs/core": "^0.2.0",
16
+ "@backtickjs/node-plugin": "^0.2.0",
17
+ "@backtickjs/jsx-react": "^0.2.0",
18
18
  "@hono/node-server": "^2.1.4",
19
19
  "hono": "^4.13.13",
20
+ "react": "19.2.3",
21
+ "react-dom": "19.2.3",
20
22
  "typescript": "^6.0.3"
21
23
  },
22
24
  "devDependencies": {
23
- "@backtickjs/prettier-plugin": "^0.1.10",
24
- "@backtickjs/tsc": "^0.1.10",
25
+ "@backtickjs/prettier-plugin": "^0.2.0",
26
+ "@backtickjs/tsc": "^0.2.0",
25
27
  "@types/node": "^25.9.1",
28
+ "@types/react": "19.2.18",
29
+ "@types/react-dom": "19.2.7",
30
+ "agent-device": "^0.21.27",
31
+ "nodemon": "^3.1.14",
26
32
  "prettier": "^3.6.2"
27
33
  },
34
+ "nodemonConfig": {
35
+ "watch": [
36
+ "server"
37
+ ],
38
+ "ext": "ts,tsx"
39
+ },
28
40
  "backtick": {
29
41
  "plugins": [
30
- "@backtickjs/react/plugin"
42
+ "@backtickjs/jsx-react/plugin"
31
43
  ]
32
44
  }
33
45
  }
@@ -1,5 +1,5 @@
1
1
  import { cs } from "@backtickjs/core";
2
- import { useEffect, useRef } from "@backtickjs/react";
2
+ import { useEffect, useRef } from "react" with { backtick: "client" };
3
3
 
4
4
  // A client component: it runs in the browser, so its animation does too. It
5
5
  // waves when it appears, and again when you click it.
@@ -35,7 +35,7 @@ export async function Home() {
35
35
  <section style={styles.step}>
36
36
  <h2 style={styles.subtitle}>Step 1: Try it</h2>
37
37
  <p style={styles.body}>
38
- Edit <b>server/Home.tsx</b> and save. This page redraws on its own.
38
+ Edit <b>server/Home.tsx</b>, save, and refresh this page.
39
39
  </p>
40
40
  </section>
41
41
 
@@ -56,7 +56,7 @@ export async function Home() {
56
56
  </p>
57
57
  </section>
58
58
 
59
- <a style={styles.link} href="https://backtickjs.com/docs">
59
+ <a style={styles.link} href="https://backtickjs.com/docs/">
60
60
  Read the docs →
61
61
  </a>
62
62
  </main>
@@ -1,7 +1,7 @@
1
1
  import { bundler } from "@backtickjs/bundler";
2
2
  import { cs } from "@backtickjs/core";
3
- import { createRoot } from "@backtickjs/react/client";
4
- import type { JSX } from "@backtickjs/react/jsx-runtime";
3
+ import { createRoot } from "react-dom/client" with { backtick: "client" };
4
+ import type { JSX } from "@backtickjs/jsx-react/jsx-runtime";
5
5
 
6
6
  // The versions of React the page loads, below: what each page is bundled for.
7
7
  const modules = {
@@ -10,23 +10,8 @@ const modules = {
10
10
  "react-dom/client": "19.2.3",
11
11
  };
12
12
 
13
- // Development is only what `npm start` runs, which sets NODE_ENV: any other
14
- // run, a deploy included, is production.
15
- export const development = process.env.NODE_ENV === "development";
16
-
17
- // In development, what reloads the page whenever the server restarts, as it
18
- // does when you save.
19
- const reload = `
20
- <script type="module">
21
- let run;
22
- setInterval(async () => {
23
- try {
24
- const next = await (await fetch("/live")).text();
25
- if (run !== undefined && next !== run) location.reload();
26
- run = next;
27
- } catch {}
28
- }, 1000);
29
- </script>`;
13
+ // Development unless NODE_ENV says production, as hosts set it for a deploy.
14
+ const development = process.env.NODE_ENV !== "production";
30
15
 
31
16
  // A page: the element, bundled for the browser, and React from a CDN.
32
17
  export async function toHtml(element: JSX.Element): Promise<string> {
@@ -60,7 +45,6 @@ export async function toHtml(element: JSX.Element): Promise<string> {
60
45
  <body style="margin: 0">
61
46
  <div id="app"></div>
62
47
  <script type="module" src="data:text/javascript,${encodeURIComponent(code)}"></script>
63
- ${development ? reload : ""}
64
48
  </body>
65
49
  </html>`;
66
50
  }
@@ -1,7 +1,7 @@
1
1
  import { serve } from "@hono/node-server";
2
2
  import { Hono } from "hono";
3
3
  import { Home } from "./Home.js";
4
- import { development, toHtml } from "./html.js";
4
+ import { toHtml } from "./html.js";
5
5
 
6
6
  const app = new Hono();
7
7
 
@@ -9,13 +9,6 @@ const app = new Hono();
9
9
  // parameters, `/stories/:id`, read with `context.req.param("id")`.
10
10
  app.get("/", async (context) => context.html(await toHtml(<Home />)));
11
11
 
12
- // In development, this run of the server: `--watch` starts a new one whenever
13
- // you save, and the page, seeing a new one at `/live`, reloads.
14
- if (development) {
15
- const run = crypto.randomUUID();
16
- app.get("/live", (context) => context.text(run));
17
- }
18
-
19
12
  // Answered rather than crashed on, so the browser can show why.
20
13
  app.onError((error, context) => {
21
14
  console.error(error);
@@ -12,7 +12,7 @@
12
12
  "lib": ["esnext", "dom"],
13
13
  "types": ["node"],
14
14
  "jsx": "react-jsx",
15
- "jsxImportSource": "@backtickjs/react"
15
+ "jsxImportSource": "@backtickjs/jsx-react"
16
16
  },
17
17
  "include": ["server"]
18
18
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "agent-device": {
4
+ "command": "npx",
5
+ "args": ["--no", "agent-device", "mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,9 @@
1
+ # Seeing the app while you work
2
+
3
+ `npm start` runs the server on http://localhost:3000, and Expo beside it; the app runs in Expo Go. The `agent-device` MCP tools, from `.mcp.json`, see and use it, once `adb devices` lists the phone: by USB, or Wi-Fi with Wireless debugging paired.
4
+
5
+ The server's output is in the terminal `npm start` runs in.
6
+
7
+ Saving a file under `server/` restarts the server, and Expo reloads the app once it's back: what the server keeps in memory starts over.
8
+
9
+ A change to `client/moduleMap.ts` or the packages makes a new build: the app has to be closed and opened again. Until then, the server answers the open app with `UnknownBuild`.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -4,6 +4,7 @@
4
4
  "slug": "backtick-app",
5
5
  "version": "1.0.0",
6
6
  "orientation": "portrait",
7
- "userInterfaceStyle": "light"
7
+ "userInterfaceStyle": "light",
8
+ "plugins": ["@backtickjs/builds/expo"]
8
9
  }
9
10
  }
@@ -0,0 +1,69 @@
1
+ import { buildIdFrom } from "@backtickjs/builds/client";
2
+ import type { Bundle } from "@backtickjs/core";
3
+ import { execRoot } from "@backtickjs/core/cjs";
4
+ import Constants from "expo-constants";
5
+ import { StatusBar } from "expo-status-bar";
6
+ import { type ReactNode, Suspense, use, useState } from "react";
7
+ import { ActivityIndicator, Platform, Text } from "react-native";
8
+ import {
9
+ ErrorBoundary,
10
+ type FallbackProps,
11
+ getErrorMessage,
12
+ } from "react-error-boundary";
13
+ import { moduleMap } from "./moduleMap";
14
+
15
+ // Your Backtick server runs on the computer Expo was started from.
16
+ const host = Constants.expoConfig?.hostUri?.split(":")[0] ?? "localhost";
17
+ const server = `http://${host}:3000`;
18
+
19
+ // This build's id, which names the modules it carries, for the server to
20
+ // bundle screens it can run: `@backtickjs/builds/expo`, in app.json, adds it.
21
+ const buildId = buildIdFrom(Constants.expoConfig?.extra, Platform.OS);
22
+
23
+ // The screen at `path` on your server: its bundle, fetched and run with the
24
+ // app's modules. Your fetch to shape: headers, auth, caching.
25
+ async function fetchScreen(path: string): Promise<ReactNode> {
26
+ const response = await fetch(`${server}${path}`, {
27
+ headers: { "x-build-id": buildId },
28
+ });
29
+ if (!response.ok) {
30
+ throw new Error(`${path} answered ${response.status}.`);
31
+ }
32
+ const bundle = (await response.text()) as Bundle<ReactNode>;
33
+ return execRoot(bundle, moduleMap);
34
+ }
35
+
36
+ export default function App() {
37
+ // Held here, which doesn't suspend, so the request outlives the render
38
+ // that waits for it.
39
+ const [screen] = useState(() => fetchScreen("/"));
40
+ // The whole screen, its layout included, comes from the server: see
41
+ // server/Home.tsx.
42
+ return (
43
+ <>
44
+ <ErrorBoundary FallbackComponent={Failed}>
45
+ <Suspense fallback={<ActivityIndicator style={{ flex: 1 }} />}>
46
+ <Screen screen={screen} />
47
+ </Suspense>
48
+ </ErrorBoundary>
49
+ <StatusBar style="auto" />
50
+ </>
51
+ );
52
+ }
53
+
54
+ // What the server sent, once it has: suspending until then, and throwing,
55
+ // for the boundary, a screen that couldn't be fetched or run.
56
+ function Screen({ screen }: { screen: Promise<ReactNode> }) {
57
+ return use(screen);
58
+ }
59
+
60
+ // Why the screen couldn't be drawn: the server unreachable, or a screen the
61
+ // app can't run.
62
+ function Failed({ error }: FallbackProps) {
63
+ return (
64
+ <Text style={{ margin: 24, marginTop: 96, color: "#b91c1c" }}>
65
+ {getErrorMessage(error) ?? String(error)}
66
+ {"\n\n"}Is your Backtick server running? npm start runs it alongside Expo.
67
+ </Text>
68
+ );
69
+ }
@@ -1,4 +1,3 @@
1
- import type { ModuleMap } from "@backtickjs/react-native-client";
2
1
  import * as React from "react";
3
2
  import * as JSXRuntime from "react/jsx-runtime";
4
3
  import * as ReactNative from "react-native";
@@ -12,4 +11,4 @@ export const moduleMap = {
12
11
  react: React,
13
12
  "react/jsx-runtime": JSXRuntime,
14
13
  "react-native": ReactNative,
15
- } satisfies ModuleMap;
14
+ };
@@ -2,12 +2,13 @@
2
2
  "name": "backtick-app-react-native",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "main": "index.ts",
5
+ "main": "client/index.ts",
6
6
  "scripts": {
7
- "start": "node scripts/start.mjs",
8
- "android": "node scripts/start.mjs --android",
9
- "ios": "node scripts/start.mjs --ios",
10
- "web": "node scripts/start.mjs --web",
7
+ "server": "nodemon --exec \"node --enable-source-maps --import @backtickjs/node-plugin server/index.tsx\"",
8
+ "start": "concurrently --raw --kill-others npm:server \"expo start\"",
9
+ "android": "concurrently --raw --kill-others npm:server \"expo start --android\"",
10
+ "ios": "concurrently --raw --kill-others npm:server \"expo start --ios\"",
11
+ "web": "concurrently --raw --kill-others npm:server \"expo start --web\"",
11
12
  "typecheck": "tsc --noEmit && backtick-tsc -p server --noEmit",
12
13
  "test": "node --import ./server/test/setup.mjs --import @backtickjs/node-plugin --test \"server/**/*.test.tsx\"",
13
14
  "reset-project": "node scripts/reset-project.mjs",
@@ -17,13 +18,11 @@
17
18
  "format:check": "prettier --check --ignore-unknown ."
18
19
  },
19
20
  "dependencies": {
20
- "@backtickjs/builds": "^0.1.10",
21
- "@backtickjs/bundler": "^0.1.10",
22
- "@backtickjs/core": "^0.1.10",
23
- "@backtickjs/node-plugin": "^0.1.10",
24
- "@backtickjs/react": "^19.2.3",
25
- "@backtickjs/react-native": "^0.86.3",
26
- "@backtickjs/react-native-client": "^0.1.10",
21
+ "@backtickjs/builds": "^0.2.0",
22
+ "@backtickjs/bundler": "^0.2.0",
23
+ "@backtickjs/core": "^0.2.0",
24
+ "@backtickjs/node-plugin": "^0.2.0",
25
+ "@backtickjs/jsx-react": "^0.2.0",
27
26
  "@hono/node-server": "^2.1.4",
28
27
  "expo": "~57.0.26",
29
28
  "expo-constants": "~57.0.20",
@@ -31,24 +30,38 @@
31
30
  "hono": "^4.13.13",
32
31
  "react": "19.2.3",
33
32
  "react-dom": "19.2.3",
33
+ "react-error-boundary": "6.1.6",
34
34
  "react-native": "0.86.3",
35
35
  "react-native-web": "^0.21.0",
36
36
  "typescript": "^6.0.3"
37
37
  },
38
38
  "devDependencies": {
39
- "@backtickjs/prettier-plugin": "^0.1.10",
40
- "@backtickjs/tsc": "^0.1.10",
39
+ "@backtickjs/prettier-plugin": "^0.2.0",
40
+ "@backtickjs/tsc": "^0.2.0",
41
41
  "@testing-library/react": "^16.3.3",
42
42
  "@testing-library/user-event": "^14.6.7",
43
43
  "@types/node": "^25.9.1",
44
44
  "@types/react": "19.2.18",
45
+ "agent-device": "^0.21.27",
46
+ "concurrently": "^10.0.6",
45
47
  "global-jsdom": "^30.0.0",
46
48
  "jsdom": "^30.0.1",
49
+ "nodemon": "^3.1.14",
47
50
  "prettier": "^3.6.2"
48
51
  },
52
+ "nodemonConfig": {
53
+ "watch": [
54
+ "server"
55
+ ],
56
+ "ext": "ts,tsx",
57
+ "events": {
58
+ "start": "node scripts/reload-expo.mjs"
59
+ }
60
+ },
49
61
  "backtick": {
62
+ "clientRoot": "client",
50
63
  "plugins": [
51
- "@backtickjs/react/plugin"
64
+ "@backtickjs/jsx-react/plugin"
52
65
  ]
53
66
  }
54
67
  }
@@ -0,0 +1,37 @@
1
+ // Run by nodemon each time it starts your server: once the server listens,
2
+ // the apps Expo serves reload, as pressing `r` in Expo's terminal does: a
3
+ // `reload` on its message socket, which it accepts from this computer.
4
+ // Nothing happens without Expo running.
5
+ import { connect } from "node:net";
6
+ import { setTimeout as sleep } from "node:timers/promises";
7
+
8
+ // Your server's port, and Expo's: React Native's variable for it.
9
+ const port = 3000;
10
+ const metro = `ws://127.0.0.1:${process.env.RCT_METRO_PORT ?? 8081}/message`;
11
+
12
+ // A connection, not a request, so the server has nothing to answer or log.
13
+ const listening = () =>
14
+ new Promise((resolve) => {
15
+ const socket = connect(port, "localhost");
16
+ socket.on("connect", () => {
17
+ socket.end();
18
+ resolve(true);
19
+ });
20
+ socket.on("error", () => resolve(false));
21
+ });
22
+
23
+ // Waited for, so the app doesn't fetch from a server still starting; given up
24
+ // on when it crashed instead.
25
+ for (let tries = 0; tries < 120; tries++) {
26
+ if (await listening()) {
27
+ const socket = new WebSocket(metro);
28
+ socket.onopen = () => {
29
+ socket.send(JSON.stringify({ version: 2, method: "reload" }));
30
+ socket.close();
31
+ };
32
+ // Expo isn't running: there's no app to reload.
33
+ socket.onerror = () => {};
34
+ break;
35
+ }
36
+ await sleep(250);
37
+ }
@@ -4,7 +4,7 @@ import { rmSync, writeFileSync } from "node:fs";
4
4
  writeFileSync(
5
5
  "server/Home.tsx",
6
6
  `import { cs } from "@backtickjs/core";
7
- import { Text, View } from "@backtickjs/react-native";
7
+ import { Text, View } from "react-native" with { backtick: "client" };
8
8
 
9
9
  export async function Home() {
10
10
  return cs\`(
@@ -1,6 +1,8 @@
1
1
  import { cs } from "@backtickjs/core";
2
- import { useEffect, useRef } from "@backtickjs/react";
3
- import { Animated, Platform, Pressable } from "@backtickjs/react-native";
2
+ import { useEffect, useRef } from "react" with { backtick: "client" };
3
+ import { Animated, Platform, Pressable } from "react-native" with {
4
+ backtick: "client",
5
+ };
4
6
 
5
7
  // A client component: it runs on the phone, so its animation does too. It
6
8
  // waves when it appears, and again when you tap it.
@@ -6,7 +6,7 @@ import {
6
6
  StyleSheet,
7
7
  Text,
8
8
  View,
9
- } from "@backtickjs/react-native";
9
+ } from "react-native" with { backtick: "client" };
10
10
  import { HelloWave } from "./HelloWave.js";
11
11
 
12
12
  // A server component: it runs on your server for every request, and hands the
@@ -49,8 +49,8 @@ export async function Home() {
49
49
  <$View style={styles.step}>
50
50
  <$Text style={styles.subtitle}>Step 1: Try it</$Text>
51
51
  <$Text style={styles.body}>
52
- Edit <$Text style={styles.code}>server/Home.tsx</$Text> and save.
53
- This screen redraws without rebuilding the app.
52
+ Edit <$Text style={styles.code}>server/Home.tsx</$Text>, save, and
53
+ reload the app. This screen changes without rebuilding it.
54
54
  </$Text>
55
55
  </$View>
56
56
 
@@ -74,7 +74,7 @@ export async function Home() {
74
74
 
75
75
  <$Text
76
76
  style={styles.link}
77
- onPress={() => $Linking.openURL("https://backtickjs.com/docs")}
77
+ onPress={() => $Linking.openURL("https://backtickjs.com/docs/")}
78
78
  >
79
79
  Read the docs →
80
80
  </$Text>
@@ -13,15 +13,6 @@ app.use(cors({ origin: "*", allowHeaders: ["x-build-id"] }));
13
13
  // take parameters, `/stories/:id`, read with `context.req.param("id")`.
14
14
  app.get("/", (context) => screen(context, <Home />));
15
15
 
16
- // Development is only what `npm start` runs, which sets NODE_ENV: any other
17
- // run, a deploy included, is production. In development, this run of the
18
- // server: `--watch` starts a new one whenever you save, and the app, seeing a
19
- // new one at `/live`, draws its screen again.
20
- if (process.env.NODE_ENV === "development") {
21
- const run = crypto.randomUUID();
22
- app.get("/live", (context) => context.text(run));
23
- }
24
-
25
16
  // Answered rather than crashed on, so the app can show why.
26
17
  app.onError((error, context) => {
27
18
  console.error(error);
@@ -1,33 +1,23 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { createBuilds, DeprecatedBuild } from "@backtickjs/builds";
2
+ import { createBuilds } from "@backtickjs/builds";
3
3
  import { bundler } from "@backtickjs/bundler";
4
- import type { JSX } from "@backtickjs/react/jsx-runtime";
4
+ import type { JSX } from "@backtickjs/jsx-react/jsx-runtime";
5
5
  import type { Context } from "hono";
6
- import { AppUpdateRequired } from "./AppUpdateRequired.js";
7
6
 
8
7
  // Your app's builds: each sends its id, and a screen is bundled for the
9
- // modules that build carries, from moduleMap.ts. A build in users' hands
8
+ // modules that build carries, from client/moduleMap.ts. A build in users' hands
10
9
  // keeps working as your app moves on, once registered: see builds/.
11
10
  const builds = createBuilds({
12
11
  projectRoot: fileURLToPath(new URL("..", import.meta.url)),
13
12
  });
14
13
 
15
14
  /**
16
- * `element` as the response to a request from your app: bundled for the
17
- * modules the requesting build carries, or, for a build you retired, a
18
- * screen asking to update, bundled for what it has.
15
+ * `element` as the response to a request from your app, bundled for the
16
+ * modules the requesting build carries.
19
17
  */
20
18
  export async function screen(context: Context, element: JSX.Element) {
21
- let input = element;
22
- let modules;
23
- try {
24
- modules = builds.modulesOf(context.req.header("x-build-id") || null);
25
- } catch (error) {
26
- if (!(error instanceof DeprecatedBuild)) throw error;
27
- input = <AppUpdateRequired />;
28
- modules = error.modules;
29
- }
30
- const bundle = await bundler.build({ input, modules });
19
+ const modules = builds.modulesOf(context.req.header("x-build-id") ?? null);
20
+ const bundle = await bundler.build({ input: element, modules });
31
21
  const { code } = bundle.generate({ format: "cjs" });
32
22
  return context.body(code, 200, { "content-type": "text/javascript" });
33
23
  }
@@ -2,12 +2,12 @@ import { fileURLToPath } from "node:url";
2
2
  import { createBuilds } from "@backtickjs/builds";
3
3
  import { bundler } from "@backtickjs/bundler";
4
4
  import type { Spliceable } from "@backtickjs/core";
5
- import { evaluate } from "@backtickjs/react-native-client";
5
+ import { execRoot } from "@backtickjs/core/cjs";
6
6
  import * as React from "react";
7
7
  import * as JSXRuntime from "react/jsx-runtime";
8
8
  import * as ReactNativeWeb from "react-native-web";
9
9
 
10
- // What the app's web build provides, as moduleMap.ts says, with React
10
+ // What the app's web build provides, as client/moduleMap.ts says, with React
11
11
  // Native's web build standing in for React Native. Add a stand-in here when
12
12
  // your module map offers another package.
13
13
  const modules = createBuilds({
@@ -21,8 +21,8 @@ const moduleMap = {
21
21
 
22
22
  // A screen, bundled as your server bundles it and run as your app runs it,
23
23
  // for Testing Library to draw.
24
- export async function drawScreen(input: Spliceable): Promise<React.ReactNode> {
24
+ export async function drawScreen<T extends Spliceable>(input: T) {
25
25
  const bundle = await bundler.build({ input, modules });
26
26
  const { code } = bundle.generate({ format: "cjs" });
27
- return evaluate(code, moduleMap) as React.ReactNode;
27
+ return execRoot(code, moduleMap);
28
28
  }
@@ -11,8 +11,7 @@
11
11
  "skipLibCheck": true,
12
12
  "types": ["node"],
13
13
  "jsx": "react-jsx",
14
- "jsxImportSource": "@backtickjs/react"
14
+ "jsxImportSource": "@backtickjs/jsx-react"
15
15
  },
16
- // with app.config.ts, which runs in Node as the server does
17
- "include": [".", "../app.config.ts"]
16
+ "include": ["."]
18
17
  }
@@ -3,5 +3,6 @@
3
3
  "compilerOptions": {
4
4
  "strict": true
5
5
  },
6
- "exclude": ["server", "app.config.ts"]
6
+ // The app; the server, in server/, has its own.
7
+ "include": ["client"]
7
8
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "agent-device": {
4
+ "command": "npx",
5
+ "args": ["--no", "agent-device", "mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ # Seeing the page while you work
2
+
3
+ `npm start` runs the server on http://localhost:3000, which serves the page. The `agent-device` MCP tools, from `.mcp.json`, see and use it in a browser, once `npx agent-device web setup` has installed one; they need Node 24 or later.
4
+
5
+ The server's output is in the terminal `npm start` runs in.
6
+
7
+ Saving a file under `server/` restarts the server: open the page again to see the change. What the server keeps in memory starts over.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -4,30 +4,39 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "start": "node scripts/start.mjs",
7
+ "start": "nodemon --exec \"node --enable-source-maps --import @backtickjs/node-plugin server/index.tsx\"",
8
8
  "reset-project": "node scripts/reset-project.mjs",
9
9
  "typecheck": "backtick-tsc --noEmit",
10
10
  "format": "prettier --write --ignore-unknown .",
11
11
  "format:check": "prettier --check --ignore-unknown ."
12
12
  },
13
13
  "dependencies": {
14
- "@backtickjs/bundler": "^0.1.10",
15
- "@backtickjs/core": "^0.1.10",
16
- "@backtickjs/node-plugin": "^0.1.10",
17
- "@backtickjs/solid-js": "^1.9.14",
14
+ "@backtickjs/bundler": "^0.2.0",
15
+ "@backtickjs/core": "^0.2.0",
16
+ "@backtickjs/node-plugin": "^0.2.0",
17
+ "@backtickjs/jsx-solid": "^0.2.0",
18
18
  "@hono/node-server": "^2.1.4",
19
19
  "hono": "^4.13.13",
20
+ "solid-js": "1.9.14",
20
21
  "typescript": "^6.0.3"
21
22
  },
22
23
  "devDependencies": {
23
- "@backtickjs/prettier-plugin": "^0.1.10",
24
- "@backtickjs/tsc": "^0.1.10",
24
+ "@backtickjs/prettier-plugin": "^0.2.0",
25
+ "@backtickjs/tsc": "^0.2.0",
25
26
  "@types/node": "^25.9.1",
27
+ "agent-device": "^0.21.27",
28
+ "nodemon": "^3.1.14",
26
29
  "prettier": "^3.6.2"
27
30
  },
31
+ "nodemonConfig": {
32
+ "watch": [
33
+ "server"
34
+ ],
35
+ "ext": "ts,tsx"
36
+ },
28
37
  "backtick": {
29
38
  "plugins": [
30
- "@backtickjs/solid-js/plugin"
39
+ "@backtickjs/jsx-solid/plugin"
31
40
  ]
32
41
  }
33
42
  }
@@ -1,5 +1,5 @@
1
1
  import { cs } from "@backtickjs/core";
2
- import { onMount } from "@backtickjs/solid-js";
2
+ import { onMount } from "solid-js" with { backtick: "client" };
3
3
 
4
4
  // A client component: it runs in the browser, so its animation does too. It
5
5
  // waves when it appears, and again when you click it.
@@ -35,7 +35,7 @@ export async function Home() {
35
35
  <section style={styles.step}>
36
36
  <h2 style={styles.subtitle}>Step 1: Try it</h2>
37
37
  <p style={styles.body}>
38
- Edit <b>server/Home.tsx</b> and save. This page redraws on its own.
38
+ Edit <b>server/Home.tsx</b>, save, and refresh this page.
39
39
  </p>
40
40
  </section>
41
41
 
@@ -56,7 +56,7 @@ export async function Home() {
56
56
  </p>
57
57
  </section>
58
58
 
59
- <a style={styles.link} href="https://backtickjs.com/docs">
59
+ <a style={styles.link} href="https://backtickjs.com/docs/">
60
60
  Read the docs →
61
61
  </a>
62
62
  </main>
@@ -1,7 +1,7 @@
1
1
  import { bundler } from "@backtickjs/bundler";
2
2
  import { cs } from "@backtickjs/core";
3
- import { render } from "@backtickjs/solid-js/web";
4
- import type { JSX } from "@backtickjs/solid-js/jsx-runtime";
3
+ import { render } from "solid-js/web" with { backtick: "client" };
4
+ import type { JSX } from "@backtickjs/jsx-solid/jsx-runtime";
5
5
 
6
6
  // The version of Solid the page loads, below: what each page is bundled for.
7
7
  const modules = {
@@ -10,23 +10,8 @@ const modules = {
10
10
  "solid-js/web": "1.9.14",
11
11
  };
12
12
 
13
- // Development is only what `npm start` runs, which sets NODE_ENV: any other
14
- // run, a deploy included, is production.
15
- export const development = process.env.NODE_ENV === "development";
16
-
17
- // In development, what reloads the page whenever the server restarts, as it
18
- // does when you save.
19
- const reload = `
20
- <script type="module">
21
- let run;
22
- setInterval(async () => {
23
- try {
24
- const next = await (await fetch("/live")).text();
25
- if (run !== undefined && next !== run) location.reload();
26
- run = next;
27
- } catch {}
28
- }, 1000);
29
- </script>`;
13
+ // Development unless NODE_ENV says production, as hosts set it for a deploy.
14
+ const development = process.env.NODE_ENV !== "production";
30
15
 
31
16
  // A page: the element, bundled for the browser, and Solid from a CDN.
32
17
  export async function toHtml(element: JSX.Element): Promise<string> {
@@ -60,7 +45,6 @@ export async function toHtml(element: JSX.Element): Promise<string> {
60
45
  <body style="margin: 0">
61
46
  <div id="app"></div>
62
47
  <script type="module" src="data:text/javascript,${encodeURIComponent(code)}"></script>
63
- ${development ? reload : ""}
64
48
  </body>
65
49
  </html>`;
66
50
  }
@@ -1,7 +1,7 @@
1
1
  import { serve } from "@hono/node-server";
2
2
  import { Hono } from "hono";
3
3
  import { Home } from "./Home.js";
4
- import { development, toHtml } from "./html.js";
4
+ import { toHtml } from "./html.js";
5
5
 
6
6
  const app = new Hono();
7
7
 
@@ -9,13 +9,6 @@ const app = new Hono();
9
9
  // parameters, `/stories/:id`, read with `context.req.param("id")`.
10
10
  app.get("/", async (context) => context.html(await toHtml(<Home />)));
11
11
 
12
- // In development, this run of the server: `--watch` starts a new one whenever
13
- // you save, and the page, seeing a new one at `/live`, reloads.
14
- if (development) {
15
- const run = crypto.randomUUID();
16
- app.get("/live", (context) => context.text(run));
17
- }
18
-
19
12
  // Answered rather than crashed on, so the browser can show why.
20
13
  app.onError((error, context) => {
21
14
  console.error(error);
@@ -12,7 +12,7 @@
12
12
  "lib": ["esnext", "dom"],
13
13
  "types": ["node"],
14
14
  "jsx": "react-jsx",
15
- "jsxImportSource": "@backtickjs/solid-js"
15
+ "jsxImportSource": "@backtickjs/jsx-solid"
16
16
  },
17
17
  "include": ["server"]
18
18
  }
@@ -1,26 +0,0 @@
1
- // Starts your Backtick server in development: it restarts whenever a file
2
- // under `server/` changes, and the page reloads with it. Any other way of
3
- // running the server, a deploy included, is production.
4
- import { spawn } from "node:child_process";
5
- import { readFileSync } from "node:fs";
6
-
7
- // The runtime is the one package.json has a Backtick loader for: Bun's, or
8
- // else Node's.
9
- const { dependencies } = JSON.parse(readFileSync("package.json", "utf8"));
10
- const [runtime, ...loader] =
11
- "@backtickjs/bun-plugin" in dependencies
12
- ? ["bun", "--preload", "@backtickjs/bun-plugin"]
13
- : ["node", "--enable-source-maps", "--import", "@backtickjs/node-plugin"];
14
-
15
- const server = spawn(runtime, ["--watch", ...loader, "server/index.tsx"], {
16
- stdio: "inherit",
17
- env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? "development" },
18
- });
19
-
20
- server.on("exit", (code) => process.exit(code ?? 0));
21
-
22
- // Stopped as a whole: a signal to this script alone, as a task runner sends
23
- // rather than Ctrl-C to the terminal, stops the server with it.
24
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
25
- process.on(signal, () => server.kill(signal));
26
- }
@@ -1,114 +0,0 @@
1
- import { buildIdFrom, evaluate } from "@backtickjs/react-native-client";
2
- import Constants from "expo-constants";
3
- import { StatusBar } from "expo-status-bar";
4
- import {
5
- Component,
6
- type ReactNode,
7
- Suspense,
8
- use,
9
- useEffect,
10
- useState,
11
- } from "react";
12
- import { ActivityIndicator, Platform, Text } from "react-native";
13
- import { moduleMap } from "./moduleMap";
14
-
15
- // Your Backtick server runs on the computer Expo was started from.
16
- const host = Constants.expoConfig?.hostUri?.split(":")[0] ?? "localhost";
17
- const server = `http://${host}:3000`;
18
-
19
- // This build's id, which names the modules it carries, for the server to
20
- // bundle screens it can run: see app.config.ts.
21
- const buildId = buildIdFrom(Constants.expoConfig?.extra, Platform.OS);
22
-
23
- // The screen at `path` on your server: its bundle, fetched and run with the
24
- // app's modules. Your fetch to shape: headers, auth, caching.
25
- async function fetchScreen(path: string): Promise<ReactNode> {
26
- const response = await fetch(`${server}${path}`, {
27
- headers: { "x-build-id": buildId },
28
- });
29
- if (!response.ok) {
30
- throw new Error(`${path} answered ${response.status}.`);
31
- }
32
- return evaluate(await response.text(), moduleMap) as ReactNode;
33
- }
34
-
35
- export default function App() {
36
- // Held here, which doesn't suspend, so the request outlives the render
37
- // that waits for it. Each request is numbered, so a new one also clears an
38
- // error the last one showed.
39
- const [request, setRequest] = useState(() => ({
40
- number: 0,
41
- screen: fetchScreen("/"),
42
- }));
43
- useEffect(() => {
44
- // In development, the screen is fetched again each time the server
45
- // restarts, so a change to server/ shows as soon as you save.
46
- if (__DEV__) {
47
- return onServerRestart(() =>
48
- setRequest(({ number }) => ({
49
- number: number + 1,
50
- screen: fetchScreen("/"),
51
- })),
52
- );
53
- }
54
- }, []);
55
- // The whole screen, its layout included, comes from the server: see
56
- // server/Home.tsx.
57
- return (
58
- <>
59
- <Boundary key={request.number}>
60
- <Suspense fallback={<ActivityIndicator style={{ flex: 1 }} />}>
61
- <Screen screen={request.screen} />
62
- </Suspense>
63
- </Boundary>
64
- <StatusBar style="auto" />
65
- </>
66
- );
67
- }
68
-
69
- // What the server sent, once it has: suspending until then, and throwing,
70
- // for the boundary, a screen that couldn't be fetched or run.
71
- function Screen({ screen }: { screen: Promise<ReactNode> }) {
72
- return use(screen);
73
- }
74
-
75
- // Calls `restarted` when the server starts a new run: it names each run at
76
- // `/live`, and a new name is a new run.
77
- function onServerRestart(restarted: () => void): () => void {
78
- let run: string | undefined;
79
- const timer = setInterval(async () => {
80
- try {
81
- const next = await (await fetch(`${server}/live`)).text();
82
- if (run !== undefined && next !== run) {
83
- restarted();
84
- }
85
- run = next;
86
- } catch {
87
- // Restarting: the next tick finds the new run.
88
- }
89
- }, 1000);
90
- return () => clearInterval(timer);
91
- }
92
-
93
- // Why the screen couldn't be drawn: the server unreachable, or a screen the
94
- // app can't run.
95
- class Boundary extends Component<{ children: ReactNode }, { error: unknown }> {
96
- state = { error: null as unknown };
97
-
98
- static getDerivedStateFromError(error: unknown) {
99
- return { error };
100
- }
101
-
102
- render() {
103
- if (this.state.error === null) {
104
- return this.props.children;
105
- }
106
- return (
107
- <Text style={{ margin: 24, marginTop: 96, color: "#b91c1c" }}>
108
- {String((this.state.error as Error).message ?? this.state.error)}
109
- {"\n\n"}Is your Backtick server running? npm start runs it alongside
110
- Expo.
111
- </Text>
112
- );
113
- }
114
- }
@@ -1,10 +0,0 @@
1
- import { buildIds } from "@backtickjs/builds";
2
- import type { ConfigContext, ExpoConfig } from "expo/config";
3
-
4
- // app.json, with this build's id on each platform: it names the modules the
5
- // build carries, which the app sends and the server bundles screens for.
6
- export default ({ config, projectRoot }: ConfigContext): ExpoConfig =>
7
- ({
8
- ...config,
9
- extra: { ...config.extra, buildIds: buildIds(projectRoot) },
10
- }) as ExpoConfig;
@@ -1,66 +0,0 @@
1
- // Starts your Backtick server in development, then Expo, both in this terminal:
2
- // Expo keeps its keys and QR code, and the server restarts whenever a file
3
- // under `server/` changes. Arguments are Expo's, as `npm run ios` passes
4
- // `--ios`.
5
- import { spawn } from "node:child_process";
6
- import { readFileSync } from "node:fs";
7
- import { networkInterfaces } from "node:os";
8
- import { setTimeout as sleep } from "node:timers/promises";
9
-
10
- // The runtime is the one package.json has a Backtick loader for: Bun's, or
11
- // else Node's.
12
- const { dependencies } = JSON.parse(readFileSync("package.json", "utf8"));
13
- const [runtime, ...loader] =
14
- "@backtickjs/bun-plugin" in dependencies
15
- ? ["bun", "--preload", "@backtickjs/bun-plugin"]
16
- : ["node", "--enable-source-maps", "--import", "@backtickjs/node-plugin"];
17
-
18
- const server = spawn(runtime, ["--watch", ...loader, "server/index.tsx"], {
19
- stdio: "inherit",
20
- env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? "development" },
21
- });
22
-
23
- // `bunx` where Bun runs this script, as it does where Node isn't installed.
24
- const npx = process.versions.bun ? "bunx" : "npx";
25
- const expo = spawn(npx, ["expo", "start", ...process.argv.slice(2)], {
26
- stdio: "inherit",
27
- shell: process.platform === "win32",
28
- });
29
-
30
- expo.on("exit", (code) => {
31
- server.kill();
32
- process.exit(code ?? 0);
33
- });
34
-
35
- // Stopped as a whole: a signal to this script alone, as a task runner sends
36
- // rather than Ctrl-C to the terminal, stops the server and Expo with it.
37
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
38
- process.on(signal, () => {
39
- server.kill(signal);
40
- expo.kill(signal);
41
- });
42
- }
43
-
44
- // Expo shows how to open the app, its QR code, only in an interactive
45
- // terminal. Run elsewhere, as an agent runs it, this prints the address once
46
- // Metro is up.
47
- if (!process.stdout.isTTY) {
48
- const args = process.argv.slice(2);
49
- const flag = args.findIndex((arg) => arg === "--port" || arg === "-p");
50
- const port = flag === -1 ? 8081 : Number(args[flag + 1]);
51
- const address = Object.values(networkInterfaces())
52
- .flat()
53
- .find(
54
- (network) => network?.family === "IPv4" && !network.internal,
55
- )?.address;
56
- while (address !== undefined && expo.exitCode === null) {
57
- const status = await fetch(`http://localhost:${port}/status`)
58
- .then((response) => response.text())
59
- .catch(() => "");
60
- if (status === "packager-status:running") {
61
- console.log(`\nOpen exp://${address}:${port} in Expo Go.\n`);
62
- break;
63
- }
64
- await sleep(500);
65
- }
66
- }
@@ -1,15 +0,0 @@
1
- import { cs } from "@backtickjs/core";
2
- import { Text, View } from "@backtickjs/react-native";
3
-
4
- // Every path, for a build you retired: drawn with React Native alone, which
5
- // every build has, and bundled for the retired build's own modules. Point
6
- // your users at your store listing here.
7
- export function AppUpdateRequired() {
8
- return cs`(
9
- <$View style={{ flex: 1, justifyContent: "center", padding: 24 }}>
10
- <$Text style={{ fontSize: 16, textAlign: "center" }}>
11
- This version of the app is no longer supported. Update it to keep going.
12
- </$Text>
13
- </$View>
14
- )`;
15
- }
@@ -1,26 +0,0 @@
1
- // Starts your Backtick server in development: it restarts whenever a file
2
- // under `server/` changes, and the page reloads with it. Any other way of
3
- // running the server, a deploy included, is production.
4
- import { spawn } from "node:child_process";
5
- import { readFileSync } from "node:fs";
6
-
7
- // The runtime is the one package.json has a Backtick loader for: Bun's, or
8
- // else Node's.
9
- const { dependencies } = JSON.parse(readFileSync("package.json", "utf8"));
10
- const [runtime, ...loader] =
11
- "@backtickjs/bun-plugin" in dependencies
12
- ? ["bun", "--preload", "@backtickjs/bun-plugin"]
13
- : ["node", "--enable-source-maps", "--import", "@backtickjs/node-plugin"];
14
-
15
- const server = spawn(runtime, ["--watch", ...loader, "server/index.tsx"], {
16
- stdio: "inherit",
17
- env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? "development" },
18
- });
19
-
20
- server.on("exit", (code) => process.exit(code ?? 0));
21
-
22
- // Stopped as a whole: a signal to this script alone, as a task runner sends
23
- // rather than Ctrl-C to the terminal, stops the server with it.
24
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
25
- process.on(signal, () => server.kill(signal));
26
- }