create-backtick-app 0.1.4 → 0.1.6
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 +16 -18
- package/dist/index.js +50 -43
- 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
|
@@ -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)
|
|
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.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -17,13 +17,7 @@ yarn create backtick-app
|
|
|
17
17
|
bun create backtick-app
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
It asks
|
|
21
|
-
|
|
22
|
-
1. What is your app named? (default `my-app`)
|
|
23
|
-
2. Which framework? `react-native` (an Expo app), `react` or `solid-js` (a web page)
|
|
24
|
-
3. Which runtime? `node` or `bun`: Bun is the default when you ran it with Bun (`bun create`), Node otherwise
|
|
25
|
-
|
|
26
|
-
It then copies the template into a new directory, installs with the package manager you ran it with (npm, pnpm, Yarn or Bun), and prints the commands to start. 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`). Given the name on the command line, it asks nothing. Requires Node 22.15 or later.
|
|
27
21
|
|
|
28
22
|
## Options
|
|
29
23
|
|
|
@@ -31,13 +25,13 @@ It then copies the template into a new directory, installs with the package mana
|
|
|
31
25
|
npx create-backtick-app@latest [name] [options]
|
|
32
26
|
```
|
|
33
27
|
|
|
34
|
-
| Option | Description
|
|
35
|
-
| ----------------------- |
|
|
36
|
-
| `name` | The directory to create, and the app's name. It must be new or empty.
|
|
37
|
-
| `-t, --template <name>` | `react-native`, `react` or `solid-js
|
|
38
|
-
| `-r, --runtime <name>` | What runs your Backtick server: `node
|
|
39
|
-
| `-y, --yes` |
|
|
40
|
-
| `--no-install` | Copy the files without installing dependencies.
|
|
28
|
+
| Option | Description |
|
|
29
|
+
| ----------------------- | --------------------------------------------------------------------- |
|
|
30
|
+
| `name` | The directory to create, and the app's name. It must be new or empty. |
|
|
31
|
+
| `-t, --template <name>` | `react-native`, the default; or `react` or `solid-js`, a web page. |
|
|
32
|
+
| `-r, --runtime <name>` | What runs your Backtick server: `node`, the default, or `bun`. |
|
|
33
|
+
| `-y, --yes` | Take `my-app` as the name rather than asking. |
|
|
34
|
+
| `--no-install` | Copy the files without installing dependencies. |
|
|
41
35
|
|
|
42
36
|
With `npm create`, pass options after `--`: `npm create backtick-app@latest my-app -- -t react`.
|
|
43
37
|
|
|
@@ -58,10 +52,13 @@ Every template has:
|
|
|
58
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). The app and the server share a `package.json`, so the server bundles for the React and React Native versions installed there.
|
|
59
53
|
|
|
60
54
|
```sh
|
|
61
|
-
npm
|
|
55
|
+
npm start # then scan the QR code with your phone, in Expo Go
|
|
56
|
+
npm run ios # or android, or web: a simulator, an emulator or a browser
|
|
62
57
|
```
|
|
63
58
|
|
|
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.
|
|
59
|
+
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.
|
|
60
|
+
|
|
61
|
+
`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
62
|
|
|
66
63
|
### `react` and `solid-js`
|
|
67
64
|
|
|
@@ -78,7 +75,8 @@ Then open http://localhost:3000. Edit `server/Home.tsx` and save to see the page
|
|
|
78
75
|
- [`@backtickjs/core`](https://github.com/backtickjs/backtick/tree/main/packages/core): the `cs` tag
|
|
79
76
|
- [`@backtickjs/bundler`](https://github.com/backtickjs/backtick/tree/main/packages/bundler): runs server components and writes the bundle
|
|
80
77
|
- [`@backtickjs/react-native-client`](https://github.com/backtickjs/backtick/tree/main/packages/react-native-client): runs a bundle in a React Native app
|
|
81
|
-
- [
|
|
78
|
+
- [Quick start](https://backtickjs.com/docs)
|
|
79
|
+
- [Tutorial](https://backtickjs.com/docs/tutorial)
|
|
82
80
|
|
|
83
81
|
## License
|
|
84
82
|
|
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
|
|
@@ -33,15 +49,15 @@ const RUNTIMES = {
|
|
|
33
49
|
};
|
|
34
50
|
const USAGE = `Usage: create-backtick-app [name] [options]
|
|
35
51
|
|
|
52
|
+
Creates a React Native app with a Backtick server beside it, on Node, or on
|
|
53
|
+
Bun when run with Bun.
|
|
54
|
+
|
|
36
55
|
Options:
|
|
37
56
|
-t, --template <name> The framework: ${Object.keys(TEMPLATES).join(", ")}
|
|
38
57
|
-r, --runtime <name> The server's runtime: ${Object.keys(RUNTIMES).join(", ")}
|
|
39
|
-
-y, --yes
|
|
58
|
+
-y, --yes Take my-app as the name rather than asking
|
|
40
59
|
--no-install Skip installing packages
|
|
41
|
-
-h, --help Show this help
|
|
42
|
-
|
|
43
|
-
Asks for what isn't given, in a terminal; elsewhere, uses the defaults:
|
|
44
|
-
my-app, react-native, and node (bun when run with bun).`;
|
|
60
|
+
-h, --help Show this help`;
|
|
45
61
|
let parsed;
|
|
46
62
|
try {
|
|
47
63
|
parsed = parseArgs({
|
|
@@ -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);
|
|
@@ -80,41 +103,24 @@ if (values.runtime !== undefined && !(values.runtime in RUNTIMES)) {
|
|
|
80
103
|
const manager = (process.env.npm_config_user_agent ?? "npm").split("/")[0];
|
|
81
104
|
const run = ["npm", "pnpm", "yarn", "bun"].includes(manager) ? manager : "npm";
|
|
82
105
|
const suggestedRuntime = run === "bun" ? "bun" : "node";
|
|
83
|
-
//
|
|
84
|
-
// agent or a CI script can't answer, so it gets
|
|
85
|
-
|
|
106
|
+
// The name is the one question, asked when none was given, and only in a
|
|
107
|
+
// terminal: an agent or a CI script can't answer, so it gets `my-app`, as
|
|
108
|
+
// `--yes` does. The framework and the runtime have defaults, and flags for
|
|
109
|
+
// the rest, as create-expo-app does: a name on the command line is a project
|
|
110
|
+
// with no questions.
|
|
111
|
+
const ask = positionals[0] === undefined && process.stdin.isTTY === true && !values.yes;
|
|
86
112
|
const answers = await prompts([
|
|
87
113
|
{
|
|
88
|
-
type:
|
|
114
|
+
type: ask ? "text" : null,
|
|
89
115
|
name: "name",
|
|
90
116
|
message: "What is your app named?",
|
|
91
117
|
initial: "my-app",
|
|
92
118
|
},
|
|
93
|
-
{
|
|
94
|
-
type: values.template !== undefined || !ask ? null : "select",
|
|
95
|
-
name: "template",
|
|
96
|
-
message: "Which framework?",
|
|
97
|
-
choices: Object.entries(TEMPLATES).map(([value, template]) => ({
|
|
98
|
-
title: template.title,
|
|
99
|
-
description: template.description,
|
|
100
|
-
value,
|
|
101
|
-
})),
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
type: values.runtime !== undefined || !ask ? null : "select",
|
|
105
|
-
name: "runtime",
|
|
106
|
-
message: "Which runtime?",
|
|
107
|
-
choices: Object.entries(RUNTIMES).map(([value, runtime]) => ({
|
|
108
|
-
title: runtime.title,
|
|
109
|
-
value,
|
|
110
|
-
})),
|
|
111
|
-
initial: Object.keys(RUNTIMES).indexOf(suggestedRuntime),
|
|
112
|
-
},
|
|
113
119
|
], { onCancel: () => process.exit(1) });
|
|
114
120
|
const name = positionals[0] ?? answers.name ?? "my-app";
|
|
115
|
-
const templateName = values.template ??
|
|
121
|
+
const templateName = values.template ?? "react-native";
|
|
116
122
|
const template = TEMPLATES[templateName];
|
|
117
|
-
const runtimeName = values.runtime ??
|
|
123
|
+
const runtimeName = values.runtime ?? suggestedRuntime;
|
|
118
124
|
const target = path.resolve(name);
|
|
119
125
|
const slug = path.basename(target);
|
|
120
126
|
if (existsSync(target) && readdirSync(target).length > 0) {
|
|
@@ -162,15 +168,16 @@ if (!values["no-install"]) {
|
|
|
162
168
|
process.exit(status ?? 1);
|
|
163
169
|
}
|
|
164
170
|
}
|
|
171
|
+
// Created where it was run, there's nowhere to go first.
|
|
172
|
+
const here = target === process.cwd();
|
|
173
|
+
const script = (name) => `${run} run ${name}`;
|
|
174
|
+
const start = [...(here ? [] : [`cd ${slug}`]), script("start")]
|
|
175
|
+
.map((command) => ` ${command}`)
|
|
176
|
+
.join("\n");
|
|
165
177
|
console.log(`
|
|
166
178
|
✅ Your project is ready!
|
|
167
179
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
- cd ${slug}
|
|
171
|
-
${template.scripts.map((script) => `- ${run} run ${script}`).join("\n")}
|
|
172
|
-
|
|
173
|
-
${template.then}
|
|
180
|
+
${template.next(start, script)}
|
|
174
181
|
`);
|
|
175
182
|
function rewriteJson(file, change) {
|
|
176
183
|
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.6",
|
|
15
|
+
"@backtickjs/core": "^0.1.6",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.6",
|
|
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.6",
|
|
22
|
+
"@backtickjs/tsc": "^0.1.6",
|
|
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.6",
|
|
19
|
+
"@backtickjs/core": "^0.1.6",
|
|
20
|
+
"@backtickjs/node-plugin": "^0.1.6",
|
|
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.6",
|
|
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.6",
|
|
35
|
+
"@backtickjs/tsc": "^0.1.6",
|
|
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.6",
|
|
15
|
+
"@backtickjs/core": "^0.1.6",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.6",
|
|
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.6",
|
|
22
|
+
"@backtickjs/tsc": "^0.1.6",
|
|
23
23
|
"@types/node": "^25.9.1",
|
|
24
24
|
"prettier": "^3.6.2"
|
|
25
25
|
},
|