From d19c22d5921bdf21de9a7d7bc043b8523ac81893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roj=20=5Bro=CB=90=CA=92=5D?= Date: Tue, 30 Jan 2024 14:34:38 +0300 Subject: [PATCH] feat: add back twind in init wizard (#2290) --- init.ts | 89 +++++++++++++++++++++++++++++++++++-------- src/dev/imports.ts | 12 ++++-- tests/init_test.ts | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 19 deletions(-) diff --git a/init.ts b/init.ts index 2d82fbaef06..9a28f7ce46b 100644 --- a/init.ts +++ b/init.ts @@ -6,6 +6,7 @@ import { dotenvImports, freshImports, tailwindImports, + twindImports, } from "./src/dev/imports.ts"; ensureMinDenoVersion(); @@ -26,22 +27,26 @@ USAGE: OPTIONS: --force Overwrite existing files - --tailwind Setup project to use 'tailwind' for styling - --vscode Setup project for VSCode + --tailwind Use Tailwind for styling + --twind Use Twind for styling + --vscode Setup project for VS Code --docker Setup Project to use Docker `; const CONFIRM_EMPTY_MESSAGE = "The target directory is not empty (files could get overwritten). Do you want to continue anyway?"; -const USE_TAILWIND_MESSAGE = - "Fresh has built in support for styling using Tailwind CSS. Do you want to use this?"; - const USE_VSCODE_MESSAGE = "Do you use VS Code?"; const flags = parse(Deno.args, { - boolean: ["force", "tailwind", "vscode", "docker", "help"], - default: { "force": null, "tailwind": null, "vscode": null, "docker": null }, + boolean: ["force", "tailwind", "twind", "vscode", "docker", "help"], + default: { + force: null, + tailwind: null, + twind: null, + vscode: null, + docker: null, + }, alias: { help: "h", }, @@ -52,6 +57,10 @@ if (flags.help) { Deno.exit(0); } +if (flags.tailwind && flags.twind) { + error("Cannot use Tailwind and Twind at the same time."); +} + console.log(); console.log( colors.bgRgb8( @@ -90,9 +99,25 @@ try { } console.log("%cLet's set up your new Fresh project.\n", "font-weight: bold"); -const useTailwind = flags.tailwind === null - ? confirm(USE_TAILWIND_MESSAGE) - : flags.tailwind; +let useTailwind = flags.tailwind || false; +let useTwind = flags.twind || false; + +if (flags.tailwind == null && flags.twind == null) { + if (confirm("Do you want to use a styling library?")) { + console.log(); + console.log("1. Tailwind"); + console.log("2. Twind"); + switch ( + (prompt("Which styling library do you want to use? [1]") || "1").trim() + ) { + case "2": + useTwind = true; + break; + default: + useTailwind = true; + } + } +} const useVSCode = flags.vscode === null ? confirm(USE_VSCODE_MESSAGE) @@ -322,6 +347,24 @@ if (useTailwind) { ); } +const TWIND_CONFIG_TS = `import { defineConfig, Preset } from "@twind/core"; +import presetTailwind from "@twind/preset-tailwind"; +import presetAutoprefix from "@twind/preset-autoprefix"; + +export default { + ...defineConfig({ + presets: [presetTailwind() as Preset, presetAutoprefix() as Preset], + }), + selfURL: import.meta.url, +}; +`; +if (useTwind) { + await Deno.writeTextFile( + join(resolvedDirectory, "twind.config.ts"), + TWIND_CONFIG_TS, + ); +} + const NO_TAILWIND_STYLES = ` *, *::before, @@ -461,7 +504,7 @@ export default function App({ Component }: PageProps) { ${basename(resolvedDirectory)} - + ${useTwind ? "" : ``} @@ -481,10 +524,12 @@ const TAILWIND_CSS = `@tailwind base; @tailwind utilities;`; const cssStyles = useTailwind ? TAILWIND_CSS : NO_TAILWIND_STYLES; -await Deno.writeTextFile( - join(resolvedDirectory, "static", "styles.css"), - cssStyles, -); +if (!useTwind) { + await Deno.writeTextFile( + join(resolvedDirectory, "static", "styles.css"), + cssStyles, + ); +} const STATIC_LOGO = ` @@ -515,10 +560,19 @@ if (useTailwind) { FRESH_CONFIG_TS += `import tailwind from "$fresh/plugins/tailwind.ts"; `; } +if (useTwind) { + FRESH_CONFIG_TS += `import twind from "$fresh/plugins/twindv1.ts"; +import twindConfig from "./twind.config.ts"; +`; +} FRESH_CONFIG_TS += ` export default defineConfig({${ - useTailwind ? `\n plugins: [tailwind()],\n` : "" + useTailwind + ? `\n plugins: [tailwind()],\n` + : useTwind + ? `\n plugins: [twind(twindConfig)],\n` + : "" }}); `; const CONFIG_TS_PATH = join(resolvedDirectory, "fresh.config.ts"); @@ -592,6 +646,9 @@ if (useTailwind) { // deno-lint-ignore no-explicit-any (config as any).nodeModulesDir = true; } +if (useTwind) { + twindImports(config.imports); +} dotenvImports(config.imports); const DENO_CONFIG = JSON.stringify(config, null, 2) + "\n"; diff --git a/src/dev/imports.ts b/src/dev/imports.ts index 5a1d967ea22..14689364cc2 100644 --- a/src/dev/imports.ts +++ b/src/dev/imports.ts @@ -1,7 +1,9 @@ export const RECOMMENDED_PREACT_VERSION = "10.19.2"; export const RECOMMENDED_PREACT_SIGNALS_VERSION = "1.2.1"; export const RECOMMENDED_PREACT_SIGNALS_CORE_VERSION = "1.5.0"; -export const RECOMMENDED_TWIND_VERSION = "0.16.19"; +export const RECOMMENDED_TWIND_CORE_VERSION = "1.1.3"; +export const RECOMMENDED_TWIND_PRESET_AUTOPREFIX_VERSION = "1.0.7"; +export const RECOMMENDED_TWIND_PRESET_TAILWIND_VERSION = "1.1.4"; export const RECOMMENDED_STD_VERSION = "0.211.0"; export const RECOMMENDED_TAILIWIND_VERSION = "3.4.1"; @@ -16,8 +18,12 @@ export function freshImports(imports: Record) { } export function twindImports(imports: Record) { - imports["twind"] = `https://esm.sh/twind@${RECOMMENDED_TWIND_VERSION}`; - imports["twind/"] = `https://esm.sh/twind@${RECOMMENDED_TWIND_VERSION}/`; + imports["@twind/core"] = + `https://esm.sh/@twind/core@${RECOMMENDED_TWIND_CORE_VERSION}`; + imports["@twind/preset-tailwind"] = + `https://esm.sh/@twind/preset-tailwind@${RECOMMENDED_TWIND_PRESET_TAILWIND_VERSION}/`; + imports["@twind/preset-autoprefix"] = + `https://esm.sh/@twind/preset-autoprefix@${RECOMMENDED_TWIND_PRESET_AUTOPREFIX_VERSION}/`; } export function tailwindImports(imports: Record) { diff --git a/tests/init_test.ts b/tests/init_test.ts index f24447165f5..347e9967542 100644 --- a/tests/init_test.ts +++ b/tests/init_test.ts @@ -242,6 +242,100 @@ Deno.test({ }, }); +Deno.test({ + name: "fresh-init --twind --vscode", + async fn(t) { + // Preparation + const tmpDirName = await Deno.makeTempDir(); + + await t.step("execute init command", async () => { + const cliProcess = new Deno.Command(Deno.execPath(), { + args: [ + "run", + "-A", + "init.ts", + tmpDirName, + "--twind", + "--vscode", + ], + stdin: "null", + stdout: "null", + }); + const { code } = await cliProcess.output(); + assertEquals(code, 0); + }); + + const files = [ + "/README.md", + "/fresh.gen.ts", + "/twind.config.ts", + "/components/Button.tsx", + "/islands/Counter.tsx", + "/main.ts", + "/routes/greet/[name].tsx", + "/routes/api/joke.ts", + "/routes/_app.tsx", + "/routes/index.tsx", + "/static/logo.svg", + "/.vscode/settings.json", + "/.vscode/extensions.json", + "/.gitignore", + ]; + + await t.step("check generated files", async () => { + await assertFileExistence(files, tmpDirName); + }); + + await t.step("start up the server and access the root page", async () => { + const { serverProcess, lines, address } = await startFreshServer({ + args: ["run", "-A", "--check", "main.ts"], + cwd: tmpDirName, + }); + + await delay(100); + + // Access the root page + const res = await fetch(address); + await res.body?.cancel(); + assertEquals(res.status, STATUS_CODE.OK); + + // verify the island is revived. + const browser = await puppeteer.launch({ args: ["--no-sandbox"] }); + const page = await browser.newPage(); + await page.goto(address, { waitUntil: "networkidle2" }); + + const counter = await page.$("body > div > div > div > p"); + let counterValue = await counter?.evaluate((el) => el.textContent); + assertEquals(counterValue, "3"); + + const fontWeight = await counter?.evaluate((el) => + getComputedStyle(el).fontWeight + ); + assertEquals(fontWeight, "400"); + + const buttonPlus = await page.$( + "body > div > div > div > button:nth-child(3)", + ); + await buttonPlus?.click(); + + await waitForText(page, "body > div > div > div > p", "4"); + + counterValue = await counter?.evaluate((el) => el.textContent); + assert(counterValue === "4"); + await page.close(); + await browser.close(); + + serverProcess.kill("SIGTERM"); + await serverProcess.status; + + // Drain the lines stream + for await (const _ of lines) { /* noop */ } + }); + + await retry(() => Deno.remove(tmpDirName, { recursive: true })); + }, +}); + Deno.test("fresh-init error(help)", async function (t) { const includeText = "fresh-init";