diff --git a/README.md b/README.md index 694ca4c..3108bf7 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This is a monorepo containing multiple standalone projects. Each project lives i code-samples/ ├── typesense-astro-search/ # Astro + Typesense search implementation ├── typesense-next-search-bar/ # Next.js + Typesense search implementation +├── typesense-solid-js-search/ # SolidJS + Typesense search implementation ├── typesense-vanilla-js-search/ # Vanilla JS + Typesense search implementation ├── ... # More projects coming soon └── README.md # You are here @@ -21,6 +22,7 @@ code-samples/ | -------------------------------------------------------------- | ---------- | ---------------------------------------------------- | | [typesense-astro-search](./typesense-astro-search) | Astro | A modern search bar with instant search capabilities | | [typesense-next-search-bar](./typesense-next-search-bar) | Next.js | A modern search bar with instant search capabilities | +| [typesense-solid-js-search](./typesense-solid-js-search) | SolidJS | A modern search bar with instant search capabilities | | [typesense-vanilla-js-search](./typesense-vanilla-js-search) | Vanilla JS | A modern search bar with instant search capabilities | ## Getting Started diff --git a/typesense-solid-js-search/.env.sample b/typesense-solid-js-search/.env.sample new file mode 100644 index 0000000..7e42c7c --- /dev/null +++ b/typesense-solid-js-search/.env.sample @@ -0,0 +1,4 @@ +VITE_TYPESENSE_API_KEY=xyz +VITE_TYPESENSE_HOST=localhost +VITE_TYPESENSE_PORT=8108 +VITE_TYPESENSE_PROTOCOL=http \ No newline at end of file diff --git a/typesense-solid-js-search/README.md b/typesense-solid-js-search/README.md new file mode 100644 index 0000000..90a6fd9 --- /dev/null +++ b/typesense-solid-js-search/README.md @@ -0,0 +1,81 @@ +# SolidJS Search Bar with Typesense + +A modern search bar application built with SolidJS and Typesense, featuring instant search capabilities. + +## Tech Stack + +- SolidJS +- Typesense +- typesense-instantsearch-adapter & instantsearch.js + +## Prerequisites + +- Node.js 18+ and npm 9+. +- Docker (for running Typesense locally). Alternatively, you can use a Typesense Cloud cluster. +- Basic knowledge of SolidJS. + +## Quick Start + +### 1. Clone the repository + +```bash +git clone https://github.com/typesense/code-samples.git +cd typesense-solid-js-search +``` + +### 2. Install dependencies + +```bash +npm install +``` + +### 3. Set up environment variables + +Create a `.env` file in the project root with the following content: + +```env +VITE_TYPESENSE_API_KEY=xyz +VITE_TYPESENSE_HOST=localhost +VITE_TYPESENSE_PORT=8108 +VITE_TYPESENSE_PROTOCOL=http +``` + +### 4. Project Structure + +```text +├── src +│ ├── components +│ │ ├── BookCard.tsx +│ │ ├── BookList.tsx +│ │ ├── BookSearch.tsx +│ │ ├── Heading.tsx +│ │ └── icons.tsx +│ ├── types +│ │ └── Book.ts +│ ├── utils +│ │ └── typesense.ts +│ ├── App.tsx +│ ├── index.css +│ └── index.tsx +├── index.html +└── package.json +``` + +### 5. Start the development server + +```bash +npm run dev +``` + +Open [http://localhost:5173](http://localhost:5173) in your browser. + +### 6. Deployment + +Set env variables to point the app to the Typesense Cluster: + +```env +VITE_TYPESENSE_API_KEY=xxx +VITE_TYPESENSE_HOST=xxx.typesense.net +VITE_TYPESENSE_PORT=443 +VITE_TYPESENSE_PROTOCOL=https +``` diff --git a/typesense-solid-js-search/index.html b/typesense-solid-js-search/index.html new file mode 100644 index 0000000..bb839bf --- /dev/null +++ b/typesense-solid-js-search/index.html @@ -0,0 +1,13 @@ + + + + + + + Typesense Solid.js Search + + +
+ + + diff --git a/typesense-solid-js-search/package.json b/typesense-solid-js-search/package.json new file mode 100644 index 0000000..267c428 --- /dev/null +++ b/typesense-solid-js-search/package.json @@ -0,0 +1,23 @@ +{ + "name": "typesense-solid-js-search", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "instantsearch.js": "^4.86.1", + "solid-js": "^1.9.10", + "typesense": "^2.1.0", + "typesense-instantsearch-adapter": "^2.9.0" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "vite-plugin-solid": "^2.11.10" + } +} diff --git a/typesense-solid-js-search/public/favicon.png b/typesense-solid-js-search/public/favicon.png new file mode 100644 index 0000000..f42ec19 Binary files /dev/null and b/typesense-solid-js-search/public/favicon.png differ diff --git a/typesense-solid-js-search/src/App.css b/typesense-solid-js-search/src/App.css new file mode 100644 index 0000000..409f8cc --- /dev/null +++ b/typesense-solid-js-search/src/App.css @@ -0,0 +1,4 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + font-weight: 400; +} diff --git a/typesense-solid-js-search/src/App.tsx b/typesense-solid-js-search/src/App.tsx new file mode 100644 index 0000000..dcbe50a --- /dev/null +++ b/typesense-solid-js-search/src/App.tsx @@ -0,0 +1,14 @@ +import "./App.css"; +import Heading from "./components/Heading"; +import { BookSearch } from "./components/BookSearch"; + +function App() { + return ( + <> + + + + ); +} + +export default App; diff --git a/typesense-solid-js-search/src/components/BookCard.module.css b/typesense-solid-js-search/src/components/BookCard.module.css new file mode 100644 index 0000000..3b92412 --- /dev/null +++ b/typesense-solid-js-search/src/components/BookCard.module.css @@ -0,0 +1,73 @@ +.bookCard { + background: white; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: + transform 0.2s ease, + box-shadow 0.2s ease; +} + +.bookCard:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.bookImageContainer { + width: 100%; + height: 250px; + overflow: hidden; + background: #f5f5f5; + display: flex; + align-items: center; + justify-content: center; +} + +.bookImage { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; +} + +.bookInfo { + padding: 1rem; +} + +.bookTitle { + font-size: 1.1rem; + font-weight: 600; + color: #2c3e50; + margin: 0 0 0.5rem; + line-height: 1.3; +} + +.bookAuthor { + color: #7f8c8d; + font-size: 0.9rem; + margin: 0 0 0.5rem; +} + +.ratingContainer { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.starRating { + color: #f39c12; + font-size: 0.9rem; +} + +.ratingText { + color: #95a5a6; + font-size: 0.8rem; +} + +.bookYear { + color: #95a5a6; + font-size: 0.85rem; + margin: 0; +} diff --git a/typesense-solid-js-search/src/components/BookCard.tsx b/typesense-solid-js-search/src/components/BookCard.tsx new file mode 100644 index 0000000..3c52301 --- /dev/null +++ b/typesense-solid-js-search/src/components/BookCard.tsx @@ -0,0 +1,42 @@ +import styles from "./BookCard.module.css"; +import type { Book } from "../types/Book"; + +interface BookCardProps { + book: Book; +} + +export function BookCard(props: BookCardProps) { + const stars = "★".repeat(Math.round(props.book.average_rating || 0)); + + return ( +
+ {props.book.image_url && ( +
+ {`Cover +
+ )} +
+

{props.book.title}

+

+ {props.book.authors?.join(", ") || "Unknown Author"} +

+
+ {stars} + + {props.book.average_rating?.toFixed(1) || "0"} ( + {props.book.ratings_count?.toLocaleString() || 0} ratings) + +
+ {props.book.publication_year && ( +

+ Published: {props.book.publication_year} +

+ )} +
+
+ ); +} diff --git a/typesense-solid-js-search/src/components/BookList.module.css b/typesense-solid-js-search/src/components/BookList.module.css new file mode 100644 index 0000000..34b2b67 --- /dev/null +++ b/typesense-solid-js-search/src/components/BookList.module.css @@ -0,0 +1,79 @@ +.bookList { + padding-bottom: 2rem; +} + +.loadingContainer { + text-align: center; + padding: 3rem 1rem; +} + +.spinner { + display: inline-block; + width: 2rem; + height: 2rem; + border: 3px solid #f3f3f3; + border-top: 3px solid #667eea; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.noResults { + text-align: center; + padding: 3rem 1rem; + background: #f9f9f9; + border-radius: 8px; + margin: 2rem 0; +} + +.noResults h3 { + color: #333; + margin-bottom: 0.5rem; +} + +.noResults p { + color: #666; + margin: 0; +} + +.resultsCount { + color: #666; + margin-bottom: 1.5rem; + font-size: 0.95rem; +} + +.bookGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; + list-style: none; + padding: 0; + margin: 0; +} + +@media (min-width: 768px) { + .bookGrid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .bookGrid { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 1280px) { + .bookGrid { + grid-template-columns: repeat(4, 1fr); + } +} diff --git a/typesense-solid-js-search/src/components/BookList.tsx b/typesense-solid-js-search/src/components/BookList.tsx new file mode 100644 index 0000000..85a94f0 --- /dev/null +++ b/typesense-solid-js-search/src/components/BookList.tsx @@ -0,0 +1,35 @@ +import { For, Show } from "solid-js"; +import { BookCard } from "./BookCard"; +import styles from "./BookList.module.css"; +import type { Book } from "../types/Book"; + +interface BookListProps { + books: Book[]; + loading: boolean; +} + +export function BookList(props: BookListProps) { + return ( +
+ +
+
+

Searching...

+
+
+ + +
+

No books found

+

Try adjusting your search or try different keywords.

+
+
+ + 0}> +
+ {(book) => } +
+
+
+ ); +} diff --git a/typesense-solid-js-search/src/components/BookSearch.module.css b/typesense-solid-js-search/src/components/BookSearch.module.css new file mode 100644 index 0000000..f17a37d --- /dev/null +++ b/typesense-solid-js-search/src/components/BookSearch.module.css @@ -0,0 +1,128 @@ +.searchContainer { + max-width: 1200px; + margin: 0 auto; + padding: 2rem 1.5rem; +} + +.searchBoxContainer { + max-width: 600px; + margin: 0 auto 2rem; +} + +/* Override InstantSearch searchbox styles */ +.ais-SearchBox { + width: 100%; +} + +.searchForm { + position: relative; + width: 100%; + background: transparent; +} + +.searchInput { + width: 100%; + padding: 0.85rem 3rem 0.85rem 3rem; + font-size: 1rem; + border: 2px solid #e0e0e0; + border-radius: 30px; + outline: none; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + box-sizing: border-box; + background: white; +} + +.searchInput:focus { + border-color: #667eea; + box-shadow: 0 2px 12px rgba(102, 126, 234, 0.2); +} + +/* Add magnifying glass icon using pseudo-element */ +.searchForm::before { + content: ""; + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + width: 16px; + height: 16px; + background-image: url("data:image/svg+xml,%3Csvg fill='none' stroke='currentColor' viewBox='0 0 24 24' width='16' height='16'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z'%3E%3C/path%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + color: #7f8c8d; + pointer-events: none; + z-index: 1; +} + +/* Hide InstantSearch default reset button more aggressively */ +.ais-SearchBox-reset { + display: none !important; + visibility: hidden !important; + opacity: 0 !important; + width: 0 !important; + height: 0 !important; + overflow: hidden !important; + position: absolute !important; + left: -9999px !important; +} + +/* Also hide any reset button inside the form */ +.ais-SearchBox-form .ais-SearchBox-reset { + display: none !important; + visibility: hidden !important; + opacity: 0 !important; + width: 0 !important; + height: 0 !important; + overflow: hidden !important; + position: absolute !important; + left: -9999px !important; +} + +.searchSubmit, +.searchReset { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + cursor: pointer; + padding: 0.5rem; + color: #7f8c8d; + transition: color 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.searchSubmit { + display: none; +} + +.searchReset { + right: 0.75rem; +} + +.searchSubmit:hover, +.searchReset:hover { + color: #667eea; +} + +.searchSubmitIcon, +.searchResetIcon { + width: 16px; + height: 16px; +} + +.resultsCount { + color: #666; + margin-bottom: 1.5rem; + font-size: 0.95rem; + text-align: center; +} + +@media (max-width: 768px) { + .searchContainer { + padding: 1.5rem 1rem; + } +} diff --git a/typesense-solid-js-search/src/components/BookSearch.tsx b/typesense-solid-js-search/src/components/BookSearch.tsx new file mode 100644 index 0000000..9b994b3 --- /dev/null +++ b/typesense-solid-js-search/src/components/BookSearch.tsx @@ -0,0 +1,99 @@ +import { onMount, onCleanup, createSignal } from "solid-js"; +import { typesenseInstantsearchAdapter } from "../utils/typesense"; +import instantsearch from "instantsearch.js"; +import { searchBox, hits, stats, configure } from "instantsearch.js/es/widgets"; +import { BookList } from "./BookList"; +import styles from "./BookSearch.module.css"; +import type { Book } from "../types/Book"; + +export function BookSearch() { + const [books, setBooks] = createSignal([]); + const [loading, setLoading] = createSignal(false); + + let search: any; + + onMount(() => { + search = instantsearch({ + indexName: "books", + searchClient: typesenseInstantsearchAdapter.searchClient, + future: { + preserveSharedStateOnUnmount: true, + }, + }); + + search.addWidgets([ + configure({ + hitsPerPage: 12, + }), + searchBox({ + container: "#searchbox", + placeholder: "Search by title or author...", + showReset: false, + showSubmit: false, + cssClasses: { + form: styles.searchForm, + input: styles.searchInput, + submit: styles.searchSubmit, + }, + }), + stats({ + container: "#stats", + templates: { + text(data, { html }) { + if (data.hasManyResults) { + return html`${data.nbHits.toLocaleString()} results found`; + } else if (data.hasOneResult) { + return html`1 result found`; + } else { + return html`No results found`; + } + }, + }, + }), + hits({ + container: "#hits", + templates: { + item: () => { + return ""; + }, + empty: () => { + return ""; + }, + }, + transformItems: (items: any[]) => { + const booksData = items.map((item) => item as Book); + setBooks(booksData); + return items; + }, + }), + ]); + + // Listen for search state changes + search.on("render", () => { + const helper = search.helper; + setLoading(helper.state.loading); + }); + + search.start(); + }); + + onCleanup(() => { + if (search) { + search.dispose(); + } + }); + + return ( +
+
+ +
+ +
+ + + + +
+ ); +} diff --git a/typesense-solid-js-search/src/components/Heading.module.css b/typesense-solid-js-search/src/components/Heading.module.css new file mode 100644 index 0000000..7b0f233 --- /dev/null +++ b/typesense-solid-js-search/src/components/Heading.module.css @@ -0,0 +1,93 @@ +:root { + --primary-glow: linear-gradient( + 135deg, + rgba(213, 43, 127, 0.4), + rgba(102, 126, 234, 0.4) + ); + --accent: #d52b7f; + --step-3: clamp(1.5rem, 4vw, 2.5rem); + --step-0: clamp(0.875rem, 2vw, 1rem); +} + +.Heading { + width: 100%; + text-align: center; + position: relative; + margin-top: 6rem; + margin-bottom: 2rem; +} + +.Heading::after { + content: ""; + background: var(--primary-glow); + width: 320px; + height: 180px; + z-index: -1; + left: 50%; + transform: translateX(-50%); + position: absolute; + filter: blur(45px); + top: -50%; + animation: move 40s ease-out infinite; +} + +.Heading h1 { + font-size: var(--step-3); + font-weight: 700; + color: #1a1a1a; + margin: 0 0 0.5rem 0; +} + +.typesense { + color: var(--accent); + font-size: var(--step-0); + text-decoration: none; + font-family: monospace; +} + +.typesense:hover { + text-decoration: underline; +} + +.githubLink { + position: fixed; + top: 2rem; + right: 2rem; + color: #333; + transition: color 0.2s ease; +} + +.githubLink:hover { + color: #000; +} + +.githubLink svg { + width: 28px; + height: 28px; +} + +.solidJSLogo { + width: calc(3.75vmax + 3.25rem); + position: relative; + top: calc(0.1vmax + 0.1rem); + vertical-align: middle; + margin-left: 0.25rem; +} + +@keyframes move { + 0% { + transform: translate(0%) scale(1); + } + 45% { + transform: translate(-40%, 20%) scale(1.2); + } + 75% { + transform: translate(5%, 40%) scale(1.3); + } + 90% { + transform: translate(-5%, 5%) scale(0.9); + } + 100% { + transform: translate(0%) scale(1); + } +} diff --git a/typesense-solid-js-search/src/components/Heading.tsx b/typesense-solid-js-search/src/components/Heading.tsx new file mode 100644 index 0000000..5c3ddf8 --- /dev/null +++ b/typesense-solid-js-search/src/components/Heading.tsx @@ -0,0 +1,33 @@ +import { GithubIcon, SolidJSIcon } from "./icons"; +import s from "./Heading.module.css"; + +export default function Heading() { + return ( + <> +
+

Solid JS Search Bar

+
+ powered by{" "} + + typesense| + {" "} + & +
+
+ + + + + ); +} diff --git a/typesense-solid-js-search/src/components/icons.tsx b/typesense-solid-js-search/src/components/icons.tsx new file mode 100644 index 0000000..35618b5 --- /dev/null +++ b/typesense-solid-js-search/src/components/icons.tsx @@ -0,0 +1,158 @@ +import type { JSX } from "solid-js"; +type _svgProp = JSX.HTMLAttributes; + +export function GithubIcon({ ...svgProps }: _svgProp) { + return ( + + + + ); +} +export function SolidJSIcon({ ...svgProps }: _svgProp) { + const style = ` .a { + fill: #76b3e1; + } + + .b, .d { + isolation: isolate; + opacity: 0.3; + } + + .b { + fill: url(#a); + } + + .c { + fill: #518ac8; + } + + .d { + fill: url(#b); + } + + .e { + fill: url(#c); + } + + .f { + fill: url(#d); + } + + .g { + fill: #58595b; + }`; + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/typesense-solid-js-search/src/index.css b/typesense-solid-js-search/src/index.css new file mode 100644 index 0000000..e69de29 diff --git a/typesense-solid-js-search/src/index.tsx b/typesense-solid-js-search/src/index.tsx new file mode 100644 index 0000000..f67cd20 --- /dev/null +++ b/typesense-solid-js-search/src/index.tsx @@ -0,0 +1,8 @@ +/* @refresh reload */ +import { render } from 'solid-js/web' +import './index.css' +import App from './App.tsx' + +const root = document.getElementById('root') + +render(() => , root!) diff --git a/typesense-solid-js-search/src/types/Book.ts b/typesense-solid-js-search/src/types/Book.ts new file mode 100644 index 0000000..a60803f --- /dev/null +++ b/typesense-solid-js-search/src/types/Book.ts @@ -0,0 +1,9 @@ +export type Book = { + id: string; + title: string; + authors: string[]; + publication_year: number; + average_rating: number; + image_url: string; + ratings_count: number; +}; diff --git a/typesense-solid-js-search/src/utils/typesense.ts b/typesense-solid-js-search/src/utils/typesense.ts new file mode 100644 index 0000000..fe4a79d --- /dev/null +++ b/typesense-solid-js-search/src/utils/typesense.ts @@ -0,0 +1,17 @@ +import TypesenseInstantSearchAdapter from "typesense-instantsearch-adapter"; + +export const typesenseInstantsearchAdapter = new TypesenseInstantSearchAdapter({ + server: { + apiKey: import.meta.env.VITE_TYPESENSE_API_KEY || "xyz", + nodes: [ + { + host: import.meta.env.VITE_TYPESENSE_HOST || "localhost", + port: Number(import.meta.env.VITE_TYPESENSE_PORT) || 8108, + protocol: import.meta.env.VITE_TYPESENSE_PROTOCOL || "http", + }, + ], + }, + additionalSearchParameters: { + query_by: "title,authors", + }, +}); diff --git a/typesense-solid-js-search/tsconfig.app.json b/typesense-solid-js-search/tsconfig.app.json new file mode 100644 index 0000000..c0b480e --- /dev/null +++ b/typesense-solid-js-search/tsconfig.app.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/typesense-solid-js-search/tsconfig.json b/typesense-solid-js-search/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/typesense-solid-js-search/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/typesense-solid-js-search/tsconfig.node.json b/typesense-solid-js-search/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/typesense-solid-js-search/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/typesense-solid-js-search/vite.config.ts b/typesense-solid-js-search/vite.config.ts new file mode 100644 index 0000000..4095d9b --- /dev/null +++ b/typesense-solid-js-search/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' + +export default defineConfig({ + plugins: [solid()], +})