create-backtick-app 0.2.0 → 0.2.1

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
@@ -17,7 +17,7 @@ yarn create backtick-app
17
17
  bun create backtick-app
18
18
  ```
19
19
 
20
- It asks for a name, and nothing else: it copies the React Native template into that directory, installs with the package manager you ran it with (npm, pnpm, Yarn or Bun), and prints the commands to start. The server runs on Node, or on Bun when you ran it with Bun (`bun create`). Given the name on the command line, it asks nothing. Requires Node 22.15 or later.
20
+ It asks for a name, and nothing else: it copies the React Native template into that directory, installs with the package manager you ran it with (npm, pnpm, Yarn or Bun), and prints the commands to start. The server runs on Node, or on Bun when you ran it with Bun (`bun create`) or passed `--runtime bun`; tests run on Node either way. Given the name on the command line, it asks nothing. Requires Node 22.15 or later.
21
21
 
22
22
  ## Options
23
23
 
@@ -52,10 +52,7 @@ Every template has:
52
52
 
53
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.
54
54
 
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:
56
-
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.
58
- - On EAS, `eas-build-post-install` fails a preview or production build that isn't registered.
55
+ Each build of the app sends the modules it carries: those `client/moduleMap.ts` offers, at the versions `app.config.ts` records. The server bundles each screen for those modules ([more](https://github.com/backtickjs/backtick/tree/main/packages/core#every-build-in-users-hands)), so a build in users' hands keeps working as the app moves on, with nothing to record before it ships.
59
56
 
60
57
  ```sh
61
58
  npm start # then scan the QR code with your phone, in Expo Go
package/dist/index.js CHANGED
@@ -142,8 +142,8 @@ rewriteJson(path.join(target, "package.json"), (json) => {
142
142
  json.name = slug;
143
143
  // The templates ship Node's loader and scripts; another runtime gets its own.
144
144
  const loader = RUNTIMES[runtimeName].loader;
145
+ const version = json.dependencies["@backtickjs/node-plugin"];
145
146
  if (!(loader in json.dependencies)) {
146
- const version = json.dependencies["@backtickjs/node-plugin"];
147
147
  delete json.dependencies["@backtickjs/node-plugin"];
148
148
  json.dependencies = Object.fromEntries(Object.entries({ ...json.dependencies, [loader]: version }).sort());
149
149
  }
@@ -159,6 +159,13 @@ rewriteJson(path.join(target, "package.json"), (json) => {
159
159
  for (const [name, command] of Object.entries(events)) {
160
160
  events[name] = bun(command);
161
161
  }
162
+ // What still runs on Node, as the tests do, keeps Node's loader.
163
+ if (Object.values(json.scripts).some((command) => command.includes("@backtickjs/node-plugin"))) {
164
+ json.devDependencies = Object.fromEntries(Object.entries({
165
+ ...json.devDependencies,
166
+ "@backtickjs/node-plugin": version,
167
+ }).sort());
168
+ }
162
169
  }
163
170
  });
164
171
  if (existsSync(path.join(target, "app.json"))) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backtick-app",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",
@@ -11,10 +11,10 @@
11
11
  "format:check": "prettier --check --ignore-unknown ."
12
12
  },
13
13
  "dependencies": {
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",
14
+ "@backtickjs/bundler": "^0.2.1",
15
+ "@backtickjs/core": "^0.2.1",
16
+ "@backtickjs/node-plugin": "^0.2.1",
17
+ "@backtickjs/jsx-react": "^0.2.1",
18
18
  "@hono/node-server": "^2.1.4",
19
19
  "hono": "^4.13.13",
20
20
  "react": "19.2.3",
@@ -22,8 +22,8 @@
22
22
  "typescript": "^6.0.3"
23
23
  },
24
24
  "devDependencies": {
25
- "@backtickjs/prettier-plugin": "^0.2.0",
26
- "@backtickjs/tsc": "^0.2.0",
25
+ "@backtickjs/prettier-plugin": "^0.2.1",
26
+ "@backtickjs/tsc": "^0.2.1",
27
27
  "@types/node": "^25.9.1",
28
28
  "@types/react": "19.2.18",
29
29
  "@types/react-dom": "19.2.7",
@@ -6,4 +6,4 @@ The server's output is in the terminal `npm start` runs in.
6
6
 
7
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
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`.
9
+ An upgraded package reaches the server once the app is closed and opened again: a reload keeps the versions it was opened with.
@@ -0,0 +1,12 @@
1
+ import { installedPackages } from "@backtickjs/core/node";
2
+ import type { ConfigContext, ExpoConfig } from "expo/config";
3
+
4
+ // app.json, with the installed version of each of the app's dependencies,
5
+ // which the app reads to tell its server the modules it carries.
6
+ export default ({ config, projectRoot }: ConfigContext): ExpoConfig => ({
7
+ ...(config as ExpoConfig),
8
+ extra: {
9
+ ...config.extra,
10
+ backtick: { packages: installedPackages(projectRoot) },
11
+ },
12
+ });
@@ -4,7 +4,6 @@
4
4
  "slug": "backtick-app",
5
5
  "version": "1.0.0",
6
6
  "orientation": "portrait",
7
- "userInterfaceStyle": "light",
8
- "plugins": ["@backtickjs/builds/expo"]
7
+ "userInterfaceStyle": "light"
9
8
  }
10
9
  }
@@ -1,10 +1,9 @@
1
- import { buildIdFrom } from "@backtickjs/builds/client";
2
- import type { Bundle } from "@backtickjs/core";
1
+ import { type Bundle, modulesOf } from "@backtickjs/core";
3
2
  import { execRoot } from "@backtickjs/core/cjs";
4
3
  import Constants from "expo-constants";
5
4
  import { StatusBar } from "expo-status-bar";
6
5
  import { type ReactNode, Suspense, use, useState } from "react";
7
- import { ActivityIndicator, Platform, Text } from "react-native";
6
+ import { ActivityIndicator, Text } from "react-native";
8
7
  import {
9
8
  ErrorBoundary,
10
9
  type FallbackProps,
@@ -16,15 +15,17 @@ import { moduleMap } from "./moduleMap";
16
15
  const host = Constants.expoConfig?.hostUri?.split(":")[0] ?? "localhost";
17
16
  const server = `http://${host}:3000`;
18
17
 
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);
18
+ // The modules this build carries, for the server to bundle screens it can
19
+ // run: app.config.ts adds its packages' versions.
20
+ const modules = JSON.stringify(
21
+ modulesOf(moduleMap, Constants.expoConfig?.extra?.backtick?.packages),
22
+ );
22
23
 
23
24
  // The screen at `path` on your server: its bundle, fetched and run with the
24
25
  // app's modules. Your fetch to shape: headers, auth, caching.
25
26
  async function fetchScreen(path: string): Promise<ReactNode> {
26
27
  const response = await fetch(`${server}${path}`, {
27
- headers: { "x-build-id": buildId },
28
+ headers: { "x-backtick-modules": modules },
28
29
  });
29
30
  if (!response.ok) {
30
31
  throw new Error(`${path} answered ${response.status}.`);
@@ -3,9 +3,9 @@ import * as JSXRuntime from "react/jsx-runtime";
3
3
  import * as ReactNative from "react-native";
4
4
 
5
5
  // What a screen may require, as your app imports it: the packages it's built
6
- // with. Add one when a screen uses another package. Imports and a table,
7
- // nothing else: the server reads it, without running it, to know what each
8
- // build of your app carries. A platform that differs gets its own file, as
6
+ // with. Add one when a screen uses another package, from package.json's
7
+ // dependencies: the app sends your server what it offers, at the versions
8
+ // it's built with. A platform that differs gets its own file, as
9
9
  // Metro picks files: moduleMap.web.ts, moduleMap.ios.ts.
10
10
  export const moduleMap = {
11
11
  react: React,
@@ -12,17 +12,14 @@
12
12
  "typecheck": "tsc --noEmit && backtick-tsc -p server --noEmit",
13
13
  "test": "node --import ./server/test/setup.mjs --import @backtickjs/node-plugin --test \"server/**/*.test.tsx\"",
14
14
  "reset-project": "node scripts/reset-project.mjs",
15
- "register-build": "backtick-builds register",
16
- "eas-build-post-install": "backtick-builds check",
17
15
  "format": "prettier --write --ignore-unknown .",
18
16
  "format:check": "prettier --check --ignore-unknown ."
19
17
  },
20
18
  "dependencies": {
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",
19
+ "@backtickjs/bundler": "^0.2.1",
20
+ "@backtickjs/core": "^0.2.1",
21
+ "@backtickjs/node-plugin": "^0.2.1",
22
+ "@backtickjs/jsx-react": "^0.2.1",
26
23
  "@hono/node-server": "^2.1.4",
27
24
  "expo": "~57.0.26",
28
25
  "expo-constants": "~57.0.20",
@@ -36,8 +33,8 @@
36
33
  "typescript": "^6.0.3"
37
34
  },
38
35
  "devDependencies": {
39
- "@backtickjs/prettier-plugin": "^0.2.0",
40
- "@backtickjs/tsc": "^0.2.0",
36
+ "@backtickjs/prettier-plugin": "^0.2.1",
37
+ "@backtickjs/tsc": "^0.2.1",
41
38
  "@testing-library/react": "^16.3.3",
42
39
  "@testing-library/user-event": "^14.6.7",
43
40
  "@types/node": "^25.9.1",
@@ -59,7 +56,6 @@
59
56
  }
60
57
  },
61
58
  "backtick": {
62
- "clientRoot": "client",
63
59
  "plugins": [
64
60
  "@backtickjs/jsx-react/plugin"
65
61
  ]
@@ -7,7 +7,7 @@ import { screen } from "./screen.js";
7
7
  const app = new Hono();
8
8
 
9
9
  // `npm run web` serves the app from another origin.
10
- app.use(cors({ origin: "*", allowHeaders: ["x-build-id"] }));
10
+ app.use(cors({ origin: "*", allowHeaders: ["x-backtick-modules"] }));
11
11
 
12
12
  // Your app's screens: add a route, and the app can fetch it. A path can
13
13
  // take parameters, `/stories/:id`, read with `context.req.param("id")`.
@@ -1,22 +1,14 @@
1
- import { fileURLToPath } from "node:url";
2
- import { createBuilds } from "@backtickjs/builds";
3
1
  import { bundler } from "@backtickjs/bundler";
4
2
  import type { JSX } from "@backtickjs/jsx-react/jsx-runtime";
5
3
  import type { Context } from "hono";
6
4
 
7
- // Your app's builds: each sends its id, and a screen is bundled for the
8
- // modules that build carries, from client/moduleMap.ts. A build in users' hands
9
- // keeps working as your app moves on, once registered: see builds/.
10
- const builds = createBuilds({
11
- projectRoot: fileURLToPath(new URL("..", import.meta.url)),
12
- });
13
-
14
5
  /**
15
6
  * `element` as the response to a request from your app, bundled for the
16
- * modules the requesting build carries.
7
+ * modules the requesting build carries, as client/moduleMap.ts offers them
8
+ * at its packages' versions.
17
9
  */
18
10
  export async function screen(context: Context, element: JSX.Element) {
19
- const modules = builds.modulesOf(context.req.header("x-build-id") ?? null);
11
+ const modules = JSON.parse(context.req.header("x-backtick-modules") ?? "{}");
20
12
  const bundle = await bundler.build({ input: element, modules });
21
13
  const { code } = bundle.generate({ format: "cjs" });
22
14
  return context.body(code, 200, { "content-type": "text/javascript" });
@@ -1,7 +1,7 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { createBuilds } from "@backtickjs/builds";
3
2
  import { bundler } from "@backtickjs/bundler";
4
- import type { Spliceable } from "@backtickjs/core";
3
+ import { modulesOf, type Spliceable } from "@backtickjs/core";
4
+ import { installedPackages } from "@backtickjs/core/node";
5
5
  import { execRoot } from "@backtickjs/core/cjs";
6
6
  import * as React from "react";
7
7
  import * as JSXRuntime from "react/jsx-runtime";
@@ -10,14 +10,16 @@ import * as ReactNativeWeb from "react-native-web";
10
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
- const modules = createBuilds({
14
- projectRoot: fileURLToPath(new URL("../..", import.meta.url)),
15
- }).installed("web");
16
13
  const moduleMap = {
17
14
  react: React,
18
15
  "react/jsx-runtime": JSXRuntime,
19
16
  "react-native": ReactNativeWeb,
20
17
  };
18
+ // at the versions the app installs, as its build sends them
19
+ const modules = modulesOf(
20
+ moduleMap,
21
+ installedPackages(fileURLToPath(new URL("../..", import.meta.url))),
22
+ );
21
23
 
22
24
  // A screen, bundled as your server bundles it and run as your app runs it,
23
25
  // for Testing Library to draw.
@@ -3,6 +3,6 @@
3
3
  "compilerOptions": {
4
4
  "strict": true
5
5
  },
6
- // The app; the server, in server/, has its own.
7
- "include": ["client"]
6
+ // The app and its config; the server, in server/, has its own.
7
+ "include": ["client", "app.config.ts"]
8
8
  }
@@ -11,18 +11,18 @@
11
11
  "format:check": "prettier --check --ignore-unknown ."
12
12
  },
13
13
  "dependencies": {
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",
14
+ "@backtickjs/bundler": "^0.2.1",
15
+ "@backtickjs/core": "^0.2.1",
16
+ "@backtickjs/node-plugin": "^0.2.1",
17
+ "@backtickjs/jsx-solid": "^0.2.1",
18
18
  "@hono/node-server": "^2.1.4",
19
19
  "hono": "^4.13.13",
20
- "solid-js": "1.9.14",
20
+ "solid-js": "1.9.17",
21
21
  "typescript": "^6.0.3"
22
22
  },
23
23
  "devDependencies": {
24
- "@backtickjs/prettier-plugin": "^0.2.0",
25
- "@backtickjs/tsc": "^0.2.0",
24
+ "@backtickjs/prettier-plugin": "^0.2.1",
25
+ "@backtickjs/tsc": "^0.2.1",
26
26
  "@types/node": "^25.9.1",
27
27
  "agent-device": "^0.21.27",
28
28
  "nodemon": "^3.1.14",
@@ -5,9 +5,9 @@ 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 = {
8
- "solid-js": "1.9.14",
9
- "solid-js/store": "1.9.14",
10
- "solid-js/web": "1.9.14",
8
+ "solid-js": "1.9.17",
9
+ "solid-js/store": "1.9.17",
10
+ "solid-js/web": "1.9.17",
11
11
  };
12
12
 
13
13
  // Development unless NODE_ENV says production, as hosts set it for a deploy.
@@ -35,9 +35,9 @@ export async function toHtml(element: JSX.Element): Promise<string> {
35
35
  <script type="importmap">
36
36
  {
37
37
  "imports": {
38
- "solid-js": "https://cdn.jsdelivr.net/npm/solid-js@1.9.14/dist/solid.js",
39
- "solid-js/web": "https://cdn.jsdelivr.net/npm/solid-js@1.9.14/web/dist/web.js",
40
- "solid-js/store": "https://cdn.jsdelivr.net/npm/solid-js@1.9.14/store/dist/store.js"
38
+ "solid-js": "https://cdn.jsdelivr.net/npm/solid-js@1.9.17/dist/solid.js",
39
+ "solid-js/web": "https://cdn.jsdelivr.net/npm/solid-js@1.9.17/web/dist/web.js",
40
+ "solid-js/store": "https://cdn.jsdelivr.net/npm/solid-js@1.9.17/store/dist/store.js"
41
41
  }
42
42
  }
43
43
  </script>