create-backtick-app 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +84 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +150 -0
- package/package.json +56 -3
- package/templates/react/.prettierignore +8 -0
- package/templates/react/.prettierrc +3 -0
- package/templates/react/.vscode/extensions.json +6 -0
- package/templates/react/gitignore +16 -0
- package/templates/react/package.json +31 -0
- package/templates/react/scripts/reset-project.mjs +21 -0
- package/templates/react/scripts/start.mjs +20 -0
- package/templates/react/server/HelloWave.tsx +30 -0
- package/templates/react/server/Home.tsx +65 -0
- package/templates/react/server/html.ts +62 -0
- package/templates/react/server/index.tsx +33 -0
- package/templates/react/tsconfig.json +18 -0
- package/templates/react-native/.prettierignore +16 -0
- package/templates/react-native/.prettierrc +3 -0
- package/templates/react-native/.vscode/extensions.json +6 -0
- package/templates/react-native/App.tsx +117 -0
- package/templates/react-native/app.json +9 -0
- package/templates/react-native/gitignore +41 -0
- package/templates/react-native/index.ts +8 -0
- package/templates/react-native/package.json +44 -0
- package/templates/react-native/scripts/reset-project.mjs +22 -0
- package/templates/react-native/scripts/start.mjs +31 -0
- package/templates/react-native/server/HelloWave.tsx +51 -0
- package/templates/react-native/server/Home.tsx +84 -0
- package/templates/react-native/server/index.tsx +50 -0
- package/templates/react-native/server/tsconfig.json +17 -0
- package/templates/react-native/tsconfig.json +7 -0
- package/templates/solid-js/.prettierignore +8 -0
- package/templates/solid-js/.prettierrc +3 -0
- package/templates/solid-js/.vscode/extensions.json +6 -0
- package/templates/solid-js/gitignore +16 -0
- package/templates/solid-js/package.json +31 -0
- package/templates/solid-js/scripts/reset-project.mjs +21 -0
- package/templates/solid-js/scripts/start.mjs +20 -0
- package/templates/solid-js/server/HelloWave.tsx +34 -0
- package/templates/solid-js/server/Home.tsx +65 -0
- package/templates/solid-js/server/html.ts +62 -0
- package/templates/solid-js/server/index.tsx +33 -0
- package/templates/solid-js/tsconfig.json +18 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { evaluate } from "@backtickjs/react-native-client";
|
|
2
|
+
import Constants from "expo-constants";
|
|
3
|
+
import { StatusBar } from "expo-status-bar";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import {
|
|
6
|
+
Component,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
Suspense,
|
|
9
|
+
use,
|
|
10
|
+
useEffect,
|
|
11
|
+
useState,
|
|
12
|
+
} from "react";
|
|
13
|
+
import * as JSXRuntime from "react/jsx-runtime";
|
|
14
|
+
import * as ReactNative from "react-native";
|
|
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
|
+
};
|
|
23
|
+
|
|
24
|
+
// Your Backtick server runs on the computer Expo was started from.
|
|
25
|
+
const host = Constants.expoConfig?.hostUri?.split(":")[0] ?? "localhost";
|
|
26
|
+
const server = `http://${host}:3000`;
|
|
27
|
+
|
|
28
|
+
// The screen at `path` on your server: its bundle, fetched and run with the
|
|
29
|
+
// app's packages. Your fetch to shape: headers, auth, caching.
|
|
30
|
+
async function fetchScreen(path: string): Promise<ReactNode> {
|
|
31
|
+
const response = await fetch(`${server}${path}`);
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`${path} answered ${response.status}.`);
|
|
34
|
+
}
|
|
35
|
+
return evaluate(await response.text(), modules) as ReactNode;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function App() {
|
|
39
|
+
// Held here, which doesn't suspend, so the request outlives the render
|
|
40
|
+
// that waits for it. Each request is numbered, so a new one also clears an
|
|
41
|
+
// error the last one showed.
|
|
42
|
+
const [request, setRequest] = useState(() => ({
|
|
43
|
+
number: 0,
|
|
44
|
+
screen: fetchScreen("/home"),
|
|
45
|
+
}));
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
// In development, the screen is fetched again each time the server
|
|
48
|
+
// restarts, so a change to server/ shows as soon as you save.
|
|
49
|
+
if (__DEV__) {
|
|
50
|
+
return onServerRestart(() =>
|
|
51
|
+
setRequest(({ number }) => ({
|
|
52
|
+
number: number + 1,
|
|
53
|
+
screen: fetchScreen("/home"),
|
|
54
|
+
})),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}, []);
|
|
58
|
+
// The whole screen, its layout included, comes from the server: see
|
|
59
|
+
// server/Home.tsx.
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<Boundary key={request.number}>
|
|
63
|
+
<Suspense fallback={<ActivityIndicator style={{ flex: 1 }} />}>
|
|
64
|
+
<Screen screen={request.screen} />
|
|
65
|
+
</Suspense>
|
|
66
|
+
</Boundary>
|
|
67
|
+
<StatusBar style="auto" />
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// What the server sent, once it has: suspending until then, and throwing,
|
|
73
|
+
// for the boundary, a screen that couldn't be fetched or run.
|
|
74
|
+
function Screen({ screen }: { screen: Promise<ReactNode> }) {
|
|
75
|
+
return use(screen);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Calls `restarted` when the server starts a new run: it names each run at
|
|
79
|
+
// `/live`, and a new name is a new run.
|
|
80
|
+
function onServerRestart(restarted: () => void): () => void {
|
|
81
|
+
let run: string | undefined;
|
|
82
|
+
const timer = setInterval(async () => {
|
|
83
|
+
try {
|
|
84
|
+
const next = await (await fetch(`${server}/live`)).text();
|
|
85
|
+
if (run !== undefined && next !== run) {
|
|
86
|
+
restarted();
|
|
87
|
+
}
|
|
88
|
+
run = next;
|
|
89
|
+
} catch {
|
|
90
|
+
// Restarting: the next tick finds the new run.
|
|
91
|
+
}
|
|
92
|
+
}, 1000);
|
|
93
|
+
return () => clearInterval(timer);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Why the screen couldn't be drawn: the server unreachable, or a screen the
|
|
97
|
+
// app can't run.
|
|
98
|
+
class Boundary extends Component<{ children: ReactNode }, { error: unknown }> {
|
|
99
|
+
state = { error: null as unknown };
|
|
100
|
+
|
|
101
|
+
static getDerivedStateFromError(error: unknown) {
|
|
102
|
+
return { error };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
render() {
|
|
106
|
+
if (this.state.error === null) {
|
|
107
|
+
return this.props.children;
|
|
108
|
+
}
|
|
109
|
+
return (
|
|
110
|
+
<Text style={{ margin: 24, marginTop: 96, color: "#b91c1c" }}>
|
|
111
|
+
{String((this.state.error as Error).message ?? this.state.error)}
|
|
112
|
+
{"\n\n"}Is your Backtick server running? npm start runs it alongside
|
|
113
|
+
Expo.
|
|
114
|
+
</Text>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
node_modules/
|
|
5
|
+
|
|
6
|
+
# Expo
|
|
7
|
+
.expo/
|
|
8
|
+
dist/
|
|
9
|
+
web-build/
|
|
10
|
+
expo-env.d.ts
|
|
11
|
+
|
|
12
|
+
# Native
|
|
13
|
+
.kotlin/
|
|
14
|
+
*.orig.*
|
|
15
|
+
*.jks
|
|
16
|
+
*.p8
|
|
17
|
+
*.p12
|
|
18
|
+
*.key
|
|
19
|
+
*.mobileprovision
|
|
20
|
+
|
|
21
|
+
# Metro
|
|
22
|
+
.metro-health-check*
|
|
23
|
+
|
|
24
|
+
# debug
|
|
25
|
+
npm-debug.*
|
|
26
|
+
yarn-debug.*
|
|
27
|
+
yarn-error.*
|
|
28
|
+
|
|
29
|
+
# macOS
|
|
30
|
+
.DS_Store
|
|
31
|
+
*.pem
|
|
32
|
+
|
|
33
|
+
# local env files
|
|
34
|
+
.env*.local
|
|
35
|
+
|
|
36
|
+
# typescript
|
|
37
|
+
*.tsbuildinfo
|
|
38
|
+
|
|
39
|
+
# generated native folders
|
|
40
|
+
/ios
|
|
41
|
+
/android
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { registerRootComponent } from "expo";
|
|
2
|
+
|
|
3
|
+
import App from "./App";
|
|
4
|
+
|
|
5
|
+
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
|
|
6
|
+
// It also ensures that whether you load the app in Expo Go or in a native build,
|
|
7
|
+
// the environment is set up appropriately
|
|
8
|
+
registerRootComponent(App);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backtick-app-react-native",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node scripts/start.mjs",
|
|
8
|
+
"android": "node scripts/start.mjs --android",
|
|
9
|
+
"ios": "node scripts/start.mjs --ios",
|
|
10
|
+
"web": "node scripts/start.mjs --web",
|
|
11
|
+
"typecheck": "tsc --noEmit && backtick-tsc -p server --noEmit",
|
|
12
|
+
"reset-project": "node scripts/reset-project.mjs",
|
|
13
|
+
"format": "prettier --write --ignore-unknown .",
|
|
14
|
+
"format:check": "prettier --check --ignore-unknown ."
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@backtickjs/bundler": "^0.1.1",
|
|
18
|
+
"@backtickjs/core": "^0.1.1",
|
|
19
|
+
"@backtickjs/node-plugin": "^0.1.1",
|
|
20
|
+
"@backtickjs/react": "^19.2.3",
|
|
21
|
+
"@backtickjs/react-native": "^0.86.3",
|
|
22
|
+
"@backtickjs/react-native-client": "^0.1.1",
|
|
23
|
+
"expo": "~57.0.26",
|
|
24
|
+
"expo-constants": "~57.0.20",
|
|
25
|
+
"expo-status-bar": "~57.0.1",
|
|
26
|
+
"react": "19.2.3",
|
|
27
|
+
"react-dom": "19.2.3",
|
|
28
|
+
"react-native": "0.86.3",
|
|
29
|
+
"react-native-web": "^0.21.0",
|
|
30
|
+
"typescript": "^6.0.3"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@backtickjs/prettier-plugin": "^0.1.1",
|
|
34
|
+
"@backtickjs/tsc": "^0.1.1",
|
|
35
|
+
"@types/node": "^25.9.1",
|
|
36
|
+
"@types/react": "19.2.18",
|
|
37
|
+
"prettier": "^3.6.2"
|
|
38
|
+
},
|
|
39
|
+
"backtick": {
|
|
40
|
+
"plugins": [
|
|
41
|
+
"@backtickjs/react/plugin"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Replaces the welcome screen with a blank one, so you start fresh.
|
|
2
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
writeFileSync(
|
|
5
|
+
"server/Home.tsx",
|
|
6
|
+
`import { cs } from "@backtickjs/core";
|
|
7
|
+
import { Text, View } from "@backtickjs/react-native";
|
|
8
|
+
|
|
9
|
+
export async function Home() {
|
|
10
|
+
return cs\`(
|
|
11
|
+
<$View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
|
12
|
+
<$Text>Edit server/Home.tsx to edit this screen.</$Text>
|
|
13
|
+
</$View>
|
|
14
|
+
)\`;
|
|
15
|
+
}
|
|
16
|
+
`,
|
|
17
|
+
);
|
|
18
|
+
rmSync("server/HelloWave.tsx", { force: true });
|
|
19
|
+
|
|
20
|
+
console.log(
|
|
21
|
+
"✅ Project reset. server/Home.tsx is now a blank screen, and the welcome example is gone.",
|
|
22
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Starts your Backtick server in development, then Expo, both in this terminal:
|
|
2
|
+
// Expo keeps its keys and QR code, and the server restarts whenever a file
|
|
3
|
+
// under `server/` changes. Arguments are Expo's, as `npm run ios` passes
|
|
4
|
+
// `--ios`.
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
|
|
8
|
+
// The runtime is the one package.json has a Backtick loader for: Bun's, or
|
|
9
|
+
// else Node's.
|
|
10
|
+
const { dependencies } = JSON.parse(readFileSync("package.json", "utf8"));
|
|
11
|
+
const [runtime, ...loader] =
|
|
12
|
+
"@backtickjs/bun-plugin" in dependencies
|
|
13
|
+
? ["bun", "--preload", "@backtickjs/bun-plugin"]
|
|
14
|
+
: ["node", "--enable-source-maps", "--import", "@backtickjs/node-plugin"];
|
|
15
|
+
|
|
16
|
+
const server = spawn(runtime, ["--watch", ...loader, "server/index.tsx"], {
|
|
17
|
+
stdio: "inherit",
|
|
18
|
+
env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? "development" },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// `bunx` where Bun runs this script, as it does where Node isn't installed.
|
|
22
|
+
const npx = process.versions.bun ? "bunx" : "npx";
|
|
23
|
+
const expo = spawn(npx, ["expo", "start", ...process.argv.slice(2)], {
|
|
24
|
+
stdio: "inherit",
|
|
25
|
+
shell: process.platform === "win32",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expo.on("exit", (code) => {
|
|
29
|
+
server.kill();
|
|
30
|
+
process.exit(code ?? 0);
|
|
31
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { cs } from "@backtickjs/core";
|
|
2
|
+
import { useEffect, useRef } from "@backtickjs/react";
|
|
3
|
+
import { Animated, Pressable } from "@backtickjs/react-native";
|
|
4
|
+
|
|
5
|
+
// A client component: it runs on the phone, so its animation does too. It
|
|
6
|
+
// waves when it appears, and again when you tap it.
|
|
7
|
+
export const HelloWave = cs`() => {
|
|
8
|
+
const rotation = $useRef(new $Animated.Value(0)).current;
|
|
9
|
+
const wave = () =>
|
|
10
|
+
$Animated
|
|
11
|
+
.loop(
|
|
12
|
+
$Animated.sequence([
|
|
13
|
+
$Animated.timing(rotation, {
|
|
14
|
+
toValue: 1,
|
|
15
|
+
duration: 150,
|
|
16
|
+
useNativeDriver: true,
|
|
17
|
+
}),
|
|
18
|
+
$Animated.timing(rotation, {
|
|
19
|
+
toValue: 0,
|
|
20
|
+
duration: 150,
|
|
21
|
+
useNativeDriver: true,
|
|
22
|
+
}),
|
|
23
|
+
]),
|
|
24
|
+
{ iterations: 4 },
|
|
25
|
+
)
|
|
26
|
+
.start();
|
|
27
|
+
|
|
28
|
+
$useEffect(wave, []);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<$Pressable onPress={wave}>
|
|
32
|
+
<$Animated.Text
|
|
33
|
+
style={{
|
|
34
|
+
fontSize: 28,
|
|
35
|
+
lineHeight: 32,
|
|
36
|
+
marginTop: -6,
|
|
37
|
+
transform: [
|
|
38
|
+
{
|
|
39
|
+
rotate: rotation.interpolate({
|
|
40
|
+
inputRange: [0, 1],
|
|
41
|
+
outputRange: ["0deg", "25deg"],
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
👋
|
|
48
|
+
</$Animated.Text>
|
|
49
|
+
</$Pressable>
|
|
50
|
+
);
|
|
51
|
+
}`;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { cs } from "@backtickjs/core";
|
|
2
|
+
import {
|
|
3
|
+
Linking,
|
|
4
|
+
ScrollView,
|
|
5
|
+
StatusBar,
|
|
6
|
+
StyleSheet,
|
|
7
|
+
Text,
|
|
8
|
+
View,
|
|
9
|
+
} from "@backtickjs/react-native";
|
|
10
|
+
import { HelloWave } from "./HelloWave.js";
|
|
11
|
+
|
|
12
|
+
// A server component: it runs on your server for every request, and hands the
|
|
13
|
+
// app its data and its client components.
|
|
14
|
+
export async function Home() {
|
|
15
|
+
// Read here, on your server, and sent to the phone as plain values.
|
|
16
|
+
const node = process.version;
|
|
17
|
+
const assembledAt = new Date().toLocaleTimeString();
|
|
18
|
+
|
|
19
|
+
// The whole layout is the screen's, safe areas included: iOS insets a root
|
|
20
|
+
// scroll view itself, and Android draws under its status bar, so the screen
|
|
21
|
+
// pads for it.
|
|
22
|
+
return cs`{
|
|
23
|
+
const styles = $StyleSheet.create({
|
|
24
|
+
screen: {
|
|
25
|
+
padding: 24,
|
|
26
|
+
paddingTop: ($StatusBar.currentHeight ?? 0) + 24,
|
|
27
|
+
gap: 24,
|
|
28
|
+
},
|
|
29
|
+
titleRow: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
30
|
+
title: { fontSize: 32, fontWeight: "bold", lineHeight: 32 },
|
|
31
|
+
step: { gap: 8 },
|
|
32
|
+
subtitle: { fontSize: 20, fontWeight: "bold" },
|
|
33
|
+
body: { fontSize: 16, lineHeight: 24 },
|
|
34
|
+
code: { fontWeight: "600" },
|
|
35
|
+
link: { fontSize: 16, lineHeight: 30, color: "#0a7ea4" },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<$ScrollView
|
|
40
|
+
contentInsetAdjustmentBehavior="automatic"
|
|
41
|
+
style={{ backgroundColor: "#fff" }}
|
|
42
|
+
contentContainerStyle={styles.screen}
|
|
43
|
+
>
|
|
44
|
+
<$View style={styles.titleRow}>
|
|
45
|
+
<$Text style={styles.title}>Welcome!</$Text>
|
|
46
|
+
<$HelloWave />
|
|
47
|
+
</$View>
|
|
48
|
+
|
|
49
|
+
<$View style={styles.step}>
|
|
50
|
+
<$Text style={styles.subtitle}>Step 1: Try it</$Text>
|
|
51
|
+
<$Text style={styles.body}>
|
|
52
|
+
Edit <$Text style={styles.code}>server/Home.tsx</$Text> and save.
|
|
53
|
+
This screen redraws without rebuilding the app.
|
|
54
|
+
</$Text>
|
|
55
|
+
</$View>
|
|
56
|
+
|
|
57
|
+
<$View style={styles.step}>
|
|
58
|
+
<$Text style={styles.subtitle}>Step 2: Server and client</$Text>
|
|
59
|
+
<$Text style={styles.body}>
|
|
60
|
+
Your server assembled this screen with Node {$node} at{" "}
|
|
61
|
+
{$assembledAt}. The waving hand is a client component: its animation
|
|
62
|
+
runs on your phone. Tap it.
|
|
63
|
+
</$Text>
|
|
64
|
+
</$View>
|
|
65
|
+
|
|
66
|
+
<$View style={styles.step}>
|
|
67
|
+
<$Text style={styles.subtitle}>Step 3: Get a fresh start</$Text>
|
|
68
|
+
<$Text style={styles.body}>
|
|
69
|
+
When you're ready, run{" "}
|
|
70
|
+
<$Text style={styles.code}>npm run reset-project</$Text> for a blank{" "}
|
|
71
|
+
<$Text style={styles.code}>server/Home.tsx</$Text>.
|
|
72
|
+
</$Text>
|
|
73
|
+
</$View>
|
|
74
|
+
|
|
75
|
+
<$Text
|
|
76
|
+
style={styles.link}
|
|
77
|
+
onPress={() => $Linking.openURL("https://backtickjs.com/docs")}
|
|
78
|
+
>
|
|
79
|
+
Read the docs →
|
|
80
|
+
</$Text>
|
|
81
|
+
</$ScrollView>
|
|
82
|
+
);
|
|
83
|
+
}`;
|
|
84
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { bundler } from "@backtickjs/bundler";
|
|
4
|
+
import { Home } from "./Home.js";
|
|
5
|
+
|
|
6
|
+
// Development is only what `npm start` runs, which sets NODE_ENV: any other
|
|
7
|
+
// run, a deploy included, is production. In development, this run of the
|
|
8
|
+
// server: `--watch` starts a new one whenever you save, and the app, seeing a
|
|
9
|
+
// new one at `/live`, draws its screen again.
|
|
10
|
+
const development = process.env.NODE_ENV === "development";
|
|
11
|
+
const run = crypto.randomUUID();
|
|
12
|
+
|
|
13
|
+
// The versions of React and React Native the app is built with: this
|
|
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
|
+
};
|
|
22
|
+
|
|
23
|
+
const server = createServer(async (request, response) => {
|
|
24
|
+
// `npm run web` serves the app from another origin.
|
|
25
|
+
response.setHeader("access-control-allow-origin", "*");
|
|
26
|
+
if (development && request.url === "/live") {
|
|
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();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
server.listen(3000, () => {
|
|
49
|
+
console.log("Backtick server on http://localhost:3000");
|
|
50
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Imports kept as written: a name a screen only splices, as `$View`, looks
|
|
4
|
+
// unused to TypeScript, which would otherwise drop its import.
|
|
5
|
+
"verbatimModuleSyntax": true,
|
|
6
|
+
"module": "esnext",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"target": "esnext",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"types": ["node"],
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"jsxImportSource": "@backtickjs/react"
|
|
15
|
+
},
|
|
16
|
+
"include": ["."]
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backtick-app-solid-js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node scripts/start.mjs",
|
|
8
|
+
"reset-project": "node scripts/reset-project.mjs",
|
|
9
|
+
"typecheck": "backtick-tsc --noEmit",
|
|
10
|
+
"format": "prettier --write --ignore-unknown .",
|
|
11
|
+
"format:check": "prettier --check --ignore-unknown ."
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@backtickjs/bundler": "^0.1.1",
|
|
15
|
+
"@backtickjs/core": "^0.1.1",
|
|
16
|
+
"@backtickjs/node-plugin": "^0.1.1",
|
|
17
|
+
"@backtickjs/solid-js": "^1.9.14",
|
|
18
|
+
"typescript": "^6.0.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@backtickjs/prettier-plugin": "^0.1.1",
|
|
22
|
+
"@backtickjs/tsc": "^0.1.1",
|
|
23
|
+
"@types/node": "^25.9.1",
|
|
24
|
+
"prettier": "^3.6.2"
|
|
25
|
+
},
|
|
26
|
+
"backtick": {
|
|
27
|
+
"plugins": [
|
|
28
|
+
"@backtickjs/solid-js/plugin"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Replaces the welcome page with a blank one, so you start fresh.
|
|
2
|
+
import { rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
writeFileSync(
|
|
5
|
+
"server/Home.tsx",
|
|
6
|
+
`import { cs } from "@backtickjs/core";
|
|
7
|
+
|
|
8
|
+
export async function Home() {
|
|
9
|
+
return cs\`(
|
|
10
|
+
<main style={{ padding: "24px", "font-family": "system-ui, sans-serif" }}>
|
|
11
|
+
<p>Edit server/Home.tsx to edit this page.</p>
|
|
12
|
+
</main>
|
|
13
|
+
)\`;
|
|
14
|
+
}
|
|
15
|
+
`,
|
|
16
|
+
);
|
|
17
|
+
rmSync("server/HelloWave.tsx", { force: true });
|
|
18
|
+
|
|
19
|
+
console.log(
|
|
20
|
+
"✅ Project reset. server/Home.tsx is now a blank page, and the welcome example is gone.",
|
|
21
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Starts your Backtick server in development: it restarts whenever a file
|
|
2
|
+
// under `server/` changes, and the page reloads with it. Any other way of
|
|
3
|
+
// running the server, a deploy included, is production.
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
// The runtime is the one package.json has a Backtick loader for: Bun's, or
|
|
8
|
+
// else Node's.
|
|
9
|
+
const { dependencies } = JSON.parse(readFileSync("package.json", "utf8"));
|
|
10
|
+
const [runtime, ...loader] =
|
|
11
|
+
"@backtickjs/bun-plugin" in dependencies
|
|
12
|
+
? ["bun", "--preload", "@backtickjs/bun-plugin"]
|
|
13
|
+
: ["node", "--enable-source-maps", "--import", "@backtickjs/node-plugin"];
|
|
14
|
+
|
|
15
|
+
const server = spawn(runtime, ["--watch", ...loader, "server/index.tsx"], {
|
|
16
|
+
stdio: "inherit",
|
|
17
|
+
env: { ...process.env, NODE_ENV: process.env.NODE_ENV ?? "development" },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
server.on("exit", (code) => process.exit(code ?? 0));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { cs } from "@backtickjs/core";
|
|
2
|
+
import { onMount } from "@backtickjs/solid-js";
|
|
3
|
+
|
|
4
|
+
// A client component: it runs in the browser, so its animation does too. It
|
|
5
|
+
// waves when it appears, and again when you click it.
|
|
6
|
+
export const HelloWave = cs`() => {
|
|
7
|
+
let hand!: HTMLSpanElement;
|
|
8
|
+
const wave = () => {
|
|
9
|
+
hand.animate(
|
|
10
|
+
[
|
|
11
|
+
{ transform: "rotate(0deg)" },
|
|
12
|
+
{ transform: "rotate(25deg)" },
|
|
13
|
+
{ transform: "rotate(0deg)" },
|
|
14
|
+
],
|
|
15
|
+
{ duration: 300, iterations: 4 },
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
$onMount(wave);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<span
|
|
23
|
+
ref={hand}
|
|
24
|
+
onClick={wave}
|
|
25
|
+
style={{
|
|
26
|
+
display: "inline-block",
|
|
27
|
+
cursor: "pointer",
|
|
28
|
+
"font-size": "28px",
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
31
|
+
👋
|
|
32
|
+
</span>
|
|
33
|
+
);
|
|
34
|
+
}`;
|