Compare commits
5
Commits
v0.4.1
..
5cee5f99e9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cee5f99e9 | ||
|
|
5e449a560b | ||
|
|
ab67b5a5da | ||
|
|
c93a8fc68d | ||
|
|
180f16defd |
No files matched your search
@@ -1,8 +1,6 @@
|
||||
# frontend-svelte
|
||||
|
||||
Copier template for a SvelteKit frontend, either as a `frontend/` part next to other parts or alone at the root of the project (`layout`): SvelteKit on Bun with Tailwind v4, ultracite (biome) and optional shadcn-svelte, served by a Bun server with SSR, as a static SPA behind `serve.ts`, or as a static SPA that the backend api serves from `frontend/build`.
|
||||
|
||||
Ships `frontend/docker-compose.yml` with root-relative paths and profiles per service, `docker-compose.override.yml.example` (the `caddy` network aliases, copied by `make env`), a `Makefile` with `fmt`, `check`, `build` and `dev`, `.env.example`, `CLAUDE.md`, `.claude/rules` for Svelte and ultracite. The Svelte MCP server comes from `infra`. Applied on top of the `infra` template, `layout: parts` or `layout: single` respectively.
|
||||
Copier template for a SvelteKit frontend on Bun with Tailwind v4, biome or ESLint + Prettier and optional shadcn-svelte: SSR on Bun, a static SPA behind `serve.ts`, or a static SPA served by the backend api. A `frontend/` part or the whole project (`layout`), on top of `infra`.
|
||||
|
||||
```sh
|
||||
copier copy --trust -d project_name="My Thing" -d serve=bun <this-repo> <dest>
|
||||
|
||||
+11
-1
@@ -37,6 +37,14 @@ ui_library:
|
||||
shadcn-svelte: shadcn
|
||||
default: none
|
||||
|
||||
linter:
|
||||
type: str
|
||||
help: Linter and formatter
|
||||
choices:
|
||||
Biome with the ultracite preset: biome
|
||||
ESLint + Prettier (the sv create setup): eslint
|
||||
default: biome
|
||||
|
||||
install_deps:
|
||||
type: bool
|
||||
help: bun install after generation
|
||||
@@ -58,4 +66,6 @@ prefix:
|
||||
default: "{{ 'frontend/' if layout == 'part' else '' }}"
|
||||
|
||||
_tasks:
|
||||
- "{% if install_deps %}sh -c 'cd {{ part_dir }} && bun install'{% else %}true{% endif %}"
|
||||
# prettier-plugin-tailwindcss and biome sort classes differently, the
|
||||
# sources follow biome: an eslint project is formatted once, right away.
|
||||
- "{% if _copier_operation == 'copy' and install_deps %}sh -c 'cd {{ part_dir }} && bun install{% if linter == 'eslint' %} && bun run fix{% endif %}'{% else %}true{% endif %}"
|
||||
File renamed without changes.
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"command": "{% if layout == 'part' %}cd frontend && {% endif %}bun run fix --skip=correctness/noUnusedImports",
|
||||
"command": "{% if layout == 'part' %}cd frontend && {% endif %}bun run fix{% if linter == 'biome' %} --skip=correctness/noUnusedImports{% endif %}",
|
||||
"type": "command"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
node_modules
|
||||
.svelte-kit
|
||||
build
|
||||
.env
|
||||
# .env goes into the build: $env/static/public (and static/private) are
|
||||
# inlined at build time from it. It stays in the builder stage, the runtime
|
||||
# image gets build/ only. Local overrides (.env.local and the like) stay out.
|
||||
.env.*
|
||||
@@ -1,9 +1,6 @@
|
||||
COMPOSE := {% if layout == 'part' %}cd .. && {% endif %}docker compose
|
||||
{% if serve != 'api' %}IMAGE := {{ project_slug }}/frontend
|
||||
{% endif %}changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null
|
||||
stamp = { git update-ref $(1) HEAD 2>/dev/null || true; }
|
||||
|
||||
.PHONY: {% if layout == 'root' %}env recreate rebuild restart logs down deploy {% endif %}fmt check build pre-deploy post-deploy dev
|
||||
.PHONY: {% if layout == 'root' %}env recreate rebuild restart logs down {% endif %}fmt check build dev
|
||||
|
||||
fmt:
|
||||
bun run fix
|
||||
@@ -16,14 +13,6 @@ build:
|
||||
{% if serve == 'api' %} $(COMPOSE) run --rm frontend-dev sh -c "bun install && bun run build"
|
||||
{% else %} $(COMPOSE) build frontend
|
||||
{% endif %}
|
||||
pre-deploy:
|
||||
@if $(call changed,refs/deploy/frontend,.) || {% if serve == 'api' %}! test -d build{% else %}! docker image inspect $(IMAGE) >/dev/null 2>&1{% endif %}; then \
|
||||
$(MAKE) build && $(call stamp,refs/deploy/frontend); \
|
||||
fi
|
||||
|
||||
post-deploy:
|
||||
@:
|
||||
|
||||
dev:
|
||||
bun run dev
|
||||
{% if layout == 'root' %}
|
||||
@@ -52,8 +41,4 @@ logs:
|
||||
|
||||
down:
|
||||
$(COMPOSE) down
|
||||
|
||||
deploy: pre-deploy
|
||||
$(COMPOSE) up -d
|
||||
$(MAKE) post-deploy
|
||||
{% endif %}
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
{% if serve != 'api' %} frontend:
|
||||
build: {{ part_dir }}
|
||||
image: {{ project_slug }}/frontend
|
||||
image: {{ project_slug | replace('_', '-') }}/frontend
|
||||
profiles: [frontend, services]
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
|
||||
@@ -8,14 +8,18 @@
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"dev": "vite dev",
|
||||
{%- if linter == 'biome' %}
|
||||
"fix": "ultracite fix",
|
||||
"lint": "ultracite check",
|
||||
{%- else %}
|
||||
"fix": "prettier --write . && eslint --fix .",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
{%- endif %}
|
||||
"prepare": "svelte-kit sync || echo ''",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
{%- set deps = {
|
||||
"@biomejs/biome": "2.5.13",
|
||||
"@sveltejs/kit": "^2.63.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
@@ -24,9 +28,23 @@
|
||||
"svelte-check": "^4.6.0",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"typescript": "^6.0.3",
|
||||
"ultracite": "7.12.0",
|
||||
"vite": "^8.0.16",
|
||||
} -%}
|
||||
{%- if linter == 'biome' %}{% set _ = deps.update({
|
||||
"@biomejs/biome": "2.5.13",
|
||||
"ultracite": "7.12.0",
|
||||
}) %}{% else %}{% set _ = deps.update({
|
||||
"@eslint/compat": "^2.1.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"eslint": "^10.12.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.23.0",
|
||||
"globals": "^17.13.0",
|
||||
"prettier": "^3.9.9",
|
||||
"prettier-plugin-svelte": "^4.1.1",
|
||||
"prettier-plugin-tailwindcss": "^0.8.1",
|
||||
"typescript-eslint": "^8.71.1",
|
||||
}) %}{% endif -%}
|
||||
{%- if frontend_adapter == 'bun' %}{% set _ = deps.update({"svelte-adapter-bun": "^1.0.1"}) %}{% else %}{% set _ = deps.update({"@sveltejs/adapter-static": "^3.0.10"}) %}{% endif -%}
|
||||
{%- if ui_library == 'shadcn' %}{% set _ = deps.update({
|
||||
"@internationalized/date": "^3.12.0",
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
// biome-ignore-all lint/suspicious/noEmptySource: temporary
|
||||
@@ -0,0 +1,3 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
{% if linter == 'biome' %}// biome-ignore-all lint/suspicious/noEmptySource: temporary
|
||||
{% endif %}
|
||||
@@ -5,8 +5,8 @@ import adapter from "svelte-adapter-bun";
|
||||
const config = {
|
||||
compilerOptions: {
|
||||
runes: ({ filename }) =>
|
||||
// biome-ignore lint/performance/useTopLevelRegex: svelte default behaviour
|
||||
filename.split(/[/\\]/).includes("node_modules") ? undefined : true,
|
||||
{% if linter == 'biome' %} // biome-ignore lint/performance/useTopLevelRegex: svelte default behaviour
|
||||
{% endif %} filename.split(/[/\\]/).includes("node_modules") ? undefined : true,
|
||||
},
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
# {{ project_name }}
|
||||
|
||||
Runs in Docker, locally the same way as in production. `COMPOSE_PROFILES` in `.env` selects the services that run on this machine. `docker-compose.override.yml` holds what differs between machines: published ports and the aliases on the external `caddy` network.
|
||||
|
||||
```sh
|
||||
make env
|
||||
make recreate
|
||||
make env # .env and docker-compose.override.yml from the examples
|
||||
make recreate # the services in COMPOSE_PROFILES
|
||||
make dev # vite on the host
|
||||
make check
|
||||
```
|
||||
|
||||
`make env` creates `.env` and `docker-compose.override.yml` from their examples. `make recreate` starts the selected profiles, `make rebuild` builds the image first, `make logs`, `make restart` and `make down` do what they say.
|
||||
|
||||
To deploy, `git pull && make deploy`. It runs `make pre-deploy`, which rebuilds the image only when something in the repository changed, then `docker compose up -d`. A deploy system with its own `up` step calls `make pre-deploy` before it and `make post-deploy` after. What was built is remembered as `refs/deploy/*` in the clone.
|
||||
|
||||
`make dev` runs the Vite dev server on the host, `make fmt` formats, `make check` runs svelte-check and the linter.
|
||||
File renamed without changes.
@@ -0,0 +1,14 @@
|
||||
# Package managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
bun.lock
|
||||
bun.lockb
|
||||
|
||||
# Written by copier and the infra template, not by hand
|
||||
.copier/
|
||||
*.md
|
||||
renovate.jsonc
|
||||
|
||||
# Miscellaneous
|
||||
/static/
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }],
|
||||
"tailwindStylesheet": "./src/routes/layout.css"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { includeIgnoreFile } from "@eslint/compat";
|
||||
import js from "@eslint/js";
|
||||
import prettier from "eslint-config-prettier";
|
||||
import svelte from "eslint-plugin-svelte";
|
||||
import { defineConfig } from "eslint/config";
|
||||
import globals from "globals";
|
||||
import ts from "typescript-eslint";
|
||||
import svelteConfig from "./svelte.config.js";
|
||||
|
||||
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
|
||||
|
||||
export default defineConfig(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs.recommended,
|
||||
prettier,
|
||||
...svelte.configs.prettier,
|
||||
{
|
||||
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
||||
rules: {
|
||||
// typescript-eslint recommends against no-undef on TypeScript projects:
|
||||
// https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||
"no-undef": "off",
|
||||
},
|
||||
},
|
||||
{%- if ui_library == 'shadcn' %}
|
||||
{
|
||||
// shadcn-svelte primitives are copied in and changed upstream only.
|
||||
files: ["src/lib/components/ui/**"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"svelte/no-navigation-without-resolve": "off",
|
||||
},
|
||||
},
|
||||
{%- endif %}
|
||||
{
|
||||
files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
extraFileExtensions: [".svelte"],
|
||||
parser: ts.parser,
|
||||
projectService: true,
|
||||
svelteConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -1,20 +1,44 @@
|
||||
{% if frontend_adapter == 'bun' -%}
|
||||
FROM oven/bun:alpine
|
||||
FROM oven/bun:alpine AS deps
|
||||
|
||||
ENV TERM=xterm-256color
|
||||
ENV COLORTERM=truecolor
|
||||
ENV BUN_INSTALL_CACHE_DIR=/bun-cache
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock* ./
|
||||
|
||||
RUN bun install --frozen-lockfile
|
||||
RUN --mount=type=cache,target=/bun-cache bun install --frozen-lockfile
|
||||
|
||||
|
||||
# .env is in the context ($env/static/* are inlined at build) and stays in
|
||||
# this stage: the runtime image below gets build/ and production deps only.
|
||||
FROM deps AS builder
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN bun --bun svelte-kit sync
|
||||
RUN bun --bun run build
|
||||
|
||||
|
||||
FROM oven/bun:alpine AS runtime
|
||||
|
||||
ENV NODE_ENV=production
|
||||
# The cache mount only holds files; without the variable bun caches into
|
||||
# ~/.bun inside the image.
|
||||
ENV BUN_INSTALL_CACHE_DIR=/bun-cache
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock* ./
|
||||
|
||||
RUN --mount=type=cache,target=/bun-cache bun install --frozen-lockfile --production
|
||||
|
||||
COPY --from=builder /app/build ./build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["bun"]
|
||||
CMD ["build/index.js"]
|
||||
{%- else -%}
|
||||
|
||||
Reference in new issue
Block a user