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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +150 -0
  5. package/package.json +56 -3
  6. package/templates/react/.prettierignore +8 -0
  7. package/templates/react/.prettierrc +3 -0
  8. package/templates/react/.vscode/extensions.json +6 -0
  9. package/templates/react/gitignore +16 -0
  10. package/templates/react/package.json +31 -0
  11. package/templates/react/scripts/reset-project.mjs +21 -0
  12. package/templates/react/scripts/start.mjs +20 -0
  13. package/templates/react/server/HelloWave.tsx +30 -0
  14. package/templates/react/server/Home.tsx +65 -0
  15. package/templates/react/server/html.ts +62 -0
  16. package/templates/react/server/index.tsx +33 -0
  17. package/templates/react/tsconfig.json +18 -0
  18. package/templates/react-native/.prettierignore +16 -0
  19. package/templates/react-native/.prettierrc +3 -0
  20. package/templates/react-native/.vscode/extensions.json +6 -0
  21. package/templates/react-native/App.tsx +117 -0
  22. package/templates/react-native/app.json +9 -0
  23. package/templates/react-native/gitignore +41 -0
  24. package/templates/react-native/index.ts +8 -0
  25. package/templates/react-native/package.json +44 -0
  26. package/templates/react-native/scripts/reset-project.mjs +22 -0
  27. package/templates/react-native/scripts/start.mjs +31 -0
  28. package/templates/react-native/server/HelloWave.tsx +51 -0
  29. package/templates/react-native/server/Home.tsx +84 -0
  30. package/templates/react-native/server/index.tsx +50 -0
  31. package/templates/react-native/server/tsconfig.json +17 -0
  32. package/templates/react-native/tsconfig.json +7 -0
  33. package/templates/solid-js/.prettierignore +8 -0
  34. package/templates/solid-js/.prettierrc +3 -0
  35. package/templates/solid-js/.vscode/extensions.json +6 -0
  36. package/templates/solid-js/gitignore +16 -0
  37. package/templates/solid-js/package.json +31 -0
  38. package/templates/solid-js/scripts/reset-project.mjs +21 -0
  39. package/templates/solid-js/scripts/start.mjs +20 -0
  40. package/templates/solid-js/server/HelloWave.tsx +34 -0
  41. package/templates/solid-js/server/Home.tsx +65 -0
  42. package/templates/solid-js/server/html.ts +62 -0
  43. package/templates/solid-js/server/index.tsx +33 -0
  44. package/templates/solid-js/tsconfig.json +18 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "plugins": ["@backtickjs/prettier-plugin"]
3
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "recommendations": [
3
+ "backtickjs.backtick-vscode",
4
+ "esbenp.prettier-vscode"
5
+ ]
6
+ }
@@ -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,9 @@
1
+ {
2
+ "expo": {
3
+ "name": "backtick-app",
4
+ "slug": "backtick-app",
5
+ "version": "1.0.0",
6
+ "orientation": "portrait",
7
+ "userInterfaceStyle": "light"
8
+ }
9
+ }
@@ -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,7 @@
1
+ {
2
+ "extends": "expo/tsconfig.base",
3
+ "compilerOptions": {
4
+ "strict": true
5
+ },
6
+ "exclude": ["server"]
7
+ }
@@ -0,0 +1,8 @@
1
+ # Written by the package manager.
2
+ pnpm-lock.yaml
3
+ package-lock.json
4
+ yarn.lock
5
+ bun.lock
6
+
7
+ # Written the way VS Code writes them, one entry per line.
8
+ .vscode/extensions.json
@@ -0,0 +1,3 @@
1
+ {
2
+ "plugins": ["@backtickjs/prettier-plugin"]
3
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "recommendations": [
3
+ "backtickjs.backtick-vscode",
4
+ "esbenp.prettier-vscode"
5
+ ]
6
+ }
@@ -0,0 +1,16 @@
1
+ # dependencies
2
+ node_modules/
3
+
4
+ # debug
5
+ npm-debug.*
6
+ yarn-debug.*
7
+ yarn-error.*
8
+
9
+ # macOS
10
+ .DS_Store
11
+
12
+ # local env files
13
+ .env*.local
14
+
15
+ # typescript
16
+ *.tsbuildinfo
@@ -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
+ }`;