create-backtick-app 0.1.3 → 0.1.5
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 -3
- package/dist/index.js +36 -12
- package/dist/nodeVersion.d.ts +1 -0
- package/dist/nodeVersion.js +7 -0
- package/package.json +1 -1
- package/templates/react/package.json +5 -5
- package/templates/react-native/package.json +10 -6
- package/templates/react-native/scripts/reset-project.mjs +14 -0
- package/templates/react-native/scripts/start.mjs +26 -0
- package/templates/react-native/server/HelloWave.tsx +5 -3
- package/templates/react-native/server/Home.test.tsx +11 -0
- package/templates/react-native/server/test/drawScreen.ts +29 -0
- package/templates/react-native/server/test/react-native-web.d.ts +2 -0
- package/templates/react-native/server/test/setup.mjs +21 -0
- package/templates/solid-js/package.json +5 -5
package/README.md
CHANGED
|
@@ -58,10 +58,13 @@ Every template has:
|
|
|
58
58
|
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). The app and the server share a `package.json`, so the server bundles for the React and React Native versions installed there.
|
|
59
59
|
|
|
60
60
|
```sh
|
|
61
|
-
npm
|
|
61
|
+
npm start # then scan the QR code with your phone, in Expo Go
|
|
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.
|
|
65
|
+
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.
|
|
66
|
+
|
|
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.
|
|
65
68
|
|
|
66
69
|
### `react` and `solid-js`
|
|
67
70
|
|
|
@@ -78,7 +81,8 @@ Then open http://localhost:3000. Edit `server/Home.tsx` and save to see the page
|
|
|
78
81
|
- [`@backtickjs/core`](https://github.com/backtickjs/backtick/tree/main/packages/core): the `cs` tag
|
|
79
82
|
- [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): runs server components and writes the bundle
|
|
80
83
|
- [`@backtickjs/react-native-client`](https://github.com/backtickjs/backtick/tree/main/packages/react-native-client): runs a bundle in a React Native app
|
|
81
|
-
- [
|
|
84
|
+
- [Quick start](https://backtickjs.com/docs)
|
|
85
|
+
- [Tutorial](https://backtickjs.com/docs/tutorial)
|
|
82
86
|
|
|
83
87
|
## License
|
|
84
88
|
|
package/dist/index.js
CHANGED
|
@@ -5,24 +5,40 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import prompts from "prompts";
|
|
8
|
+
import { isSupportedNode } from "./nodeVersion.js";
|
|
9
|
+
// The web templates' next steps.
|
|
10
|
+
const webNext = (start) => `To run your project:
|
|
11
|
+
|
|
12
|
+
${start}
|
|
13
|
+
|
|
14
|
+
Then open http://localhost:3000. Edit server/Home.tsx and save to see the page change.`;
|
|
8
15
|
const TEMPLATES = {
|
|
9
16
|
"react-native": {
|
|
10
17
|
title: "React Native",
|
|
11
18
|
description: "An Expo app, its screens from your server",
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
next: (start, run) => `To open it on your phone:
|
|
20
|
+
|
|
21
|
+
1. Install Expo Go on your phone, from the App Store or Google Play.
|
|
22
|
+
2. Connect your phone to the same Wi-Fi network as this computer.
|
|
23
|
+
3. Start your project, which starts your Backtick server alongside Expo:
|
|
24
|
+
|
|
25
|
+
${start}
|
|
26
|
+
|
|
27
|
+
4. Scan the QR code it shows: with the Camera app on iPhone, or with Expo Go on Android.
|
|
28
|
+
|
|
29
|
+
Then edit server/Home.tsx and save to see the screen change on your phone.
|
|
30
|
+
|
|
31
|
+
No phone at hand? ${run("ios")}, ${run("android")} or ${run("web")} opens it in the iOS Simulator, an Android emulator or a browser instead.`,
|
|
14
32
|
},
|
|
15
33
|
react: {
|
|
16
34
|
title: "React",
|
|
17
35
|
description: "A web page, rendered by React",
|
|
18
|
-
|
|
19
|
-
then: "Then open http://localhost:3000. Edit server/Home.tsx and save to see the page change.",
|
|
36
|
+
next: webNext,
|
|
20
37
|
},
|
|
21
38
|
"solid-js": {
|
|
22
39
|
title: "solid-js",
|
|
23
40
|
description: "A web page, rendered by Solid",
|
|
24
|
-
|
|
25
|
-
then: "Then open http://localhost:3000. Edit server/Home.tsx and save to see the page change.",
|
|
41
|
+
next: webNext,
|
|
26
42
|
},
|
|
27
43
|
};
|
|
28
44
|
// What runs your Backtick server, and the loader that compiles it for that
|
|
@@ -67,6 +83,13 @@ if (values.help) {
|
|
|
67
83
|
console.log(USAGE);
|
|
68
84
|
process.exit(0);
|
|
69
85
|
}
|
|
86
|
+
// Said, not refused: what fails on another version is the app's toolchain,
|
|
87
|
+
// later, and only some of it.
|
|
88
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
89
|
+
console.warn(`Backtick and React Native support Node 22 (22.15 or later) and 24 (24.3 or ` +
|
|
90
|
+
`later), and you're on Node ${process.versions.node}. If something fails, ` +
|
|
91
|
+
`switch to Node 22 or 24.\n`);
|
|
92
|
+
}
|
|
70
93
|
if (values.template !== undefined && !(values.template in TEMPLATES)) {
|
|
71
94
|
console.error(`There's no template named ${values.template}. Pick one of: ${Object.keys(TEMPLATES).join(", ")}.`);
|
|
72
95
|
process.exit(1);
|
|
@@ -162,15 +185,16 @@ if (!values["no-install"]) {
|
|
|
162
185
|
process.exit(status ?? 1);
|
|
163
186
|
}
|
|
164
187
|
}
|
|
188
|
+
// Created where it was run, there's nowhere to go first.
|
|
189
|
+
const here = target === process.cwd();
|
|
190
|
+
const script = (name) => `${run} run ${name}`;
|
|
191
|
+
const start = [...(here ? [] : [`cd ${slug}`]), script("start")]
|
|
192
|
+
.map((command) => ` ${command}`)
|
|
193
|
+
.join("\n");
|
|
165
194
|
console.log(`
|
|
166
195
|
✅ Your project is ready!
|
|
167
196
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
- cd ${slug}
|
|
171
|
-
${template.scripts.map((script) => `- ${run} run ${script}`).join("\n")}
|
|
172
|
-
|
|
173
|
-
${template.then}
|
|
197
|
+
${template.next(start, script)}
|
|
174
198
|
`);
|
|
175
199
|
function rewriteJson(file, change) {
|
|
176
200
|
const json = JSON.parse(readFileSync(file, "utf8"));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isSupportedNode(version: string): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Whether a Node version runs a Backtick project: what React Native 0.86 and
|
|
2
|
+
// Backtick's Node plugin both support, 22.15 and later in 22, 24.3 and later
|
|
3
|
+
// in 24, and 25 on. Node 23 runs neither reliably.
|
|
4
|
+
export function isSupportedNode(version) {
|
|
5
|
+
const [major = 0, minor = 0] = version.split(".").map(Number);
|
|
6
|
+
return ((major === 22 && minor >= 15) || (major === 24 && minor >= 3) || major >= 25);
|
|
7
|
+
}
|
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.5",
|
|
15
|
+
"@backtickjs/core": "^0.1.5",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.5",
|
|
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.5",
|
|
22
|
+
"@backtickjs/tsc": "^0.1.5",
|
|
23
23
|
"@types/node": "^25.9.1",
|
|
24
24
|
"prettier": "^3.6.2"
|
|
25
25
|
},
|
|
@@ -9,17 +9,18 @@
|
|
|
9
9
|
"ios": "node scripts/start.mjs --ios",
|
|
10
10
|
"web": "node scripts/start.mjs --web",
|
|
11
11
|
"typecheck": "tsc --noEmit && backtick-tsc -p server --noEmit",
|
|
12
|
+
"test": "node --import ./server/test/setup.mjs --import @backtickjs/node-plugin --test \"server/**/*.test.tsx\"",
|
|
12
13
|
"reset-project": "node scripts/reset-project.mjs",
|
|
13
14
|
"format": "prettier --write --ignore-unknown .",
|
|
14
15
|
"format:check": "prettier --check --ignore-unknown ."
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
17
|
-
"@backtickjs/bundler": "^0.1.
|
|
18
|
-
"@backtickjs/core": "^0.1.
|
|
19
|
-
"@backtickjs/node-plugin": "^0.1.
|
|
18
|
+
"@backtickjs/bundler": "^0.1.5",
|
|
19
|
+
"@backtickjs/core": "^0.1.5",
|
|
20
|
+
"@backtickjs/node-plugin": "^0.1.5",
|
|
20
21
|
"@backtickjs/react": "^19.2.3",
|
|
21
22
|
"@backtickjs/react-native": "^0.86.3",
|
|
22
|
-
"@backtickjs/react-native-client": "^0.1.
|
|
23
|
+
"@backtickjs/react-native-client": "^0.1.5",
|
|
23
24
|
"expo": "~57.0.26",
|
|
24
25
|
"expo-constants": "~57.0.20",
|
|
25
26
|
"expo-status-bar": "~57.0.1",
|
|
@@ -30,10 +31,13 @@
|
|
|
30
31
|
"typescript": "^6.0.3"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@backtickjs/prettier-plugin": "^0.1.
|
|
34
|
-
"@backtickjs/tsc": "^0.1.
|
|
34
|
+
"@backtickjs/prettier-plugin": "^0.1.5",
|
|
35
|
+
"@backtickjs/tsc": "^0.1.5",
|
|
36
|
+
"@testing-library/react": "^16.3.3",
|
|
35
37
|
"@types/node": "^25.9.1",
|
|
36
38
|
"@types/react": "19.2.18",
|
|
39
|
+
"global-jsdom": "^30.0.0",
|
|
40
|
+
"jsdom": "^30.0.1",
|
|
37
41
|
"prettier": "^3.6.2"
|
|
38
42
|
},
|
|
39
43
|
"backtick": {
|
|
@@ -15,6 +15,20 @@ export async function Home() {
|
|
|
15
15
|
}
|
|
16
16
|
`,
|
|
17
17
|
);
|
|
18
|
+
writeFileSync(
|
|
19
|
+
"server/Home.test.tsx",
|
|
20
|
+
`import assert from "node:assert/strict";
|
|
21
|
+
import { it } from "node:test";
|
|
22
|
+
import { render, screen } from "@testing-library/react";
|
|
23
|
+
import { Home } from "./Home.js";
|
|
24
|
+
import { drawScreen } from "./test/drawScreen.js";
|
|
25
|
+
|
|
26
|
+
it("asks to be edited", async () => {
|
|
27
|
+
render(await drawScreen(<Home />));
|
|
28
|
+
assert.ok(screen.getByText("Edit server/Home.tsx to edit this screen."));
|
|
29
|
+
});
|
|
30
|
+
`,
|
|
31
|
+
);
|
|
18
32
|
rmSync("server/HelloWave.tsx", { force: true });
|
|
19
33
|
|
|
20
34
|
console.log(
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
// `--ios`.
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
|
+
import { networkInterfaces } from "node:os";
|
|
8
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
7
9
|
|
|
8
10
|
// The runtime is the one package.json has a Backtick loader for: Bun's, or
|
|
9
11
|
// else Node's.
|
|
@@ -29,3 +31,27 @@ expo.on("exit", (code) => {
|
|
|
29
31
|
server.kill();
|
|
30
32
|
process.exit(code ?? 0);
|
|
31
33
|
});
|
|
34
|
+
|
|
35
|
+
// Expo shows how to open the app, its QR code, only in an interactive
|
|
36
|
+
// terminal. Run elsewhere, as an agent runs it, this prints the address once
|
|
37
|
+
// Metro is up.
|
|
38
|
+
if (!process.stdout.isTTY) {
|
|
39
|
+
const args = process.argv.slice(2);
|
|
40
|
+
const flag = args.findIndex((arg) => arg === "--port" || arg === "-p");
|
|
41
|
+
const port = flag === -1 ? 8081 : Number(args[flag + 1]);
|
|
42
|
+
const address = Object.values(networkInterfaces())
|
|
43
|
+
.flat()
|
|
44
|
+
.find(
|
|
45
|
+
(network) => network?.family === "IPv4" && !network.internal,
|
|
46
|
+
)?.address;
|
|
47
|
+
while (address !== undefined && expo.exitCode === null) {
|
|
48
|
+
const status = await fetch(`http://localhost:${port}/status`)
|
|
49
|
+
.then((response) => response.text())
|
|
50
|
+
.catch(() => "");
|
|
51
|
+
if (status === "packager-status:running") {
|
|
52
|
+
console.log(`\nOpen exp://${address}:${port} in Expo Go.\n`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
await sleep(500);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { cs } from "@backtickjs/core";
|
|
2
2
|
import { useEffect, useRef } from "@backtickjs/react";
|
|
3
|
-
import { Animated, Pressable } from "@backtickjs/react-native";
|
|
3
|
+
import { Animated, Platform, Pressable } from "@backtickjs/react-native";
|
|
4
4
|
|
|
5
5
|
// A client component: it runs on the phone, so its animation does too. It
|
|
6
6
|
// waves when it appears, and again when you tap it.
|
|
7
7
|
export const HelloWave = cs`() => {
|
|
8
8
|
const rotation = $useRef(new $Animated.Value(0)).current;
|
|
9
|
+
// The web has no native animation driver.
|
|
10
|
+
const nativeDriver = $Platform.OS !== "web";
|
|
9
11
|
const wave = () =>
|
|
10
12
|
$Animated
|
|
11
13
|
.loop(
|
|
@@ -13,12 +15,12 @@ export const HelloWave = cs`() => {
|
|
|
13
15
|
$Animated.timing(rotation, {
|
|
14
16
|
toValue: 1,
|
|
15
17
|
duration: 150,
|
|
16
|
-
useNativeDriver:
|
|
18
|
+
useNativeDriver: nativeDriver,
|
|
17
19
|
}),
|
|
18
20
|
$Animated.timing(rotation, {
|
|
19
21
|
toValue: 0,
|
|
20
22
|
duration: 150,
|
|
21
|
-
useNativeDriver:
|
|
23
|
+
useNativeDriver: nativeDriver,
|
|
22
24
|
}),
|
|
23
25
|
]),
|
|
24
26
|
{ iterations: 4 },
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { it } from "node:test";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import { Home } from "./Home.js";
|
|
5
|
+
import { drawScreen } from "./test/drawScreen.js";
|
|
6
|
+
|
|
7
|
+
it("shows the Node version the server ran", async () => {
|
|
8
|
+
render(await drawScreen(<Home />));
|
|
9
|
+
assert.ok(screen.getByText("Welcome!"));
|
|
10
|
+
assert.ok(screen.getByText(new RegExp(`with Node ${process.version}`)));
|
|
11
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { bundler } from "@backtickjs/bundler";
|
|
3
|
+
import type { Spliceable } from "@backtickjs/core";
|
|
4
|
+
import { evaluate } from "@backtickjs/react-native-client";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import * as JSXRuntime from "react/jsx-runtime";
|
|
7
|
+
import * as ReactNativeWeb from "react-native-web";
|
|
8
|
+
|
|
9
|
+
// What the app provides, as `server/index.tsx` and `App.tsx` say, with React
|
|
10
|
+
// Native's web build standing in for React Native. Add a package here when
|
|
11
|
+
// your app provides one.
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const packageVersions = {
|
|
14
|
+
react: require("react/package.json").version,
|
|
15
|
+
"react-native": require("react-native/package.json").version,
|
|
16
|
+
};
|
|
17
|
+
const modules = {
|
|
18
|
+
react: React,
|
|
19
|
+
"react/jsx-runtime": JSXRuntime,
|
|
20
|
+
"react-native": ReactNativeWeb,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// A screen, bundled as your server bundles it and run as your app runs it,
|
|
24
|
+
// for Testing Library to draw.
|
|
25
|
+
export async function drawScreen(input: Spliceable): Promise<React.ReactNode> {
|
|
26
|
+
const bundle = await bundler.build({ input, packageVersions });
|
|
27
|
+
const { code } = bundle.generate({ format: "cjs" });
|
|
28
|
+
return evaluate(code, modules) as React.ReactNode;
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// What `npm test` runs in: jsdom as the document Testing Library draws into,
|
|
2
|
+
// React told it's under test, and each test's drawing cleared after it.
|
|
3
|
+
import "global-jsdom/register";
|
|
4
|
+
import { cleanup } from "@testing-library/react";
|
|
5
|
+
import { afterEach } from "node:test";
|
|
6
|
+
|
|
7
|
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
|
|
8
|
+
|
|
9
|
+
afterEach(cleanup);
|
|
10
|
+
|
|
11
|
+
// jsdom has no ResizeObserver, which React Native's web build looks for as it
|
|
12
|
+
// loads, to report layout.
|
|
13
|
+
window.ResizeObserver ??= class {
|
|
14
|
+
observe() {}
|
|
15
|
+
unobserve() {}
|
|
16
|
+
disconnect() {}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Loaded before `@backtickjs/node-plugin`, so Node loads it as plain
|
|
20
|
+
// CommonJS.
|
|
21
|
+
await import("react-native-web");
|
|
@@ -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.5",
|
|
15
|
+
"@backtickjs/core": "^0.1.5",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.5",
|
|
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.5",
|
|
22
|
+
"@backtickjs/tsc": "^0.1.5",
|
|
23
23
|
"@types/node": "^25.9.1",
|
|
24
24
|
"prettier": "^3.6.2"
|
|
25
25
|
},
|