diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 40171ef6e883ed..e416cff84e0279 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -310,7 +310,7 @@ pub const CreateCommand = struct { var analyzer = Analyzer{ .ctx = ctx, .example_tag = example_tag, - .entry_point = destination, + .entry_point = template, .progress = &progress, .node = node, }; diff --git a/src/create/SourceFileProjectGenerator.zig b/src/create/SourceFileProjectGenerator.zig index 116cd453081090..71a2d1b80e504d 100644 --- a/src/create/SourceFileProjectGenerator.zig +++ b/src/create/SourceFileProjectGenerator.zig @@ -5,7 +5,8 @@ pub fn generate(_: Command.Context, example_tag: Example.Tag, entry_point: strin needs_to_inject_tailwind = hasAnyTailwindClassesInSourceFiles(result.bundle_v2, result.reachable_files); } - const needs_to_inject_shadcn_ui = hasAnyShadcnImports(result.bundle_v2, result.reachable_files); + const shadcn = try getShadcnComponents(result.bundle_v2, result.reachable_files); + const needs_to_inject_shadcn_ui = shadcn.keys().len > 0; if (needs_to_inject_tailwind) { try result.dependencies.insert("tailwindcss"); @@ -15,13 +16,12 @@ pub fn generate(_: Command.Context, example_tag: Example.Tag, entry_point: strin if (needs_to_inject_shadcn_ui) { // https://ui.shadcn.com/docs/installation/manual // This will probably be tricky to keep updated. + // but hopefully the dependency scanning will just handle it for us. try result.dependencies.insert("tailwindcss-animate"); try result.dependencies.insert("class-variance-authority"); try result.dependencies.insert("clsx"); try result.dependencies.insert("tailwind-merge"); try result.dependencies.insert("lucide-react"); - - // TODO: insert components.json and other boilerplate for `shadcn/ui add` to work. } const uses_tailwind = has_tailwind_in_dependencies or needs_to_inject_tailwind; @@ -29,9 +29,17 @@ pub fn generate(_: Command.Context, example_tag: Example.Tag, entry_point: strin try result.dependencies.insert("react-dom"); } - if (uses_tailwind and result.dependencies.contains("react") and example_tag == .jslike_file) { - try ReactTailwindSpa.generate(entry_point, result); - } + const template: Template = brk: { + if (needs_to_inject_shadcn_ui and example_tag == .jslike_file) { + break :brk .{ .ReactShadcnSpa = .{ .components = shadcn } }; + } else if (uses_tailwind and example_tag == .jslike_file) { + break :brk .ReactTailwindSpa; + } else { + Global.exit(0); + } + }; + + try generateFiles(default_allocator, entry_point, result, template); Global.exit(0); } @@ -43,6 +51,11 @@ fn createFile(filename: []const u8, contents: []const u8) bun.JSC.Maybe(bool) { return .{ .result = false }; } } + + if (std.fs.path.dirname(filename)) |dirname| { + bun.makePath(std.fs.cwd(), dirname) catch {}; + } + const fd = switch (bun.sys.openatA(bun.toFD(std.fs.cwd()), filename, bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o644)) { .result => |fd| fd, .err => |err| return .{ .err = err }, @@ -87,195 +100,361 @@ fn replaceAllOccurrencesOfString(allocator: std.mem.Allocator, input: []const u8 return result.items; } -fn stringWithReplacements(input: []const u8, basename: []const u8, allocator: std.mem.Allocator) ![]u8 { - return try replaceAllOccurrencesOfString(allocator, input, "REPLACE_ME_WITH_YOUR_APP_FILE_NAME", basename); +fn stringWithReplacements(input: []const u8, basename: []const u8, relative_name: []const u8, allocator: std.mem.Allocator) ![]u8 { + if (strings.contains(input, "REPLACE_ME_WITH_YOUR_APP_BASE_NAME")) { + return try replaceAllOccurrencesOfString(allocator, input, "REPLACE_ME_WITH_YOUR_APP_BASE_NAME", basename); + } + + return try replaceAllOccurrencesOfString(allocator, input, "REPLACE_ME_WITH_YOUR_APP_FILE_NAME", relative_name); } +const ReactShadcnSpa = struct { + pub const files = .{ + .@"lib/utils.ts" = @embedFile("projects/react-shadcn-spa/src/lib/utils.ts"), + .@"src/index.css" = @embedFile("projects/react-shadcn-spa/src/index.css"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts" = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx" = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css" = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html" = @embedFile("projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html"), + .@"styles/globals.css" = @embedFile("projects/react-shadcn-spa/styles/globals.css"), + }; + + pub const bunfig = @embedFile("projects/react-shadcn-spa/bunfig.toml"); + pub const package_json = @embedFile("projects/react-shadcn-spa/package.json"); + pub const tailwind_config = @embedFile("projects/react-shadcn-spa/tailwind.config.js"); + pub const tsconfig = @embedFile("projects/react-shadcn-spa/tsconfig.json"); + pub const components_json = @embedFile("projects/react-shadcn-spa/components.json"); +}; + const ReactTailwindSpa = struct { pub const files = .{ - .@"bunfig.toml" = @embedFile("projects/react-tailwind-spa/bunfig.toml"), - .@"package.json" = @embedFile("projects/react-tailwind-spa/package.json"), - .@"build.ts" = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts"), - .css = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css"), - .html = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html"), - .@"init.tsx" = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts" = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css" = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html" = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html"), + .@"REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx" = @embedFile("projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx"), }; - pub fn generate(entry_point: string, result: *BundleV2.DependenciesScanner.Result) !void { - var basename = std.fs.path.basename(entry_point); - const extension = std.fs.path.extension(basename); - if (extension.len > 0) { - basename = basename[0 .. basename.len - extension.len]; - } + pub const bunfig = @embedFile("projects/react-tailwind-spa/bunfig.toml"); + pub const package_json = @embedFile("projects/react-tailwind-spa/package.json"); + pub const tailwind_config = ""; + pub const tsconfig = ""; + pub const components_json = ""; +}; - var is_new = false; +const Template = union(Tag) { + ReactTailwindSpa: void, + ReactShadcnSpa: struct { + components: bun.StringSet, + }, - if (!bun.sys.exists("package.json")) { - switch (createFile("package.json", files.@"package.json")) { - .result => |new| { - if (new) { - is_new = true; - Output.prettyln(" package.json created\n", .{}); + pub const Tag = enum { + ReactTailwindSpa, + ReactShadcnSpa, + }; +}; +const SourceFileProjectGenerator = @This(); +fn generateFiles(allocator: std.mem.Allocator, entry_point: string, result: *BundleV2.DependenciesScanner.Result, template: Template) !void { + var is_new = false; + var basename = std.fs.path.basename(entry_point); + const extension = std.fs.path.extension(basename); + if (extension.len > 0) { + basename = basename[0 .. basename.len - extension.len]; + } + + var normalized_buf: bun.PathBuffer = undefined; + var normalized_name: []const u8 = if (std.fs.path.isAbsolute(entry_point)) + bun.path.relativeNormalizedBuf(&normalized_buf, bun.fs.FileSystem.instance.top_level_dir, entry_point, .posix, true) + else + bun.path.normalizeBuf(entry_point, &normalized_buf, .posix); + + if (extension.len > 0) { + normalized_name = normalized_name[0 .. normalized_name.len - extension.len]; + } + + switch (@as(Template.Tag, template)) { + inline else => |active| { + const current = @field(SourceFileProjectGenerator, @tagName(active)); + + if (current.tailwind_config.len > 0) { + if (!bun.sys.exists("tailwind.config.js")) { + switch (createFile("tailwind.config.js", current.tailwind_config)) { + .result => |new| { + if (new) { + is_new = true; + Output.prettyln(" tailwind.config.js created\n", .{}); + } + }, + .err => |err| { + Output.err(err, "failed to create tailwind.config.js", .{}); + Global.crash(); + }, } - }, - .err => |err| { - Output.err(err, "failed to create package.json", .{}); - Global.crash(); - }, + } } - } - if (!bun.sys.exists("bunfig.toml")) { - switch (createFile("bunfig.toml", files.@"bunfig.toml")) { - .result => |new| { - if (new) { - is_new = true; - Output.prettyln(" bunfig.toml created\n", .{}); + if (current.components_json.len > 0) { + if (!bun.sys.exists("components.json")) { + switch (createFile("components.json", current.components_json)) { + .result => |new| { + if (new) { + is_new = true; + Output.prettyln(" components.json created\n", .{}); + } + }, + .err => |err| { + Output.err(err, "failed to create components.json", .{}); + Global.crash(); + }, } - }, - .err => |err| { - Output.err(err, "failed to create bunfig.toml", .{}); - Global.crash(); - }, + } } - } - // We leak all these, but it's pretty much fine. - const css_filename = try stringWithReplacements("REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css", basename, default_allocator); - const html_filename = try stringWithReplacements("REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html", basename, default_allocator); - const init_filename = try stringWithReplacements("REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx", basename, default_allocator); - const build_filename = try stringWithReplacements("REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts", basename, default_allocator); - const pairs = [_][2][]const u8{ - .{ try stringWithReplacements(files.css, basename, default_allocator), css_filename }, - .{ try stringWithReplacements(files.html, basename, default_allocator), html_filename }, - .{ try stringWithReplacements(files.@"init.tsx", basename, default_allocator), init_filename }, - .{ try stringWithReplacements(files.@"build.ts", basename, default_allocator), build_filename }, - }; - - for (pairs) |pair| { - switch (createFile(pair[1], pair[0])) { - .result => |new| { - if (new) { - is_new = true; - Output.prettyln(" {s} generated\n", .{pair[1]}); - } - }, - .err => |err| { - Output.err(err, "failed to create file: {s}", .{pair[1]}); - Global.crash(); - }, + if (!bun.sys.exists("package.json")) { + switch (createFile("package.json", current.package_json)) { + .result => |new| { + if (new) { + is_new = true; + Output.prettyln(" package.json created\n", .{}); + } + }, + .err => |err| { + Output.err(err, "failed to create package.json", .{}); + Global.crash(); + }, + } } - } - var argv = std.ArrayList([]const u8).init(default_allocator); - try argv.append(try bun.selfExePath()); - try argv.append("--only-missing"); - try argv.append("install"); - try argv.appendSlice(result.dependencies.keys()); - - const process = bun.spawnSync(&.{ - .argv = argv.items, - .envp = null, - .cwd = bun.fs.FileSystem.instance.top_level_dir, - .stderr = .inherit, - .stdout = .inherit, - .stdin = .inherit, - - .windows = if (Environment.isWindows) .{ - .loop = bun.JSC.EventLoopHandle.init(bun.JSC.MiniEventLoop.initGlobal(null)), - }, - }) catch |err| { + + if (!bun.sys.exists("tsconfig.json")) { + switch (createFile("tsconfig.json", current.tsconfig)) { + .result => |new| { + if (new) { + is_new = true; + Output.prettyln(" tsconfig.json created\n", .{}); + } + }, + .err => |err| { + Output.err(err, "failed to create tsconfig.json", .{}); + Global.crash(); + }, + } + } + + if (!bun.sys.exists("bunfig.toml")) { + switch (createFile("bunfig.toml", current.bunfig)) { + .result => |new| { + if (new) { + is_new = true; + Output.prettyln(" bunfig.toml created\n", .{}); + } + }, + .err => |err| { + Output.err(err, "failed to create bunfig.toml", .{}); + Global.crash(); + }, + } + } + + inline for (comptime std.meta.fieldNames(@TypeOf(current.files))) |name| { + const file_name = try stringWithReplacements(name, basename, normalized_name, allocator); + switch (createFile(file_name, try stringWithReplacements(@field(current.files, name), basename, normalized_name, default_allocator))) { + .result => |new| { + if (new) { + is_new = true; + Output.prettyln(" {s} created\n", .{file_name}); + } + }, + .err => |err| { + Output.err(err, "failed to create {s}", .{file_name}); + Global.crash(); + }, + } + } + }, + } + + // We leak all these, but it's pretty much fine. + + var argv = std.ArrayList([]const u8).init(default_allocator); + try argv.append(try bun.selfExePath()); + try argv.append("--only-missing"); + try argv.append("install"); + try argv.appendSlice(result.dependencies.keys()); + + const process = bun.spawnSync(&.{ + .argv = argv.items, + .envp = null, + .cwd = bun.fs.FileSystem.instance.top_level_dir, + .stderr = .inherit, + .stdout = .inherit, + .stdin = .inherit, + + .windows = if (Environment.isWindows) .{ + .loop = bun.JSC.EventLoopHandle.init(bun.JSC.MiniEventLoop.initGlobal(null)), + }, + }) catch |err| { + Output.err(err, "failed to install dependencies", .{}); + Global.crash(); + }; + + switch (process) { + .err => |err| { Output.err(err, "failed to install dependencies", .{}); Global.crash(); - }; + }, + .result => |spawn_result| { + if (!spawn_result.status.isOK()) { + if (spawn_result.status.signalCode()) |signal| { + if (signal.toExitCode()) |exit_code| { + Global.exit(exit_code); + } + } + + if (spawn_result.status == .exited) { + Global.exit(spawn_result.status.exited.code); + } - switch (process) { - .err => |err| { - Output.err(err, "failed to install dependencies", .{}); Global.crash(); - }, - .result => |spawn_result| { - if (!spawn_result.status.isOK()) { - if (spawn_result.status.signalCode()) |signal| { - if (signal.toExitCode()) |exit_code| { - Global.exit(exit_code); - } + } + }, + } + + if (is_new) { + switch (template) { + .ReactShadcnSpa => |*shadcn| { + if (shadcn.components.keys().len > 0) { + var shadcn_argv = try std.ArrayList([]const u8).initCapacity(default_allocator, 10); + try shadcn_argv.append(try bun.selfExePath()); + try shadcn_argv.append("x"); + try shadcn_argv.append("shadcn"); + try shadcn_argv.append("add"); + if (strings.contains(normalized_name, "/src")) { + try shadcn_argv.append("--src-dir"); } + try shadcn_argv.append("-y"); + try shadcn_argv.appendSlice(shadcn.components.keys()); + // Now we need to run shadcn to add the components to the project + const shadcn_process = bun.spawnSync(&.{ + .argv = shadcn_argv.items, + .envp = null, + .cwd = bun.fs.FileSystem.instance.top_level_dir, + .stderr = .inherit, + .stdout = .inherit, + .stdin = .inherit, + }) catch |err| { + Output.err(err, "failed to add shadcn components", .{}); + Global.crash(); + }; + + switch (shadcn_process) { + .err => |err| { + Output.err(err, "failed to add shadcn components", .{}); + Global.crash(); + }, + .result => |spawn_result| { + if (!spawn_result.status.isOK()) { + if (spawn_result.status.signalCode()) |signal| { + if (signal.toExitCode()) |exit_code| { + Global.exit(exit_code); + } + } + + if (spawn_result.status == .exited) { + Global.exit(spawn_result.status.exited.code); + } - if (spawn_result.status == .exited) { - Global.exit(spawn_result.status.exited.code); + Global.crash(); + } + }, } - Global.crash(); + Output.prettyln( + \\ Shadcn SPA created successfully! + \\ + \\To start your app, run: + \\ + \\ bun dev + \\ + \\To open your app in the browser: + \\ + \\ open http://localhost:3000/{s} + \\ + \\To build your app: + \\ + \\ bun run build + \\ + , .{ + basename, + }); } }, + .ReactTailwindSpa => { + Output.prettyln( + \\ React Tailwind SPA created successfully! + \\ + \\To start your app, run: + \\ + \\ bun dev + \\ + \\To open your app in the browser: + \\ + \\ open http://localhost:3000/{s} + \\ + \\To build your app: + \\ + \\ bun run build + \\ + , .{ + basename, + }); + }, } - if (is_new) { - Output.prettyln( - \\ React Tailwind SPA created successfully! - \\ - \\To start your app, run: - \\ - \\ bun dev - \\ - \\To open your app in the browser: - \\ - \\ open http://localhost:3000/{s} - \\ - \\To build your app: - \\ - \\ bun run build - \\ - , .{ - basename, - }); - Output.flush(); - } + Output.flush(); + } - const start = bun.spawnSync(&.{ - .argv = &.{ - try bun.selfExePath(), - "dev", - }, - .envp = null, - .cwd = bun.fs.FileSystem.instance.top_level_dir, - .stderr = .inherit, - .stdout = .inherit, - .stdin = .inherit, - - .windows = if (Environment.isWindows) .{ - .loop = bun.JSC.EventLoopHandle.init(bun.JSC.MiniEventLoop.initGlobal(null)), - }, - }) catch |err| { + const start = bun.spawnSync(&.{ + .argv = &.{ + try bun.selfExePath(), + "dev", + }, + .envp = null, + .cwd = bun.fs.FileSystem.instance.top_level_dir, + .stderr = .inherit, + .stdout = .inherit, + .stdin = .inherit, + + .windows = if (Environment.isWindows) .{ + .loop = bun.JSC.EventLoopHandle.init(bun.JSC.MiniEventLoop.initGlobal(null)), + }, + }) catch |err| { + Output.err(err, "failed to start app", .{}); + Global.crash(); + }; + + switch (start) { + .err => |err| { Output.err(err, "failed to start app", .{}); Global.crash(); - }; - - switch (start) { - .err => |err| { - Output.err(err, "failed to start app", .{}); - Global.crash(); - }, - .result => |spawn_result| { - if (!spawn_result.status.isOK()) { - if (spawn_result.status.signalCode()) |signal| { - if (signal.toExitCode()) |exit_code| { - Global.exit(exit_code); - } - } - - if (spawn_result.status == .exited) { - Global.exit(spawn_result.status.exited.code); + }, + .result => |spawn_result| { + if (!spawn_result.status.isOK()) { + if (spawn_result.status.signalCode()) |signal| { + if (signal.toExitCode()) |exit_code| { + Global.exit(exit_code); } + } - Global.crash(); + if (spawn_result.status == .exited) { + Global.exit(spawn_result.status.exited.code); } - }, - } - Global.exit(0); + Global.crash(); + } + }, } -}; + + Global.exit(0); +} fn hasAnyTailwindClassesInSourceFiles(bundler: *BundleV2, reachable_files: []const js_ast.Index) bool { const input_files = bundler.graph.input_files.slice(); @@ -346,17 +525,18 @@ fn hasAnyTailwindClassesInSourceFiles(bundler: *BundleV2, reachable_files: []con return false; } -fn hasAnyShadcnImports(bundler: *BundleV2, reachable_files: []const js_ast.Index) bool { +fn getShadcnComponents(bundler: *BundleV2, reachable_files: []const js_ast.Index) !bun.StringSet { const input_files = bundler.graph.input_files.slice(); const loaders = input_files.items(.loader); const all = bundler.graph.ast.items(.import_records); + var icons = bun.StringSet.init(default_allocator); for (reachable_files) |file| { switch (loaders[file.get()]) { .tsx, .jsx => { const import_records = all[file.get()]; for (import_records.slice()) |*import_record| { - if (strings.contains(import_record.path.text, "@/components/ui/")) { - return true; + if (strings.hasPrefixComptime(import_record.path.text, "@/components/ui/")) { + try icons.insert(import_record.path.text["@/components/ui/".len..]); } } }, @@ -364,7 +544,7 @@ fn hasAnyShadcnImports(bundler: *BundleV2, reachable_files: []const js_ast.Index } } - return false; + return icons; } const bun = @import("root").bun; const string = bun.string; diff --git a/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts new file mode 100644 index 00000000000000..2f246d73d02b57 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts @@ -0,0 +1,44 @@ +import { build } from "bun"; +import plugin from "bun-plugin-tailwind"; +import { existsSync } from "fs"; +import { rm } from "fs/promises"; +import path from "path"; + +const outdir = path.join(import.meta.dir, process.argv.length > 2 ? process.argv[2] : "dist"); + +if (existsSync(outdir)) { + console.log(`Removing existing dist directory ${outdir}`); + await rm(outdir, { recursive: true, force: true }); +} + +const start = performance.now(); + +// Scan for all HTML files in the project +const entrypoints = [...new Bun.Glob("*.html").scanSync(import.meta.dir)]; + +// Build all the HTML files +const result = await build({ + entrypoints, + outdir, + plugins: [plugin], + minify: true, + target: "browser", + sourcemap: "linked", + define: { + "process.env.NODE_ENV": JSON.stringify("production"), + }, +}); + +// Print the results +const end = performance.now(); +console.log(`[${(end - start).toFixed(2)}ms] Bundled ${result.outputs.length} files to ${outdir}`); + +const number = new Intl.NumberFormat({ + style: "decimal", + maximumFractionDigits: 2, + unit: "B", +}); + +console.table( + result.outputs.map(o => ({ name: path.relative(process.cwd(), o.name), size: number.format(o.size / 1024) + " KB" })), +); diff --git a/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx new file mode 100644 index 00000000000000..a2f9a3d79e8294 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx @@ -0,0 +1,27 @@ +import { createRoot } from "react-dom/client"; +import * as App from "./REPLACE_ME_WITH_YOUR_APP_BASE_NAME"; +import React from "react"; + +const Component = App.default || App["REPLACE_ME_WITH_YOUR_APP_BASE_NAME"]; + +function mount(root: HTMLElement) { + createRoot(root).render( + + + , + ); +} + +let root = document.getElementById("root"); +if (root) { + mount(root); +} else { + document.addEventListener("DOMContentLoaded", () => { + root = document.getElementById("root"); + if (root) { + mount(root); + } else { + throw new Error("No root element found"); + } + }); +} diff --git a/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css new file mode 100644 index 00000000000000..9d3c0b0095d526 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.css @@ -0,0 +1 @@ +@import "@/src/index.css"; diff --git a/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html new file mode 100644 index 00000000000000..547b02d9d73b03 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html @@ -0,0 +1,14 @@ + + + + + + REPLACE_ME_WITH_YOUR_APP_BASE_NAME | Powered by Bun + + + + +
+ + + diff --git a/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_NAME.html b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_NAME.html new file mode 100644 index 00000000000000..6fe77fb57a79be --- /dev/null +++ b/src/create/projects/react-shadcn-spa/REPLACE_ME_WITH_YOUR_APP_NAME.html @@ -0,0 +1,14 @@ + + + + + + REPLACE_ME_WITH_YOUR_APP_FILE_NAME | Powered by Bun + + + + +
+ + + diff --git a/src/create/projects/react-shadcn-spa/bunfig.toml b/src/create/projects/react-shadcn-spa/bunfig.toml new file mode 100644 index 00000000000000..55db1c435b65a0 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/bunfig.toml @@ -0,0 +1,2 @@ +[serve.static] +plugins = ["bun-plugin-tailwind"] \ No newline at end of file diff --git a/src/create/projects/react-shadcn-spa/components.json b/src/create/projects/react-shadcn-spa/components.json new file mode 100644 index 00000000000000..0bdd0a5a014ba7 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/src/create/projects/react-shadcn-spa/package.json b/src/create/projects/react-shadcn-spa/package.json new file mode 100644 index 00000000000000..23b1d50f6fbc11 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/package.json @@ -0,0 +1,9 @@ +{ + "name": "react-tailwind-spa", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "bun './src/**/*.html'", + "build": "bun src/*.build.ts" + } +} diff --git a/src/create/projects/react-shadcn-spa/src/index.css b/src/create/projects/react-shadcn-spa/src/index.css new file mode 100644 index 00000000000000..0e7101670a8f2d --- /dev/null +++ b/src/create/projects/react-shadcn-spa/src/index.css @@ -0,0 +1,2 @@ +@import "tailwindcss"; +@import "../styles/globals.css"; diff --git a/src/create/projects/react-shadcn-spa/src/lib/utils.ts b/src/create/projects/react-shadcn-spa/src/lib/utils.ts new file mode 100644 index 00000000000000..a5ef193506d07d --- /dev/null +++ b/src/create/projects/react-shadcn-spa/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/create/projects/react-shadcn-spa/styles/globals.css b/src/create/projects/react-shadcn-spa/styles/globals.css new file mode 100644 index 00000000000000..a1be0c343586ee --- /dev/null +++ b/src/create/projects/react-shadcn-spa/styles/globals.css @@ -0,0 +1,59 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 47.4% 11.2%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --card: 0 0% 100%; + --card-foreground: 222.2 47.4% 11.2%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 100% 50%; + --destructive-foreground: 210 40% 98%; + --ring: 215 20.2% 65.1%; + --radius: 0.5rem; + } + + .dark { + --background: 224 71% 4%; + --foreground: 213 31% 91%; + --muted: 223 47% 11%; + --muted-foreground: 215.4 16.3% 56.9%; + --accent: 216 34% 17%; + --accent-foreground: 210 40% 98%; + --popover: 224 71% 4%; + --popover-foreground: 215 20.2% 65.1%; + --border: 216 34% 17%; + --input: 216 34% 17%; + --card: 224 71% 4%; + --card-foreground: 213 31% 91%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 1.2%; + --secondary: 222.2 47.4% 11.2%; + --secondary-foreground: 210 40% 98%; + --destructive: 0 63% 31%; + --destructive-foreground: 210 40% 98%; + --ring: 216 34% 17%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply font-sans antialiased bg-background text-foreground; + } +} diff --git a/src/create/projects/react-shadcn-spa/tailwind.config.js b/src/create/projects/react-shadcn-spa/tailwind.config.js new file mode 100644 index 00000000000000..3dca693bf6a986 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/tailwind.config.js @@ -0,0 +1,49 @@ +module.exports = { + darkMode: ["class"], + content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"], + theme: { + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: `var(--radius)`, + md: `calc(var(--radius) - 2px)`, + sm: "calc(var(--radius) - 4px)", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; diff --git a/src/create/projects/react-shadcn-spa/tsconfig.json b/src/create/projects/react-shadcn-spa/tsconfig.json new file mode 100644 index 00000000000000..aeae4337eb63a9 --- /dev/null +++ b/src/create/projects/react-shadcn-spa/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "module": "preserve", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts", "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx", "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts", "REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx"] +} diff --git a/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts b/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts index 8dfc0e8c2d84df..2f246d73d02b57 100644 --- a/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts +++ b/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts @@ -1,9 +1,8 @@ import { build } from "bun"; -import { path } from "bun"; -import path from "path"; -import { rm } from "fs/promises"; import plugin from "bun-plugin-tailwind"; import { existsSync } from "fs"; +import { rm } from "fs/promises"; +import path from "path"; const outdir = path.join(import.meta.dir, process.argv.length > 2 ? process.argv[2] : "dist"); diff --git a/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx b/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx index 627fa213c3a610..a2f9a3d79e8294 100644 --- a/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx +++ b/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.client.tsx @@ -1,8 +1,8 @@ import { createRoot } from "react-dom/client"; -import * as App from "./REPLACE_ME_WITH_YOUR_APP_FILE_NAME"; +import * as App from "./REPLACE_ME_WITH_YOUR_APP_BASE_NAME"; import React from "react"; -const Component = App.default || App["REPLACE_ME_WITH_YOUR_APP_FILE_NAME"]; +const Component = App.default || App["REPLACE_ME_WITH_YOUR_APP_BASE_NAME"]; function mount(root: HTMLElement) { createRoot(root).render( diff --git a/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html b/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html index 9fc08d0e9eb17b..547b02d9d73b03 100644 --- a/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html +++ b/src/create/projects/react-tailwind-spa/REPLACE_ME_WITH_YOUR_APP_FILE_NAME.html @@ -3,12 +3,12 @@ - REPLACE_ME_WITH_YOUR_APP_FILE_NAME | Powered by Bun - + REPLACE_ME_WITH_YOUR_APP_BASE_NAME | Powered by Bun +
- + diff --git a/src/create/projects/react-tailwind-spa/package.json b/src/create/projects/react-tailwind-spa/package.json index 1850b87a1479a3..e0a5b65f00404c 100644 --- a/src/create/projects/react-tailwind-spa/package.json +++ b/src/create/projects/react-tailwind-spa/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "private": true, "scripts": { - "dev": "bun './*.html'", - "build": "bun *.build.ts" + "dev": "bun './**/*.html'", + "build": "bun REPLACE_ME_WITH_YOUR_APP_FILE_NAME.build.ts" } }