Skip to content

Commit 7eab915

Browse files
C3: add TanStack Start framework template
1 parent 7f2386e commit 7eab915

File tree

9 files changed

+162
-3
lines changed

9 files changed

+162
-3
lines changed

.changeset/busy-horses-kick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-cloudflare": minor
3+
---
4+
5+
add TanStack Start framework template

packages/create-cloudflare/e2e/helpers/framework-helpers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,15 +180,18 @@ export async function verifyPreviewScript(
180180
);
181181

182182
try {
183+
// TODO: check if this is necessary of if we can always just use localhost
184+
const host = verifyPreview.useLocalHost ? "localhost" : "127.0.0.1";
185+
183186
// Some frameworks take quite a long time to build the application (e.g. Docusaurus)
184187
// so wait up to 5 mins for the dev-server to be ready.
185188
await retry(
186189
{ times: 300, sleepMs: 5000 },
187-
async () => await fetch(`http://127.0.0.1:${port}${verifyPreview.route}`),
190+
async () => await fetch(`http://${host}:${port}${verifyPreview.route}`),
188191
);
189192

190193
// Make a request to the specified test route
191-
const res = await fetch(`http://127.0.0.1:${port}${verifyPreview.route}`);
194+
const res = await fetch(`http://${host}:${port}${verifyPreview.route}`);
192195
expect(await res.text()).toContain(verifyPreview.expectedText);
193196
} finally {
194197
// Kill the process gracefully so ports can be cleaned up

packages/create-cloudflare/e2e/helpers/run-c3.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export type RunnerConfig = {
4141
previewArgs?: string[];
4242
route: string;
4343
expectedText: string;
44+
useLocalHost?: boolean;
4445
};
4546
/**
4647
* Specifies whether to run the test script for the project and verify the exit code.

packages/create-cloudflare/e2e/tests/cli/cli.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ describe("Create Cloudflare CLI", () => {
584584
npm create cloudflare -- --framework next -- --ts
585585
pnpm create cloudflare --framework next -- --ts
586586
Allowed Values:
587-
analog, angular, astro, docusaurus, gatsby, hono, next, nuxt, qwik, react, react-router, solid, svelte, vue, waku
587+
analog, angular, astro, docusaurus, gatsby, hono, next, nuxt, qwik, react, react-router, solid, svelte, vue, waku, tanstack
588588
--platform=<value>
589589
Whether the application should be deployed to Pages or Workers. This is only applicable for Frameworks templates that support both Pages and Workers.
590590
Allowed Values:

packages/create-cloudflare/e2e/tests/frameworks/test-config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,21 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] {
582582
},
583583
nodeCompat: false,
584584
},
585+
{
586+
name: "tanstack",
587+
testCommitMessage: true,
588+
timeout: LONG_TIMEOUT,
589+
verifyDeploy: {
590+
route: "/",
591+
expectedText: "Learn TanStack",
592+
},
593+
verifyPreview: {
594+
route: "/",
595+
expectedText: "Learn TanStack",
596+
useLocalHost: true,
597+
},
598+
nodeCompat: true,
599+
},
585600
];
586601
}
587602

packages/create-cloudflare/src/frameworks/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"create-solid": "0.6.7",
1818
"create-vue": "3.18.0",
1919
"create-waku": "0.12.5-0.26.1-0",
20+
"@tanstack/create-start": "0.24.1",
2021
"gatsby": "5.15.0",
2122
"sv": "0.9.6",
2223
"nuxi": "3.28.0"

packages/create-cloudflare/src/templates.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import reactTemplate from "templates/react/c3";
4242
import scheduledTemplate from "templates/scheduled/c3";
4343
import solidTemplate from "templates/solid/c3";
4444
import svelteTemplate from "templates/svelte/c3";
45+
import tanstackTemplate from "templates/tanstack/c3";
4546
import vueTemplate from "templates/vue/c3";
4647
import wakuTemplate from "templates/waku/c3";
4748
import { isInsideGitRepo } from "./git";
@@ -206,6 +207,7 @@ export function getFrameworkMap({ experimental = false }): TemplateMap {
206207
svelte: svelteTemplate,
207208
vue: vueTemplate,
208209
waku: wakuTemplate,
210+
tanstack: tanstackTemplate,
209211
};
210212
}
211213
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import assert from "node:assert";
2+
import { logRaw } from "@cloudflare/cli";
3+
import { brandColor, dim } from "@cloudflare/cli/colors";
4+
import { runFrameworkGenerator } from "frameworks/index";
5+
import { transformFile } from "helpers/codemod";
6+
import { detectPackageManager } from "helpers/packageManagers";
7+
import { installPackages } from "helpers/packages";
8+
import * as recast from "recast";
9+
import type { TemplateConfig } from "../../src/templates";
10+
import type { types } from "recast";
11+
import type { C3Context } from "types";
12+
13+
const b = recast.types.builders;
14+
const t = recast.types.namedTypes;
15+
const { npm } = detectPackageManager();
16+
17+
const generate = async (ctx: C3Context) => {
18+
await runFrameworkGenerator(ctx, [
19+
ctx.project.name,
20+
"--framework",
21+
"react",
22+
// to prevent asking about git twice, just let c3 do it
23+
"--no-git",
24+
]);
25+
26+
logRaw(""); // newline
27+
};
28+
29+
const configure = async () => {
30+
await installPackages(["@cloudflare/vite-plugin"], {
31+
dev: true,
32+
startText: "Installing the Cloudflare Vite plugin",
33+
doneText: `${brandColor(`installed`)} ${dim("@cloudflare/vite-plugin")}`,
34+
});
35+
36+
updateViteConfig();
37+
};
38+
39+
const updateViteConfig = () => {
40+
const filePath = "vite.config.ts";
41+
42+
transformFile(filePath, {
43+
visitProgram(n) {
44+
// Add an import of the @cloudflare/vite-plugin
45+
// ```
46+
// import {cloudflare} from "@cloudflare/vite-plugin";
47+
// ```
48+
const lastImportIndex = n.node.body.findLastIndex(
49+
(statement) => statement.type === "ImportDeclaration",
50+
);
51+
const lastImport = n.get("body", lastImportIndex);
52+
const importAst = b.importDeclaration(
53+
[b.importSpecifier(b.identifier("cloudflare"))],
54+
b.stringLiteral("@cloudflare/vite-plugin"),
55+
);
56+
lastImport.insertAfter(importAst);
57+
58+
return this.traverse(n);
59+
},
60+
visitCallExpression: function (n) {
61+
// Add the imported plugin to the config
62+
// ```
63+
// defineConfig({
64+
// plugins: [react(), cloudflare()],
65+
// });
66+
const callee = n.node.callee as types.namedTypes.Identifier;
67+
if (callee.name !== "defineConfig") {
68+
return this.traverse(n);
69+
}
70+
71+
const config = n.node.arguments[0];
72+
assert(t.ObjectExpression.check(config));
73+
const pluginsProp = config.properties.find((prop) => isPluginsProp(prop));
74+
assert(pluginsProp && t.ArrayExpression.check(pluginsProp.value));
75+
pluginsProp.value.elements.push(
76+
b.callExpression(b.identifier("cloudflare"), [
77+
b.objectExpression([
78+
b.objectProperty(
79+
b.identifier("viteEnvironment"),
80+
b.objectExpression([
81+
b.objectProperty(b.identifier("name"), b.literal("ssr")),
82+
]),
83+
),
84+
]),
85+
]),
86+
);
87+
88+
return false;
89+
},
90+
});
91+
};
92+
93+
function isPluginsProp(
94+
prop: unknown,
95+
): prop is types.namedTypes.ObjectProperty | types.namedTypes.Property {
96+
return (
97+
(t.Property.check(prop) || t.ObjectProperty.check(prop)) &&
98+
t.Identifier.check(prop.key) &&
99+
prop.key.name === "plugins"
100+
);
101+
}
102+
103+
const config: TemplateConfig = {
104+
configVersion: 1,
105+
id: "tanstack",
106+
platform: "workers",
107+
frameworkCli: "@tanstack/create-start",
108+
displayName: "TanStack Start",
109+
generate,
110+
configure,
111+
copyFiles: {
112+
path: "./templates",
113+
},
114+
transformPackageJson: async () => ({
115+
scripts: {
116+
deploy: `${npm} run build && wrangler deploy`,
117+
preview: `${npm} run build && vite preview`,
118+
"cf-typegen": `wrangler types`,
119+
},
120+
}),
121+
devScript: "dev",
122+
deployScript: "deploy",
123+
previewScript: "preview",
124+
};
125+
export default config;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"$schema": "node_modules/wrangler/config-schema.json",
3+
"name": "<TBD>",
4+
"compatibility_date": "<TBD>",
5+
"compatibility_flags": ["nodejs_compat"],
6+
"main": "@tanstack/react-start/server-entry"
7+
}

0 commit comments

Comments
 (0)