create-backtick-app 0.1.8 → 0.1.9
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 +6 -1
- package/package.json +1 -1
- package/templates/react/package.json +5 -5
- package/templates/react/server/html.ts +6 -2
- package/templates/react-native/App.tsx +17 -15
- package/templates/react-native/app.config.ts +10 -0
- package/templates/react-native/moduleMap.ts +12 -0
- package/templates/react-native/package.json +9 -6
- package/templates/react-native/server/index.tsx +22 -11
- package/templates/react-native/server/test/drawScreen.ts +14 -15
- package/templates/react-native/server/tsconfig.json +2 -1
- package/templates/react-native/tsconfig.json +1 -1
- package/templates/solid-js/package.json +5 -5
- package/templates/solid-js/server/html.ts +6 -2
package/README.md
CHANGED
|
@@ -49,7 +49,12 @@ Every template has:
|
|
|
49
49
|
|
|
50
50
|
### `react-native`
|
|
51
51
|
|
|
52
|
-
An Expo app (SDK 57) with the Backtick server beside it in `server/`. `App.tsx` fetches the screen at `/home` and runs it with [`@backtickjs/react-native-client`](https://github.com/backtickjs/backtick/tree/main/packages/react-native-client)
|
|
52
|
+
An Expo app (SDK 57) with the Backtick server beside it in `server/`. `App.tsx` fetches the screen at `/home` 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
|
+
|
|
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
|
+
|
|
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
|
+
- On EAS, `eas-build-post-install` fails a preview or production build that isn't registered.
|
|
53
58
|
|
|
54
59
|
```sh
|
|
55
60
|
npm start # then scan the QR code with your phone, in Expo Go
|
package/package.json
CHANGED
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"format:check": "prettier --check --ignore-unknown ."
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@backtickjs/bundler": "^0.1.
|
|
15
|
-
"@backtickjs/core": "^0.1.
|
|
16
|
-
"@backtickjs/node-plugin": "^0.1.
|
|
14
|
+
"@backtickjs/bundler": "^0.1.9",
|
|
15
|
+
"@backtickjs/core": "^0.1.9",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.9",
|
|
17
17
|
"@backtickjs/react": "^19.2.3",
|
|
18
18
|
"typescript": "^6.0.3"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@backtickjs/prettier-plugin": "^0.1.
|
|
22
|
-
"@backtickjs/tsc": "^0.1.
|
|
21
|
+
"@backtickjs/prettier-plugin": "^0.1.9",
|
|
22
|
+
"@backtickjs/tsc": "^0.1.9",
|
|
23
23
|
"@types/node": "^25.9.1",
|
|
24
24
|
"prettier": "^3.6.2"
|
|
25
25
|
},
|
|
@@ -4,7 +4,11 @@ import { createRoot } from "@backtickjs/react/client";
|
|
|
4
4
|
import type { JSX } from "@backtickjs/react/jsx-runtime";
|
|
5
5
|
|
|
6
6
|
// The versions of React the page loads, below: what each page is bundled for.
|
|
7
|
-
const
|
|
7
|
+
const modules = {
|
|
8
|
+
react: "19.2.3",
|
|
9
|
+
"react/jsx-runtime": "19.2.3",
|
|
10
|
+
"react-dom/client": "19.2.3",
|
|
11
|
+
};
|
|
8
12
|
|
|
9
13
|
// Development is only what `npm start` runs, which sets NODE_ENV: any other
|
|
10
14
|
// run, a deploy included, is production.
|
|
@@ -28,7 +32,7 @@ const reload = `
|
|
|
28
32
|
export async function toHtml(element: JSX.Element): Promise<string> {
|
|
29
33
|
const bundle = await bundler.build({
|
|
30
34
|
input: cs`$createRoot(document.getElementById("app")!).render($element)`,
|
|
31
|
-
|
|
35
|
+
modules,
|
|
32
36
|
});
|
|
33
37
|
// In development, a map back to the files you wrote, for the browser's
|
|
34
38
|
// devtools.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { evaluate } from "@backtickjs/react-native-client";
|
|
1
|
+
import { buildIdFrom, evaluate } from "@backtickjs/react-native-client";
|
|
2
2
|
import Constants from "expo-constants";
|
|
3
3
|
import { StatusBar } from "expo-status-bar";
|
|
4
|
-
import * as React from "react";
|
|
5
4
|
import {
|
|
6
5
|
Component,
|
|
7
6
|
type ReactNode,
|
|
@@ -10,29 +9,32 @@ import {
|
|
|
10
9
|
useEffect,
|
|
11
10
|
useState,
|
|
12
11
|
} from "react";
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import { ActivityIndicator, Text } from "react-native";
|
|
16
|
-
|
|
17
|
-
// What a screen may require: the packages this app was built with.
|
|
18
|
-
const modules = {
|
|
19
|
-
react: React,
|
|
20
|
-
"react/jsx-runtime": JSXRuntime,
|
|
21
|
-
"react-native": ReactNative,
|
|
22
|
-
};
|
|
12
|
+
import { ActivityIndicator, Platform, Text } from "react-native";
|
|
13
|
+
import { moduleMap } from "./moduleMap";
|
|
23
14
|
|
|
24
15
|
// Your Backtick server runs on the computer Expo was started from.
|
|
25
16
|
const host = Constants.expoConfig?.hostUri?.split(":")[0] ?? "localhost";
|
|
26
17
|
const server = `http://${host}:3000`;
|
|
27
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
|
+
|
|
28
23
|
// The screen at `path` on your server: its bundle, fetched and run with the
|
|
29
|
-
// app's
|
|
24
|
+
// app's modules. Your fetch to shape: headers, auth, caching.
|
|
30
25
|
async function fetchScreen(path: string): Promise<ReactNode> {
|
|
31
|
-
const response = await fetch(`${server}${path}
|
|
26
|
+
const response = await fetch(`${server}${path}`, {
|
|
27
|
+
headers: { "x-build-id": buildId },
|
|
28
|
+
});
|
|
29
|
+
if (response.status === 426) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"This version of the app is no longer supported: update it.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
32
34
|
if (!response.ok) {
|
|
33
35
|
throw new Error(`${path} answered ${response.status}.`);
|
|
34
36
|
}
|
|
35
|
-
return evaluate(await response.text(),
|
|
37
|
+
return evaluate(await response.text(), moduleMap) as ReactNode;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export default function App() {
|
|
@@ -0,0 +1,10 @@
|
|
|
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;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ModuleMap } from "@backtickjs/react-native-client";
|
|
2
|
+
|
|
3
|
+
// What a screen may require, and where each comes from: the packages this
|
|
4
|
+
// app is built with. Add one when a screen uses another package. A table and
|
|
5
|
+
// nothing else: the server reads it, without running it, to know what each
|
|
6
|
+
// build of your app carries. A platform that differs gets its own file, as
|
|
7
|
+
// Metro picks files: moduleMap.web.ts, moduleMap.ios.ts.
|
|
8
|
+
export const moduleMap = {
|
|
9
|
+
react: () => require("react"),
|
|
10
|
+
"react/jsx-runtime": () => require("react/jsx-runtime"),
|
|
11
|
+
"react-native": () => require("react-native"),
|
|
12
|
+
} satisfies ModuleMap;
|
|
@@ -11,16 +11,19 @@
|
|
|
11
11
|
"typecheck": "tsc --noEmit && backtick-tsc -p server --noEmit",
|
|
12
12
|
"test": "node --import ./server/test/setup.mjs --import @backtickjs/node-plugin --test \"server/**/*.test.tsx\"",
|
|
13
13
|
"reset-project": "node scripts/reset-project.mjs",
|
|
14
|
+
"register-build": "backtick-builds register",
|
|
15
|
+
"eas-build-post-install": "backtick-builds check",
|
|
14
16
|
"format": "prettier --write --ignore-unknown .",
|
|
15
17
|
"format:check": "prettier --check --ignore-unknown ."
|
|
16
18
|
},
|
|
17
19
|
"dependencies": {
|
|
18
|
-
"@backtickjs/
|
|
19
|
-
"@backtickjs/
|
|
20
|
-
"@backtickjs/
|
|
20
|
+
"@backtickjs/builds": "^0.1.9",
|
|
21
|
+
"@backtickjs/bundler": "^0.1.9",
|
|
22
|
+
"@backtickjs/core": "^0.1.9",
|
|
23
|
+
"@backtickjs/node-plugin": "^0.1.9",
|
|
21
24
|
"@backtickjs/react": "^19.2.3",
|
|
22
25
|
"@backtickjs/react-native": "^0.86.3",
|
|
23
|
-
"@backtickjs/react-native-client": "^0.1.
|
|
26
|
+
"@backtickjs/react-native-client": "^0.1.9",
|
|
24
27
|
"expo": "~57.0.26",
|
|
25
28
|
"expo-constants": "~57.0.20",
|
|
26
29
|
"expo-status-bar": "~57.0.1",
|
|
@@ -31,8 +34,8 @@
|
|
|
31
34
|
"typescript": "^6.0.3"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
34
|
-
"@backtickjs/prettier-plugin": "^0.1.
|
|
35
|
-
"@backtickjs/tsc": "^0.1.
|
|
37
|
+
"@backtickjs/prettier-plugin": "^0.1.9",
|
|
38
|
+
"@backtickjs/tsc": "^0.1.9",
|
|
36
39
|
"@testing-library/react": "^16.3.3",
|
|
37
40
|
"@testing-library/user-event": "^14.6.7",
|
|
38
41
|
"@types/node": "^25.9.1",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
-
import {
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { createBuilds, DeprecatedBuild } from "@backtickjs/builds";
|
|
3
4
|
import { bundler } from "@backtickjs/bundler";
|
|
4
5
|
import { Home } from "./Home.js";
|
|
5
6
|
|
|
@@ -10,30 +11,40 @@ import { Home } from "./Home.js";
|
|
|
10
11
|
const development = process.env.NODE_ENV === "development";
|
|
11
12
|
const run = crypto.randomUUID();
|
|
12
13
|
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
react: require("react/package.json").version,
|
|
20
|
-
"react-native": require("react-native/package.json").version,
|
|
21
|
-
};
|
|
14
|
+
// Your app's builds: each sends its id, and a screen is bundled for the
|
|
15
|
+
// modules that build carries, from moduleMap.ts. A build in users' hands
|
|
16
|
+
// keeps working as your app moves on, once registered: see builds/.
|
|
17
|
+
const builds = createBuilds({
|
|
18
|
+
projectRoot: fileURLToPath(new URL("..", import.meta.url)),
|
|
19
|
+
});
|
|
22
20
|
|
|
23
21
|
const server = createServer(async (request, response) => {
|
|
24
22
|
// `npm run web` serves the app from another origin.
|
|
25
23
|
response.setHeader("access-control-allow-origin", "*");
|
|
24
|
+
response.setHeader("access-control-allow-headers", "x-build-id");
|
|
25
|
+
if (request.method === "OPTIONS") {
|
|
26
|
+
response.end();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
26
29
|
if (development && request.url === "/live") {
|
|
27
30
|
response.end(run);
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
30
33
|
if (request.url === "/home") {
|
|
31
34
|
try {
|
|
32
|
-
const
|
|
35
|
+
const buildId = request.headers["x-build-id"]?.toString() || null;
|
|
36
|
+
const modules = builds.modulesOf(buildId);
|
|
37
|
+
const bundle = await bundler.build({ input: <Home />, modules });
|
|
33
38
|
const { code } = bundle.generate({ format: "cjs" });
|
|
34
39
|
response.setHeader("content-type", "text/javascript");
|
|
35
40
|
response.end(code);
|
|
36
41
|
} catch (error) {
|
|
42
|
+
// A build you retired: the app asks its user to update.
|
|
43
|
+
if (error instanceof DeprecatedBuild) {
|
|
44
|
+
response.statusCode = 426;
|
|
45
|
+
response.end(String(error));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
37
48
|
// Answered rather than crashed on, so the app can show why.
|
|
38
49
|
console.error(error);
|
|
39
50
|
response.statusCode = 500;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { createBuilds } from "@backtickjs/builds";
|
|
2
3
|
import { bundler } from "@backtickjs/bundler";
|
|
3
4
|
import type { Spliceable } from "@backtickjs/core";
|
|
4
5
|
import { evaluate } from "@backtickjs/react-native-client";
|
|
@@ -6,24 +7,22 @@ import * as React from "react";
|
|
|
6
7
|
import * as JSXRuntime from "react/jsx-runtime";
|
|
7
8
|
import * as ReactNativeWeb from "react-native-web";
|
|
8
9
|
|
|
9
|
-
// What the app provides, as
|
|
10
|
-
// Native's web build standing in for React Native. Add a
|
|
11
|
-
// your
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
react:
|
|
19
|
-
"react/jsx-runtime": JSXRuntime,
|
|
20
|
-
"react-native": ReactNativeWeb,
|
|
10
|
+
// What the app's web build provides, as moduleMap.ts says, with React
|
|
11
|
+
// Native's web build standing in for React Native. Add a stand-in here when
|
|
12
|
+
// your module map offers another package.
|
|
13
|
+
const modules = createBuilds({
|
|
14
|
+
projectRoot: fileURLToPath(new URL("../..", import.meta.url)),
|
|
15
|
+
}).installed("web");
|
|
16
|
+
const moduleMap = {
|
|
17
|
+
react: () => React,
|
|
18
|
+
"react/jsx-runtime": () => JSXRuntime,
|
|
19
|
+
"react-native": () => ReactNativeWeb,
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
// A screen, bundled as your server bundles it and run as your app runs it,
|
|
24
23
|
// for Testing Library to draw.
|
|
25
24
|
export async function drawScreen(input: Spliceable): Promise<React.ReactNode> {
|
|
26
|
-
const bundle = await bundler.build({ input,
|
|
25
|
+
const bundle = await bundler.build({ input, modules });
|
|
27
26
|
const { code } = bundle.generate({ format: "cjs" });
|
|
28
|
-
return evaluate(code,
|
|
27
|
+
return evaluate(code, moduleMap) as React.ReactNode;
|
|
29
28
|
}
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"format:check": "prettier --check --ignore-unknown ."
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@backtickjs/bundler": "^0.1.
|
|
15
|
-
"@backtickjs/core": "^0.1.
|
|
16
|
-
"@backtickjs/node-plugin": "^0.1.
|
|
14
|
+
"@backtickjs/bundler": "^0.1.9",
|
|
15
|
+
"@backtickjs/core": "^0.1.9",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.9",
|
|
17
17
|
"@backtickjs/solid-js": "^1.9.14",
|
|
18
18
|
"typescript": "^6.0.3"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@backtickjs/prettier-plugin": "^0.1.
|
|
22
|
-
"@backtickjs/tsc": "^0.1.
|
|
21
|
+
"@backtickjs/prettier-plugin": "^0.1.9",
|
|
22
|
+
"@backtickjs/tsc": "^0.1.9",
|
|
23
23
|
"@types/node": "^25.9.1",
|
|
24
24
|
"prettier": "^3.6.2"
|
|
25
25
|
},
|
|
@@ -4,7 +4,11 @@ import { render } from "@backtickjs/solid-js/web";
|
|
|
4
4
|
import type { JSX } from "@backtickjs/solid-js/jsx-runtime";
|
|
5
5
|
|
|
6
6
|
// The version of Solid the page loads, below: what each page is bundled for.
|
|
7
|
-
const
|
|
7
|
+
const modules = {
|
|
8
|
+
"solid-js": "1.9.14",
|
|
9
|
+
"solid-js/store": "1.9.14",
|
|
10
|
+
"solid-js/web": "1.9.14",
|
|
11
|
+
};
|
|
8
12
|
|
|
9
13
|
// Development is only what `npm start` runs, which sets NODE_ENV: any other
|
|
10
14
|
// run, a deploy included, is production.
|
|
@@ -28,7 +32,7 @@ const reload = `
|
|
|
28
32
|
export async function toHtml(element: JSX.Element): Promise<string> {
|
|
29
33
|
const bundle = await bundler.build({
|
|
30
34
|
input: cs`$render(() => $element, document.getElementById("app")!)`,
|
|
31
|
-
|
|
35
|
+
modules,
|
|
32
36
|
});
|
|
33
37
|
// In development, a map back to the files you wrote, for the browser's
|
|
34
38
|
// devtools.
|