create-backtick-app 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 +7 -2
- package/package.json +1 -1
- package/templates/react/package.json +7 -5
- package/templates/react/scripts/start.mjs +6 -0
- package/templates/react/server/html.ts +6 -2
- package/templates/react/server/index.tsx +17 -23
- package/templates/react-native/App.tsx +14 -17
- package/templates/react-native/app.config.ts +10 -0
- package/templates/react-native/moduleMap.ts +15 -0
- package/templates/react-native/package.json +11 -6
- package/templates/react-native/scripts/start.mjs +9 -0
- package/templates/react-native/server/AppUpdateRequired.tsx +15 -0
- package/templates/react-native/server/index.tsx +22 -39
- package/templates/react-native/server/screen.tsx +33 -0
- package/templates/react-native/server/test/drawScreen.ts +11 -12
- package/templates/react-native/server/tsconfig.json +2 -1
- package/templates/react-native/tsconfig.json +1 -1
- package/templates/solid-js/package.json +7 -5
- package/templates/solid-js/scripts/start.mjs +6 -0
- package/templates/solid-js/server/html.ts +6 -2
- package/templates/solid-js/server/index.tsx +17 -23
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ Every template has:
|
|
|
41
41
|
|
|
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
|
-
- `server/index.tsx`: the Backtick server on http://localhost:3000.
|
|
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
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.
|
|
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.
|
|
@@ -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
|
|
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
|
+
|
|
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,17 @@
|
|
|
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.10",
|
|
15
|
+
"@backtickjs/core": "^0.1.10",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.10",
|
|
17
17
|
"@backtickjs/react": "^19.2.3",
|
|
18
|
+
"@hono/node-server": "^2.1.4",
|
|
19
|
+
"hono": "^4.13.13",
|
|
18
20
|
"typescript": "^6.0.3"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
|
-
"@backtickjs/prettier-plugin": "^0.1.
|
|
22
|
-
"@backtickjs/tsc": "^0.1.
|
|
23
|
+
"@backtickjs/prettier-plugin": "^0.1.10",
|
|
24
|
+
"@backtickjs/tsc": "^0.1.10",
|
|
23
25
|
"@types/node": "^25.9.1",
|
|
24
26
|
"prettier": "^3.6.2"
|
|
25
27
|
},
|
|
@@ -18,3 +18,9 @@ const server = spawn(runtime, ["--watch", ...loader, "server/index.tsx"], {
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
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
|
+
}
|
|
@@ -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,33 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { Hono } from "hono";
|
|
2
3
|
import { Home } from "./Home.js";
|
|
3
4
|
import { development, toHtml } from "./html.js";
|
|
4
5
|
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
|
|
8
|
+
// Your pages: add a route, and the browser can open it. A path can take
|
|
9
|
+
// parameters, `/stories/:id`, read with `context.req.param("id")`.
|
|
10
|
+
app.get("/", async (context) => context.html(await toHtml(<Home />)));
|
|
11
|
+
|
|
5
12
|
// In development, this run of the server: `--watch` starts a new one whenever
|
|
6
13
|
// you save, and the page, seeing a new one at `/live`, reloads.
|
|
7
|
-
|
|
14
|
+
if (development) {
|
|
15
|
+
const run = crypto.randomUUID();
|
|
16
|
+
app.get("/live", (context) => context.text(run));
|
|
17
|
+
}
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
if (request.url === "/") {
|
|
15
|
-
try {
|
|
16
|
-
const html = await toHtml(<Home />);
|
|
17
|
-
response.setHeader("content-type", "text/html");
|
|
18
|
-
response.end(html);
|
|
19
|
-
} catch (error) {
|
|
20
|
-
// Answered rather than crashed on, so the browser can show why.
|
|
21
|
-
console.error(error);
|
|
22
|
-
response.statusCode = 500;
|
|
23
|
-
response.end(String(error));
|
|
24
|
-
}
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
response.statusCode = 404;
|
|
28
|
-
response.end();
|
|
19
|
+
// Answered rather than crashed on, so the browser can show why.
|
|
20
|
+
app.onError((error, context) => {
|
|
21
|
+
console.error(error);
|
|
22
|
+
return context.text(String(error), 500);
|
|
29
23
|
});
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
serve({ fetch: app.fetch, port: 3000 }, () => {
|
|
32
26
|
console.log("Backtick server on http://localhost:3000");
|
|
33
27
|
});
|
|
@@ -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,27 @@ 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
|
+
});
|
|
32
29
|
if (!response.ok) {
|
|
33
30
|
throw new Error(`${path} answered ${response.status}.`);
|
|
34
31
|
}
|
|
35
|
-
return evaluate(await response.text(),
|
|
32
|
+
return evaluate(await response.text(), moduleMap) as ReactNode;
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
export default function App() {
|
|
@@ -41,7 +38,7 @@ export default function App() {
|
|
|
41
38
|
// error the last one showed.
|
|
42
39
|
const [request, setRequest] = useState(() => ({
|
|
43
40
|
number: 0,
|
|
44
|
-
screen: fetchScreen("/
|
|
41
|
+
screen: fetchScreen("/"),
|
|
45
42
|
}));
|
|
46
43
|
useEffect(() => {
|
|
47
44
|
// In development, the screen is fetched again each time the server
|
|
@@ -50,7 +47,7 @@ export default function App() {
|
|
|
50
47
|
return onServerRestart(() =>
|
|
51
48
|
setRequest(({ number }) => ({
|
|
52
49
|
number: number + 1,
|
|
53
|
-
screen: fetchScreen("/
|
|
50
|
+
screen: fetchScreen("/"),
|
|
54
51
|
})),
|
|
55
52
|
);
|
|
56
53
|
}
|
|
@@ -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,15 @@
|
|
|
1
|
+
import type { ModuleMap } from "@backtickjs/react-native-client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import * as JSXRuntime from "react/jsx-runtime";
|
|
4
|
+
import * as ReactNative from "react-native";
|
|
5
|
+
|
|
6
|
+
// What a screen may require, as your app imports it: the packages it's built
|
|
7
|
+
// with. Add one when a screen uses another package. Imports and a table,
|
|
8
|
+
// nothing else: the server reads it, without running it, to know what each
|
|
9
|
+
// build of your app carries. A platform that differs gets its own file, as
|
|
10
|
+
// Metro picks files: moduleMap.web.ts, moduleMap.ios.ts.
|
|
11
|
+
export const moduleMap = {
|
|
12
|
+
react: React,
|
|
13
|
+
"react/jsx-runtime": JSXRuntime,
|
|
14
|
+
"react-native": ReactNative,
|
|
15
|
+
} satisfies ModuleMap;
|
|
@@ -11,19 +11,24 @@
|
|
|
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.10",
|
|
21
|
+
"@backtickjs/bundler": "^0.1.10",
|
|
22
|
+
"@backtickjs/core": "^0.1.10",
|
|
23
|
+
"@backtickjs/node-plugin": "^0.1.10",
|
|
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.10",
|
|
27
|
+
"@hono/node-server": "^2.1.4",
|
|
24
28
|
"expo": "~57.0.26",
|
|
25
29
|
"expo-constants": "~57.0.20",
|
|
26
30
|
"expo-status-bar": "~57.0.1",
|
|
31
|
+
"hono": "^4.13.13",
|
|
27
32
|
"react": "19.2.3",
|
|
28
33
|
"react-dom": "19.2.3",
|
|
29
34
|
"react-native": "0.86.3",
|
|
@@ -31,8 +36,8 @@
|
|
|
31
36
|
"typescript": "^6.0.3"
|
|
32
37
|
},
|
|
33
38
|
"devDependencies": {
|
|
34
|
-
"@backtickjs/prettier-plugin": "^0.1.
|
|
35
|
-
"@backtickjs/tsc": "^0.1.
|
|
39
|
+
"@backtickjs/prettier-plugin": "^0.1.10",
|
|
40
|
+
"@backtickjs/tsc": "^0.1.10",
|
|
36
41
|
"@testing-library/react": "^16.3.3",
|
|
37
42
|
"@testing-library/user-event": "^14.6.7",
|
|
38
43
|
"@types/node": "^25.9.1",
|
|
@@ -32,6 +32,15 @@ expo.on("exit", (code) => {
|
|
|
32
32
|
process.exit(code ?? 0);
|
|
33
33
|
});
|
|
34
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
|
+
|
|
35
44
|
// Expo shows how to open the app, its QR code, only in an interactive
|
|
36
45
|
// terminal. Run elsewhere, as an agent runs it, this prints the address once
|
|
37
46
|
// Metro is up.
|
|
@@ -0,0 +1,15 @@
|
|
|
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,50 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { cors } from "hono/cors";
|
|
4
4
|
import { Home } from "./Home.js";
|
|
5
|
+
import { screen } from "./screen.js";
|
|
6
|
+
|
|
7
|
+
const app = new Hono();
|
|
8
|
+
|
|
9
|
+
// `npm run web` serves the app from another origin.
|
|
10
|
+
app.use(cors({ origin: "*", allowHeaders: ["x-build-id"] }));
|
|
11
|
+
|
|
12
|
+
// Your app's screens: add a route, and the app can fetch it. A path can
|
|
13
|
+
// take parameters, `/stories/:id`, read with `context.req.param("id")`.
|
|
14
|
+
app.get("/", (context) => screen(context, <Home />));
|
|
5
15
|
|
|
6
16
|
// Development is only what `npm start` runs, which sets NODE_ENV: any other
|
|
7
17
|
// run, a deploy included, is production. In development, this run of the
|
|
8
18
|
// server: `--watch` starts a new one whenever you save, and the app, seeing a
|
|
9
19
|
// new one at `/live`, draws its screen again.
|
|
10
|
-
|
|
11
|
-
const run = crypto.randomUUID();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// project's own, as the app and this server share a package.json. A server
|
|
15
|
-
// for apps of many versions would tell them apart, by their user agent for
|
|
16
|
-
// one, and bundle each for its own.
|
|
17
|
-
const require = createRequire(import.meta.url);
|
|
18
|
-
const packageVersions = {
|
|
19
|
-
react: require("react/package.json").version,
|
|
20
|
-
"react-native": require("react-native/package.json").version,
|
|
21
|
-
};
|
|
20
|
+
if (process.env.NODE_ENV === "development") {
|
|
21
|
+
const run = crypto.randomUUID();
|
|
22
|
+
app.get("/live", (context) => context.text(run));
|
|
23
|
+
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
response.end(run);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (request.url === "/home") {
|
|
31
|
-
try {
|
|
32
|
-
const bundle = await bundler.build({ input: <Home />, packageVersions });
|
|
33
|
-
const { code } = bundle.generate({ format: "cjs" });
|
|
34
|
-
response.setHeader("content-type", "text/javascript");
|
|
35
|
-
response.end(code);
|
|
36
|
-
} catch (error) {
|
|
37
|
-
// Answered rather than crashed on, so the app can show why.
|
|
38
|
-
console.error(error);
|
|
39
|
-
response.statusCode = 500;
|
|
40
|
-
response.end(String(error));
|
|
41
|
-
}
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
response.statusCode = 404;
|
|
45
|
-
response.end();
|
|
25
|
+
// Answered rather than crashed on, so the app can show why.
|
|
26
|
+
app.onError((error, context) => {
|
|
27
|
+
console.error(error);
|
|
28
|
+
return context.text(String(error), 500);
|
|
46
29
|
});
|
|
47
30
|
|
|
48
|
-
|
|
31
|
+
serve({ fetch: app.fetch, port: 3000 }, () => {
|
|
49
32
|
console.log("Backtick server on http://localhost:3000");
|
|
50
33
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { createBuilds, DeprecatedBuild } from "@backtickjs/builds";
|
|
3
|
+
import { bundler } from "@backtickjs/bundler";
|
|
4
|
+
import type { JSX } from "@backtickjs/react/jsx-runtime";
|
|
5
|
+
import type { Context } from "hono";
|
|
6
|
+
import { AppUpdateRequired } from "./AppUpdateRequired.js";
|
|
7
|
+
|
|
8
|
+
// 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
|
|
10
|
+
// keeps working as your app moves on, once registered: see builds/.
|
|
11
|
+
const builds = createBuilds({
|
|
12
|
+
projectRoot: fileURLToPath(new URL("..", import.meta.url)),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
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.
|
|
19
|
+
*/
|
|
20
|
+
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 });
|
|
31
|
+
const { code } = bundle.generate({ format: "cjs" });
|
|
32
|
+
return context.body(code, 200, { "content-type": "text/javascript" });
|
|
33
|
+
}
|
|
@@ -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,15 +7,13 @@ 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
|
-
const modules = {
|
|
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 = {
|
|
18
17
|
react: React,
|
|
19
18
|
"react/jsx-runtime": JSXRuntime,
|
|
20
19
|
"react-native": ReactNativeWeb,
|
|
@@ -23,7 +22,7 @@ const modules = {
|
|
|
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,17 @@
|
|
|
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.10",
|
|
15
|
+
"@backtickjs/core": "^0.1.10",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.10",
|
|
17
17
|
"@backtickjs/solid-js": "^1.9.14",
|
|
18
|
+
"@hono/node-server": "^2.1.4",
|
|
19
|
+
"hono": "^4.13.13",
|
|
18
20
|
"typescript": "^6.0.3"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
21
|
-
"@backtickjs/prettier-plugin": "^0.1.
|
|
22
|
-
"@backtickjs/tsc": "^0.1.
|
|
23
|
+
"@backtickjs/prettier-plugin": "^0.1.10",
|
|
24
|
+
"@backtickjs/tsc": "^0.1.10",
|
|
23
25
|
"@types/node": "^25.9.1",
|
|
24
26
|
"prettier": "^3.6.2"
|
|
25
27
|
},
|
|
@@ -18,3 +18,9 @@ const server = spawn(runtime, ["--watch", ...loader, "server/index.tsx"], {
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
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
|
+
}
|
|
@@ -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.
|
|
@@ -1,33 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { Hono } from "hono";
|
|
2
3
|
import { Home } from "./Home.js";
|
|
3
4
|
import { development, toHtml } from "./html.js";
|
|
4
5
|
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
|
|
8
|
+
// Your pages: add a route, and the browser can open it. A path can take
|
|
9
|
+
// parameters, `/stories/:id`, read with `context.req.param("id")`.
|
|
10
|
+
app.get("/", async (context) => context.html(await toHtml(<Home />)));
|
|
11
|
+
|
|
5
12
|
// In development, this run of the server: `--watch` starts a new one whenever
|
|
6
13
|
// you save, and the page, seeing a new one at `/live`, reloads.
|
|
7
|
-
|
|
14
|
+
if (development) {
|
|
15
|
+
const run = crypto.randomUUID();
|
|
16
|
+
app.get("/live", (context) => context.text(run));
|
|
17
|
+
}
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
if (request.url === "/") {
|
|
15
|
-
try {
|
|
16
|
-
const html = await toHtml(<Home />);
|
|
17
|
-
response.setHeader("content-type", "text/html");
|
|
18
|
-
response.end(html);
|
|
19
|
-
} catch (error) {
|
|
20
|
-
// Answered rather than crashed on, so the browser can show why.
|
|
21
|
-
console.error(error);
|
|
22
|
-
response.statusCode = 500;
|
|
23
|
-
response.end(String(error));
|
|
24
|
-
}
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
response.statusCode = 404;
|
|
28
|
-
response.end();
|
|
19
|
+
// Answered rather than crashed on, so the browser can show why.
|
|
20
|
+
app.onError((error, context) => {
|
|
21
|
+
console.error(error);
|
|
22
|
+
return context.text(String(error), 500);
|
|
29
23
|
});
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
serve({ fetch: app.fetch, port: 3000 }, () => {
|
|
32
26
|
console.log("Backtick server on http://localhost:3000");
|
|
33
27
|
});
|