├── .dockerignore ├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── README.zh-CN.md ├── cypress.config.ts ├── cypress ├── e2e │ └── basic.spec.ts └── tsconfig.json ├── eslint.config.js ├── index.html ├── locales ├── README.md ├── ar.yml ├── de.yml ├── en.yml ├── es.yml ├── fr.yml ├── id.yml ├── it.yml ├── ja.yml ├── ka.yml ├── ko.yml ├── pl.yml ├── pt-BR.yml ├── ru.yml ├── tr.yml ├── uk.yml ├── uz.yml ├── vi.yml └── zh-CN.yml ├── netlify.toml ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── public ├── _headers ├── favicon-dark.svg ├── favicon.svg ├── pwa-192x192.png ├── pwa-512x512.png └── safari-pinned-tab.svg ├── src ├── App.vue ├── auto-imports.d.ts ├── components.d.ts ├── components │ ├── README.md │ ├── TheCounter.vue │ ├── TheFooter.vue │ └── TheInput.vue ├── composables │ └── dark.ts ├── layouts │ ├── 404.vue │ ├── README.md │ ├── default.vue │ └── home.vue ├── main.ts ├── modules │ ├── README.md │ ├── i18n.ts │ ├── nprogress.ts │ ├── pinia.ts │ └── pwa.ts ├── pages │ ├── README.md │ ├── [...all].vue │ ├── about.md │ ├── hi │ │ └── [name].vue │ └── index.vue ├── shims.d.ts ├── stores │ └── user.ts ├── styles │ ├── main.css │ └── markdown.css ├── typed-router.d.ts └── types.ts ├── test ├── __snapshots__ │ └── component.test.ts.snap ├── basic.test.ts └── component.test.ts ├── tsconfig.json ├── uno.config.ts └── vite.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: antfu 2 | github: [antfu] 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: pnpm/action-setup@v3 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: lts/* 21 | cache: pnpm 22 | 23 | - name: Install 24 | run: pnpm install 25 | 26 | - name: Lint 27 | run: pnpm run lint 28 | 29 | typecheck: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: pnpm/action-setup@v3 34 | - uses: actions/setup-node@v4 35 | with: 36 | node-version: lts/* 37 | cache: pnpm 38 | 39 | - name: Install 40 | run: pnpm install 41 | 42 | - name: Typecheck 43 | run: pnpm run typecheck 44 | 45 | test: 46 | runs-on: ${{ matrix.os }} 47 | 48 | strategy: 49 | matrix: 50 | node-version: [18.x, 20.x] 51 | os: [ubuntu-latest] 52 | fail-fast: false 53 | 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: pnpm/action-setup@v3 57 | - name: Use Node.js ${{ matrix.node-version }} 58 | uses: actions/setup-node@v4 59 | with: 60 | node-version: ${{ matrix.node-version }} 61 | registry-url: https://registry.npmjs.org/ 62 | cache: pnpm 63 | 64 | - run: pnpm install 65 | - run: pnpm run test:unit 66 | 67 | test-e2e: 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v4 71 | - uses: actions/cache@v4 72 | with: 73 | path: | 74 | ~/.cache 75 | key: cypress-cache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} 76 | 77 | - uses: pnpm/action-setup@v3 78 | 79 | - name: Use Node.js 80 | uses: actions/setup-node@v4 81 | with: 82 | node-version: lts/* 83 | registry-url: https://registry.npmjs.org/ 84 | cache: pnpm 85 | 86 | - run: pnpm install 87 | 88 | - name: Cypress PNPM Patch 89 | run: cp pnpm-lock.yaml package-lock.json 90 | 91 | - name: Cypress 92 | uses: cypress-io/github-action@v6 93 | with: 94 | install-command: echo 95 | build: pnpm run build 96 | start: npx vite preview --port 3333 97 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vite-ssg-dist 3 | .vite-ssg-temp 4 | *.local 5 | dist 6 | dist-ssr 7 | node_modules 8 | .idea/ 9 | *.log 10 | cypress/downloads 11 | public/assets/fonts 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "antfu.iconify", 4 | "antfu.unocss", 5 | "antfu.vite", 6 | "antfu.goto-alias", 7 | "csstools.postcss", 8 | "dbaeumer.vscode-eslint", 9 | "vue.volar", 10 | "lokalise.i18n-ally", 11 | "streetsidesoftware.code-spell-checker" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["Vitesse", "Vite", "unocss", "vitest", "vueuse", "pinia", "demi", "antfu", "iconify", "intlify", "vitejs", "unplugin", "pnpm"], 3 | "i18n-ally.sourceLanguage": "en", 4 | "i18n-ally.keystyle": "nested", 5 | "i18n-ally.localesPaths": "locales", 6 | "i18n-ally.sortKeys": true, 7 | 8 | // Disable the default formatter 9 | "prettier.enable": false, 10 | "editor.formatOnSave": false, 11 | 12 | // Auto fix 13 | "editor.codeActionsOnSave": { 14 | "source.fixAll.eslint": "explicit", 15 | "source.organizeImports": "never" 16 | }, 17 | 18 | // Silent the stylistic rules in you IDE, but still auto fix them 19 | "eslint.rules.customizations": [ 20 | { "rule": "style/*", "severity": "off" }, 21 | { "rule": "format/*", "severity": "off" }, 22 | { "rule": "*-indent", "severity": "off" }, 23 | { "rule": "*-spacing", "severity": "off" }, 24 | { "rule": "*-spaces", "severity": "off" }, 25 | { "rule": "*-order", "severity": "off" }, 26 | { "rule": "*-dangle", "severity": "off" }, 27 | { "rule": "*-newline", "severity": "off" }, 28 | { "rule": "*quotes", "severity": "off" }, 29 | { "rule": "*semi", "severity": "off" } 30 | ], 31 | 32 | // The following is optional. 33 | // It's better to put under project setting `.vscode/settings.json` 34 | // to avoid conflicts with working with different eslint configs 35 | // that does not support all formats. 36 | "eslint.validate": [ 37 | "javascript", 38 | "javascriptreact", 39 | "typescript", 40 | "typescriptreact", 41 | "vue", 42 | "html", 43 | "markdown", 44 | "json", 45 | "jsonc", 46 | "yaml" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20-alpine AS build-stage 2 | 3 | WORKDIR /app 4 | RUN corepack enable 5 | 6 | COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml ./ 7 | RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store \ 8 | pnpm install --frozen-lockfile 9 | 10 | COPY . . 11 | RUN pnpm build 12 | 13 | FROM nginx:stable-alpine AS production-stage 14 | 15 | COPY --from=build-stage /app/dist /usr/share/nginx/html 16 | EXPOSE 80 17 | 18 | CMD ["nginx", "-g", "daemon off;"] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-PRESENT Anthony Fu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Vitesse - Opinionated Vite Starter Template 3 |

4 | 5 |

6 | Mocking up web app with Vitesse(speed)
7 |

8 | 9 |
10 | 11 |

12 | Live Demo 13 |

14 | 15 |
16 | 17 | > **Note**: This template is created during the early transition of Vue 3 and Vite. At this moment, if you are seeking for better Vue developer experience and more consistent maintenance, we recommend using [Nuxt 3](https://nuxt.com) instead (it also works perfectly with SPA or SSG as needed). This template still serves as a reference, but expect slower updates. 18 | 19 |
20 | 21 |

22 | English | 简体中文 23 | 24 |

25 | 26 |
27 | 28 | ## Features 29 | 30 | - ⚡️ [Vue 3](https://github.com/vuejs/core), [Vite](https://github.com/vitejs/vite), [pnpm](https://pnpm.io/), [esbuild](https://github.com/evanw/esbuild) - born with fastness 31 | 32 | - 🗂 [File based routing](./src/pages) 33 | 34 | - 📦 [Components auto importing](./src/components) 35 | 36 | - 🍍 [State Management via Pinia](https://pinia.vuejs.org/) 37 | 38 | - 📑 [Layout system](./src/layouts) 39 | 40 | - 📲 [PWA](https://github.com/antfu/vite-plugin-pwa) 41 | 42 | - 🎨 [UnoCSS](https://github.com/antfu/unocss) - the instant on-demand atomic CSS engine 43 | 44 | - 😃 [Use icons from any icon sets with classes](https://github.com/antfu/unocss/tree/main/packages/preset-icons) 45 | 46 | - 🌍 [I18n ready](./locales) 47 | 48 | - 🔎 [Component Preview](https://github.com/johnsoncodehk/vite-plugin-vue-component-preview) 49 | 50 | - 🗒 [Markdown Support](https://github.com/unplugin/unplugin-vue-markdown) 51 | 52 | - 🔥 Use the [new ` 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /locales/README.md: -------------------------------------------------------------------------------- 1 | ## i18n 2 | 3 | This directory is to serve your locale translation files. YAML under this folder would be loaded automatically and register with their filenames as locale code. 4 | 5 | Check out [`vue-i18n`](https://github.com/intlify/vue-i18n-next) for more details. 6 | 7 | If you are using VS Code, [`i18n Ally`](https://github.com/lokalise/i18n-ally) is recommended to make the i18n experience better. 8 | -------------------------------------------------------------------------------- /locales/ar.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: حول 3 | back: رجوع 4 | go: تجربة 5 | home: الرئيسية 6 | toggle_dark: التغيير إلى الوضع المظلم 7 | toggle_langs: تغيير اللغة 8 | intro: 9 | desc: vite مثال لتطبيق 10 | dynamic-route: عرض لتوجيهات ديناميكية 11 | hi: مرحبا {name} 12 | aka: معروف أيضا تحت مسمى 13 | whats-your-name: ما إسمك؟ 14 | not-found: صفحة غير موجودة 15 | -------------------------------------------------------------------------------- /locales/de.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Über 3 | back: Zurück 4 | go: Los 5 | home: Startseite 6 | toggle_dark: Dunkelmodus umschalten 7 | toggle_langs: Sprachen ändern 8 | intro: 9 | desc: Vite Startvorlage mit Vorlieben 10 | dynamic-route: Demo einer dynamischen Route 11 | hi: Hi, {name}! 12 | aka: Auch bekannt als 13 | whats-your-name: Wie heißt du? 14 | not-found: Nicht gefunden 15 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: About 3 | back: Back 4 | go: GO 5 | home: Home 6 | toggle_dark: Toggle dark mode 7 | toggle_langs: Change languages 8 | intro: 9 | desc: Opinionated Vite Starter Template 10 | dynamic-route: Demo of dynamic route 11 | hi: Hi, {name}! 12 | aka: Also known as 13 | whats-your-name: What's your name? 14 | not-found: Not found 15 | -------------------------------------------------------------------------------- /locales/es.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Acerca de 3 | back: Atrás 4 | go: Ir 5 | home: Inicio 6 | toggle_dark: Alternar modo oscuro 7 | toggle_langs: Cambiar idiomas 8 | intro: 9 | desc: Plantilla de Inicio de Vite Dogmática 10 | dynamic-route: Demo de ruta dinámica 11 | hi: ¡Hola, {name}! 12 | aka: También conocido como 13 | whats-your-name: ¿Cómo te llamas? 14 | not-found: No se ha encontrado 15 | -------------------------------------------------------------------------------- /locales/fr.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: À propos 3 | back: Retour 4 | go: Essayer 5 | home: Accueil 6 | toggle_dark: Basculer en mode sombre 7 | toggle_langs: Changer de langue 8 | intro: 9 | desc: Exemple d'application Vite 10 | dynamic-route: Démo de route dynamique 11 | hi: Salut, {name}! 12 | aka: Aussi connu sous le nom de 13 | whats-your-name: Comment t'appelles-tu ? 14 | not-found: Page non trouvée 15 | -------------------------------------------------------------------------------- /locales/id.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Tentang 3 | back: Kembali 4 | go: Pergi 5 | home: Beranda 6 | toggle_dark: Ubah ke mode gelap 7 | toggle_langs: Ubah bahasa 8 | intro: 9 | desc: Template awal vite 10 | dynamic-route: Contoh rute dinamik 11 | hi: Halo, {name}! 12 | aka: Juga diketahui sebagai 13 | whats-your-name: Siapa nama anda? 14 | not-found: Tidak ditemukan 15 | -------------------------------------------------------------------------------- /locales/it.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Su di me 3 | back: Indietro 4 | go: Vai 5 | home: Home 6 | toggle_dark: Attiva/disattiva modalità scura 7 | toggle_langs: Cambia lingua 8 | intro: 9 | desc: Modello per una Applicazione Vite 10 | dynamic-route: Demo di rotta dinamica 11 | hi: Ciao, {name}! 12 | whats-your-name: Come ti chiami? 13 | not-found: Non trovato 14 | -------------------------------------------------------------------------------- /locales/ja.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: これは? 3 | back: 戻る 4 | go: 進む 5 | home: ホーム 6 | toggle_dark: ダークモード切り替え 7 | toggle_langs: 言語切り替え 8 | intro: 9 | desc: 固執された Vite スターターテンプレート 10 | dynamic-route: 動的ルートのデモ 11 | hi: こんにちは、{name}! 12 | whats-your-name: 君の名は。 13 | not-found: 見つかりませんでした 14 | -------------------------------------------------------------------------------- /locales/ka.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: შესახებ 3 | back: უკან 4 | go: დაწყება 5 | home: მთავარი 6 | toggle_dark: გადართე მუქი რეჟიმი 7 | toggle_langs: ენის შეცვლა 8 | intro: 9 | desc: Opinionated Vite Starter Template 10 | dynamic-route: დინამიური როუტინგის დემო 11 | hi: გამარჯობა, {name}! 12 | aka: ასევე ცნობილი როგორც 13 | whats-your-name: რა გქვია? 14 | not-found: ვერ მოიძებნა 15 | -------------------------------------------------------------------------------- /locales/ko.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: 소개 3 | back: 뒤로가기 4 | go: 이동 5 | home: 홈 6 | toggle_dark: 다크모드 토글 7 | toggle_langs: 언어 변경 8 | intro: 9 | desc: Vite 애플리케이션 템플릿 10 | dynamic-route: 다이나믹 라우트 데모 11 | hi: 안녕, {name}! 12 | whats-your-name: 이름이 뭐예요? 13 | not-found: 찾을 수 없습니다 14 | -------------------------------------------------------------------------------- /locales/pl.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: O nas 3 | back: Wróć 4 | go: WEJDŹ 5 | home: Strona główna 6 | toggle_dark: Ustaw tryb nocny 7 | toggle_langs: Zmień język 8 | intro: 9 | desc: Opiniowany szablon startowy Vite 10 | dynamic-route: Demonstracja dynamicznego route 11 | hi: Cześć, {name}! 12 | aka: Znany też jako 13 | whats-your-name: Jak masz na imię? 14 | not-found: Nie znaleziono 15 | -------------------------------------------------------------------------------- /locales/pt-BR.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Sobre 3 | back: Voltar 4 | go: Ir 5 | home: Início 6 | toggle_dark: Alternar modo escuro 7 | toggle_langs: Mudar de idioma 8 | intro: 9 | desc: Modelo Opinativo de Partida de Vite 10 | dynamic-route: Demonstração de rota dinâmica 11 | hi: Olá, {name}! 12 | aka: Também conhecido como 13 | whats-your-name: Qual é o seu nome? 14 | not-found: Não encontrado 15 | -------------------------------------------------------------------------------- /locales/ru.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: О шаблоне 3 | back: Назад 4 | go: Перейти 5 | home: Главная 6 | toggle_dark: Включить темный режим 7 | toggle_langs: Сменить язык 8 | intro: 9 | desc: Самостоятельный начальный шаблон Vite 10 | dynamic-route: Демо динамического маршрута 11 | hi: Привет, {name}! 12 | whats-your-name: Как тебя зовут? 13 | not-found: Не найден 14 | -------------------------------------------------------------------------------- /locales/tr.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Hakkımda 3 | back: Geri 4 | go: İLERİ 5 | home: Anasayfa 6 | toggle_dark: Karanlık modu değiştir 7 | toggle_langs: Dilleri değiştir 8 | intro: 9 | desc: Görüşlü Vite Başlangıç Şablonu 10 | dynamic-route: Dinamik rota demosu 11 | hi: Merhaba, {name}! 12 | aka: Ayrıca şöyle bilinir 13 | whats-your-name: Adınız nedir? 14 | not-found: Bulunamadı 15 | -------------------------------------------------------------------------------- /locales/uk.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Про шаблон 3 | back: Назад 4 | go: Перейти 5 | home: Головна 6 | toggle_dark: Переключити темний режим 7 | toggle_langs: Змінити мову 8 | intro: 9 | desc: Самостійний початковий шаблон Vite 10 | dynamic-route: Демо динамічного маршруту 11 | hi: Привіт, {name}! 12 | whats-your-name: Як тебе звати? 13 | not-found: Не знайдено 14 | -------------------------------------------------------------------------------- /locales/uz.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Haqida 3 | back: Orqaga 4 | go: Kettik 5 | home: Bosh sahifa 6 | toggle_dark: Qorong‘i rejimga o‘tish 7 | toggle_langs: Tilni o‘zgartirish 8 | intro: 9 | desc: O‘ylangan boshlang‘ich Vite shabloni 10 | dynamic-route: Dynamic route demo'si 11 | hi: Assalomu alaykum, {name}! 12 | aka: shuningdek 13 | whats-your-name: Ismingiz nima? 14 | not-found: Topilmadi 15 | -------------------------------------------------------------------------------- /locales/vi.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: Về 3 | back: Quay lại 4 | go: Đi 5 | home: Khởi đầu 6 | toggle_dark: Chuyển đổi chế độ tối 7 | toggle_langs: Thay đổi ngôn ngữ 8 | intro: 9 | desc: Ý kiến cá nhân Vite Template để bắt đầu 10 | dynamic-route: Bản giới thiệu về dynamic route 11 | hi: Hi, {name}! 12 | whats-your-name: Tên bạn là gì? 13 | not-found: Không tìm thấy 14 | -------------------------------------------------------------------------------- /locales/zh-CN.yml: -------------------------------------------------------------------------------- 1 | button: 2 | about: 关于 3 | back: 返回 4 | go: 确定 5 | home: 首页 6 | toggle_dark: 切换深色模式 7 | toggle_langs: 切换语言 8 | intro: 9 | desc: 固执己见的 Vite 项目模板 10 | dynamic-route: 动态路由演示 11 | hi: 你好,{name} 12 | aka: 也叫 13 | whats-your-name: 输入你的名字 14 | not-found: 未找到页面 15 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "dist" 3 | command = "pnpm run build" 4 | 5 | [build.environment] 6 | NODE_VERSION = "20" 7 | 8 | [[redirects]] 9 | from = "/*" 10 | to = "/index.html" 11 | status = 200 12 | 13 | [[headers]] 14 | for = "/manifest.webmanifest" 15 | 16 | [headers.values] 17 | Content-Type = "application/manifest+json" 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "packageManager": "pnpm@10.7.0", 5 | "scripts": { 6 | "build": "vite-ssg build", 7 | "dev": "vite --port 3333 --open", 8 | "lint": "eslint .", 9 | "preview": "vite preview", 10 | "preview-https": "serve dist", 11 | "test": "vitest", 12 | "test:e2e": "cypress open", 13 | "test:unit": "vitest", 14 | "typecheck": "vue-tsc --noEmit", 15 | "up": "taze major -I", 16 | "postinstall": "npx simple-git-hooks", 17 | "sizecheck": "npx vite-bundle-visualizer" 18 | }, 19 | "dependencies": { 20 | "@unhead/vue": "catalog:frontend", 21 | "@unocss/reset": "catalog:frontend", 22 | "@vueuse/core": "catalog:frontend", 23 | "nprogress": "catalog:frontend", 24 | "pinia": "catalog:frontend", 25 | "vue": "catalog:frontend", 26 | "vue-i18n": "catalog:frontend", 27 | "vue-router": "catalog:frontend" 28 | }, 29 | "devDependencies": { 30 | "@antfu/eslint-config": "catalog:dev", 31 | "@iconify-json/carbon": "catalog:dev", 32 | "@intlify/unplugin-vue-i18n": "catalog:build", 33 | "@shikijs/markdown-it": "catalog:build", 34 | "@types/markdown-it-link-attributes": "catalog:types", 35 | "@types/nprogress": "catalog:types", 36 | "@unocss/eslint-config": "catalog:build", 37 | "@vitejs/plugin-vue": "catalog:build", 38 | "@vue-macros/volar": "catalog:dev", 39 | "@vue/test-utils": "catalog:dev", 40 | "beasties": "catalog:build", 41 | "cypress": "catalog:dev", 42 | "cypress-vite": "catalog:dev", 43 | "eslint": "catalog:dev", 44 | "eslint-plugin-cypress": "catalog:dev", 45 | "eslint-plugin-format": "catalog:dev", 46 | "https-localhost": "catalog:dev", 47 | "lint-staged": "catalog:dev", 48 | "markdown-it-link-attributes": "catalog:build", 49 | "rollup": "catalog:build", 50 | "shiki": "catalog:build", 51 | "simple-git-hooks": "catalog:dev", 52 | "taze": "catalog:dev", 53 | "typescript": "catalog:dev", 54 | "unocss": "catalog:build", 55 | "unplugin-auto-import": "catalog:build", 56 | "unplugin-vue-components": "catalog:build", 57 | "unplugin-vue-macros": "catalog:build", 58 | "unplugin-vue-markdown": "catalog:build", 59 | "unplugin-vue-router": "catalog:build", 60 | "vite": "catalog:build", 61 | "vite-bundle-visualizer": "catalog:build", 62 | "vite-plugin-inspect": "catalog:build", 63 | "vite-plugin-pwa": "catalog:build", 64 | "vite-plugin-vue-devtools": "catalog:build", 65 | "vite-plugin-vue-layouts": "catalog:build", 66 | "vite-ssg": "catalog:build", 67 | "vite-ssg-sitemap": "catalog:build", 68 | "vitest": "catalog:dev", 69 | "vue-tsc": "catalog:dev" 70 | }, 71 | "resolutions": { 72 | "unplugin": "catalog:build", 73 | "vite": "catalog:build", 74 | "vite-plugin-inspect": "catalog:build" 75 | }, 76 | "simple-git-hooks": { 77 | "pre-commit": "pnpm lint-staged" 78 | }, 79 | "lint-staged": { 80 | "*": "eslint --fix" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: [] 2 | 3 | catalogs: 4 | 5 | build: 6 | '@intlify/unplugin-vue-i18n': ^6.0.5 7 | '@shikijs/markdown-it': ^3.2.1 8 | '@unocss/eslint-config': ^66.1.0-beta.7 9 | '@vitejs/plugin-vue': ^5.2.3 10 | beasties: ^0.2.0 11 | markdown-it-link-attributes: ^4.0.1 12 | rollup: ^4.37.0 13 | shiki: ^3.2.1 14 | unocss: ^66.1.0-beta.7 15 | unplugin: ^2.2.2 16 | unplugin-auto-import: ^19.1.2 17 | unplugin-vue-components: ^28.4.1 18 | unplugin-vue-macros: ^2.14.5 19 | unplugin-vue-markdown: ^28.3.1 20 | unplugin-vue-router: ^0.12.0 21 | vite: ^6.2.3 22 | vite-bundle-visualizer: ^1.2.1 23 | vite-plugin-inspect: ^11.0.0 24 | vite-plugin-pwa: ^0.21.2 25 | vite-plugin-vue-devtools: ^7.7.2 26 | vite-plugin-vue-layouts: ^0.11.0 27 | vite-ssg: ^26.0.0 28 | vite-ssg-sitemap: ^0.8.1 29 | 30 | dev: 31 | '@antfu/eslint-config': ^4.11.0 32 | '@iconify-json/carbon': ^1.2.8 33 | '@vue-macros/volar': ^3.0.0-beta.7 34 | '@vue/test-utils': ^2.4.6 35 | cypress: ^14.2.1 36 | cypress-vite: ^1.6.0 37 | eslint: ^9.23.0 38 | eslint-plugin-cypress: ^4.2.0 39 | eslint-plugin-format: ^1.0.1 40 | https-localhost: ^4.7.1 41 | lint-staged: ^15.5.0 42 | simple-git-hooks: ^2.12.1 43 | taze: ^19.0.2 44 | typescript: ^5.8.2 45 | vitest: ^3.0.9 46 | vue-tsc: ^2.2.8 47 | 48 | frontend: 49 | '@unhead/vue': ^2.0.2 50 | '@unocss/reset': ^66.1.0-beta.7 51 | '@vueuse/core': ^13.0.0 52 | nprogress: ^0.2.0 53 | pinia: ^3.0.1 54 | vue: ^3.5.13 55 | vue-i18n: ^11.1.2 56 | vue-router: ^4.5.0 57 | 58 | types: 59 | '@types/markdown-it-link-attributes': ^3.0.5 60 | '@types/nprogress': ^0.2.3 61 | 62 | onlyBuiltDependencies: 63 | - cypress 64 | - esbuild 65 | - simple-git-hooks 66 | -------------------------------------------------------------------------------- /public/_headers: -------------------------------------------------------------------------------- 1 | /assets/* 2 | cache-control: max-age=31536000 3 | cache-control: immutable 4 | -------------------------------------------------------------------------------- /public/favicon-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu-collective/vitesse/309e68842bb621200533f2d4bad13948628feda0/public/pwa-192x192.png -------------------------------------------------------------------------------- /public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antfu-collective/vitesse/309e68842bb621200533f2d4bad13948628feda0/public/pwa-512x512.png -------------------------------------------------------------------------------- /public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/auto-imports.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // noinspection JSUnusedGlobalSymbols 5 | // Generated by unplugin-auto-import 6 | // biome-ignore lint: disable 7 | export {} 8 | declare global { 9 | const EffectScope: typeof import('vue')['EffectScope'] 10 | const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] 11 | const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] 12 | const computed: typeof import('vue')['computed'] 13 | const computedAsync: typeof import('@vueuse/core')['computedAsync'] 14 | const computedEager: typeof import('@vueuse/core')['computedEager'] 15 | const computedInject: typeof import('@vueuse/core')['computedInject'] 16 | const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] 17 | const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] 18 | const controlledRef: typeof import('@vueuse/core')['controlledRef'] 19 | const createApp: typeof import('vue')['createApp'] 20 | const createEventHook: typeof import('@vueuse/core')['createEventHook'] 21 | const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] 22 | const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] 23 | const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] 24 | const createRef: typeof import('@vueuse/core')['createRef'] 25 | const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] 26 | const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] 27 | const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] 28 | const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] 29 | const customRef: typeof import('vue')['customRef'] 30 | const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] 31 | const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] 32 | const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] 33 | const defineComponent: typeof import('vue')['defineComponent'] 34 | const defineLoader: typeof import('vue-router/auto')['defineLoader'] 35 | const definePage: typeof import('unplugin-vue-router/runtime')['definePage'] 36 | const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] 37 | const effectScope: typeof import('vue')['effectScope'] 38 | const extendRef: typeof import('@vueuse/core')['extendRef'] 39 | const getActiveHead: typeof import('@unhead/vue')['getActiveHead'] 40 | const getCurrentInstance: typeof import('vue')['getCurrentInstance'] 41 | const getCurrentScope: typeof import('vue')['getCurrentScope'] 42 | const h: typeof import('vue')['h'] 43 | const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] 44 | const inject: typeof import('vue')['inject'] 45 | const injectHead: typeof import('@unhead/vue')['injectHead'] 46 | const injectLocal: typeof import('@vueuse/core')['injectLocal'] 47 | const isDark: typeof import('./composables/dark')['isDark'] 48 | const isDefined: typeof import('@vueuse/core')['isDefined'] 49 | const isProxy: typeof import('vue')['isProxy'] 50 | const isReactive: typeof import('vue')['isReactive'] 51 | const isReadonly: typeof import('vue')['isReadonly'] 52 | const isRef: typeof import('vue')['isRef'] 53 | const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] 54 | const markRaw: typeof import('vue')['markRaw'] 55 | const nextTick: typeof import('vue')['nextTick'] 56 | const onActivated: typeof import('vue')['onActivated'] 57 | const onBeforeMount: typeof import('vue')['onBeforeMount'] 58 | const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] 59 | const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] 60 | const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] 61 | const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] 62 | const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] 63 | const onDeactivated: typeof import('vue')['onDeactivated'] 64 | const onElementRemoval: typeof import('@vueuse/core')['onElementRemoval'] 65 | const onErrorCaptured: typeof import('vue')['onErrorCaptured'] 66 | const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] 67 | const onLongPress: typeof import('@vueuse/core')['onLongPress'] 68 | const onMounted: typeof import('vue')['onMounted'] 69 | const onRenderTracked: typeof import('vue')['onRenderTracked'] 70 | const onRenderTriggered: typeof import('vue')['onRenderTriggered'] 71 | const onScopeDispose: typeof import('vue')['onScopeDispose'] 72 | const onServerPrefetch: typeof import('vue')['onServerPrefetch'] 73 | const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] 74 | const onUnmounted: typeof import('vue')['onUnmounted'] 75 | const onUpdated: typeof import('vue')['onUpdated'] 76 | const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] 77 | const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] 78 | const preferredDark: typeof import('./composables/dark')['preferredDark'] 79 | const provide: typeof import('vue')['provide'] 80 | const provideLocal: typeof import('@vueuse/core')['provideLocal'] 81 | const reactify: typeof import('@vueuse/core')['reactify'] 82 | const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] 83 | const reactive: typeof import('vue')['reactive'] 84 | const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] 85 | const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] 86 | const reactivePick: typeof import('@vueuse/core')['reactivePick'] 87 | const readonly: typeof import('vue')['readonly'] 88 | const ref: typeof import('vue')['ref'] 89 | const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] 90 | const refDebounced: typeof import('@vueuse/core')['refDebounced'] 91 | const refDefault: typeof import('@vueuse/core')['refDefault'] 92 | const refThrottled: typeof import('@vueuse/core')['refThrottled'] 93 | const refWithControl: typeof import('@vueuse/core')['refWithControl'] 94 | const resolveComponent: typeof import('vue')['resolveComponent'] 95 | const resolveRef: typeof import('@vueuse/core')['resolveRef'] 96 | const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] 97 | const shallowReactive: typeof import('vue')['shallowReactive'] 98 | const shallowReadonly: typeof import('vue')['shallowReadonly'] 99 | const shallowRef: typeof import('vue')['shallowRef'] 100 | const syncRef: typeof import('@vueuse/core')['syncRef'] 101 | const syncRefs: typeof import('@vueuse/core')['syncRefs'] 102 | const templateRef: typeof import('@vueuse/core')['templateRef'] 103 | const throttledRef: typeof import('@vueuse/core')['throttledRef'] 104 | const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] 105 | const toRaw: typeof import('vue')['toRaw'] 106 | const toReactive: typeof import('@vueuse/core')['toReactive'] 107 | const toRef: typeof import('vue')['toRef'] 108 | const toRefs: typeof import('vue')['toRefs'] 109 | const toValue: typeof import('vue')['toValue'] 110 | const toggleDark: typeof import('./composables/dark')['toggleDark'] 111 | const triggerRef: typeof import('vue')['triggerRef'] 112 | const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] 113 | const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] 114 | const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] 115 | const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] 116 | const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] 117 | const unref: typeof import('vue')['unref'] 118 | const unrefElement: typeof import('@vueuse/core')['unrefElement'] 119 | const until: typeof import('@vueuse/core')['until'] 120 | const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] 121 | const useAnimate: typeof import('@vueuse/core')['useAnimate'] 122 | const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] 123 | const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] 124 | const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] 125 | const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] 126 | const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] 127 | const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] 128 | const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes'] 129 | const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] 130 | const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] 131 | const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] 132 | const useArraySome: typeof import('@vueuse/core')['useArraySome'] 133 | const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique'] 134 | const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] 135 | const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] 136 | const useAttrs: typeof import('vue')['useAttrs'] 137 | const useBase64: typeof import('@vueuse/core')['useBase64'] 138 | const useBattery: typeof import('@vueuse/core')['useBattery'] 139 | const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] 140 | const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] 141 | const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] 142 | const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] 143 | const useCached: typeof import('@vueuse/core')['useCached'] 144 | const useClipboard: typeof import('@vueuse/core')['useClipboard'] 145 | const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] 146 | const useCloned: typeof import('@vueuse/core')['useCloned'] 147 | const useColorMode: typeof import('@vueuse/core')['useColorMode'] 148 | const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] 149 | const useCountdown: typeof import('@vueuse/core')['useCountdown'] 150 | const useCounter: typeof import('@vueuse/core')['useCounter'] 151 | const useCssModule: typeof import('vue')['useCssModule'] 152 | const useCssVar: typeof import('@vueuse/core')['useCssVar'] 153 | const useCssVars: typeof import('vue')['useCssVars'] 154 | const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] 155 | const useCycleList: typeof import('@vueuse/core')['useCycleList'] 156 | const useDark: typeof import('@vueuse/core')['useDark'] 157 | const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] 158 | const useDebounce: typeof import('@vueuse/core')['useDebounce'] 159 | const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] 160 | const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] 161 | const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] 162 | const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] 163 | const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] 164 | const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] 165 | const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] 166 | const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] 167 | const useDraggable: typeof import('@vueuse/core')['useDraggable'] 168 | const useDropZone: typeof import('@vueuse/core')['useDropZone'] 169 | const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] 170 | const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] 171 | const useElementHover: typeof import('@vueuse/core')['useElementHover'] 172 | const useElementSize: typeof import('@vueuse/core')['useElementSize'] 173 | const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] 174 | const useEventBus: typeof import('@vueuse/core')['useEventBus'] 175 | const useEventListener: typeof import('@vueuse/core')['useEventListener'] 176 | const useEventSource: typeof import('@vueuse/core')['useEventSource'] 177 | const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] 178 | const useFavicon: typeof import('@vueuse/core')['useFavicon'] 179 | const useFetch: typeof import('@vueuse/core')['useFetch'] 180 | const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] 181 | const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] 182 | const useFocus: typeof import('@vueuse/core')['useFocus'] 183 | const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] 184 | const useFps: typeof import('@vueuse/core')['useFps'] 185 | const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] 186 | const useGamepad: typeof import('@vueuse/core')['useGamepad'] 187 | const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] 188 | const useHead: typeof import('@unhead/vue')['useHead'] 189 | const useHeadSafe: typeof import('@unhead/vue')['useHeadSafe'] 190 | const useI18n: typeof import('vue-i18n')['useI18n'] 191 | const useId: typeof import('vue')['useId'] 192 | const useIdle: typeof import('@vueuse/core')['useIdle'] 193 | const useImage: typeof import('@vueuse/core')['useImage'] 194 | const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] 195 | const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] 196 | const useInterval: typeof import('@vueuse/core')['useInterval'] 197 | const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] 198 | const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] 199 | const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] 200 | const useLink: typeof import('vue-router/auto')['useLink'] 201 | const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] 202 | const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] 203 | const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] 204 | const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] 205 | const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] 206 | const useMemoize: typeof import('@vueuse/core')['useMemoize'] 207 | const useMemory: typeof import('@vueuse/core')['useMemory'] 208 | const useModel: typeof import('vue')['useModel'] 209 | const useMounted: typeof import('@vueuse/core')['useMounted'] 210 | const useMouse: typeof import('@vueuse/core')['useMouse'] 211 | const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] 212 | const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] 213 | const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] 214 | const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] 215 | const useNetwork: typeof import('@vueuse/core')['useNetwork'] 216 | const useNow: typeof import('@vueuse/core')['useNow'] 217 | const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] 218 | const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] 219 | const useOnline: typeof import('@vueuse/core')['useOnline'] 220 | const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] 221 | const useParallax: typeof import('@vueuse/core')['useParallax'] 222 | const useParentElement: typeof import('@vueuse/core')['useParentElement'] 223 | const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] 224 | const usePermission: typeof import('@vueuse/core')['usePermission'] 225 | const usePointer: typeof import('@vueuse/core')['usePointer'] 226 | const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] 227 | const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] 228 | const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] 229 | const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] 230 | const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] 231 | const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] 232 | const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] 233 | const usePreferredReducedTransparency: typeof import('@vueuse/core')['usePreferredReducedTransparency'] 234 | const usePrevious: typeof import('@vueuse/core')['usePrevious'] 235 | const useRafFn: typeof import('@vueuse/core')['useRafFn'] 236 | const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] 237 | const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] 238 | const useRoute: typeof import('vue-router')['useRoute'] 239 | const useRouter: typeof import('vue-router')['useRouter'] 240 | const useSSRWidth: typeof import('@vueuse/core')['useSSRWidth'] 241 | const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] 242 | const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] 243 | const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] 244 | const useScroll: typeof import('@vueuse/core')['useScroll'] 245 | const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] 246 | const useSeoMeta: typeof import('@unhead/vue')['useSeoMeta'] 247 | const useServerHead: typeof import('@unhead/vue')['useServerHead'] 248 | const useServerHeadSafe: typeof import('@unhead/vue')['useServerHeadSafe'] 249 | const useServerSeoMeta: typeof import('@unhead/vue')['useServerSeoMeta'] 250 | const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] 251 | const useShare: typeof import('@vueuse/core')['useShare'] 252 | const useSlots: typeof import('vue')['useSlots'] 253 | const useSorted: typeof import('@vueuse/core')['useSorted'] 254 | const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] 255 | const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] 256 | const useStepper: typeof import('@vueuse/core')['useStepper'] 257 | const useStorage: typeof import('@vueuse/core')['useStorage'] 258 | const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] 259 | const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] 260 | const useSupported: typeof import('@vueuse/core')['useSupported'] 261 | const useSwipe: typeof import('@vueuse/core')['useSwipe'] 262 | const useTemplateRef: typeof import('vue')['useTemplateRef'] 263 | const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] 264 | const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] 265 | const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] 266 | const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] 267 | const useThrottle: typeof import('@vueuse/core')['useThrottle'] 268 | const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] 269 | const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] 270 | const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] 271 | const useTimeout: typeof import('@vueuse/core')['useTimeout'] 272 | const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] 273 | const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] 274 | const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] 275 | const useTitle: typeof import('@vueuse/core')['useTitle'] 276 | const useToNumber: typeof import('@vueuse/core')['useToNumber'] 277 | const useToString: typeof import('@vueuse/core')['useToString'] 278 | const useToggle: typeof import('@vueuse/core')['useToggle'] 279 | const useTransition: typeof import('@vueuse/core')['useTransition'] 280 | const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] 281 | const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] 282 | const useUserStore: typeof import('./stores/user')['useUserStore'] 283 | const useVModel: typeof import('@vueuse/core')['useVModel'] 284 | const useVModels: typeof import('@vueuse/core')['useVModels'] 285 | const useVibrate: typeof import('@vueuse/core')['useVibrate'] 286 | const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] 287 | const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] 288 | const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] 289 | const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] 290 | const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] 291 | const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] 292 | const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] 293 | const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] 294 | const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] 295 | const watch: typeof import('vue')['watch'] 296 | const watchArray: typeof import('@vueuse/core')['watchArray'] 297 | const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] 298 | const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] 299 | const watchDeep: typeof import('@vueuse/core')['watchDeep'] 300 | const watchEffect: typeof import('vue')['watchEffect'] 301 | const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] 302 | const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] 303 | const watchOnce: typeof import('@vueuse/core')['watchOnce'] 304 | const watchPausable: typeof import('@vueuse/core')['watchPausable'] 305 | const watchPostEffect: typeof import('vue')['watchPostEffect'] 306 | const watchSyncEffect: typeof import('vue')['watchSyncEffect'] 307 | const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] 308 | const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] 309 | const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] 310 | const whenever: typeof import('@vueuse/core')['whenever'] 311 | } 312 | // for type re-export 313 | declare global { 314 | // @ts-ignore 315 | export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' 316 | import('vue') 317 | } 318 | 319 | // for vue template auto import 320 | import { UnwrapRef } from 'vue' 321 | declare module 'vue' { 322 | interface GlobalComponents {} 323 | interface ComponentCustomProperties { 324 | readonly EffectScope: UnwrapRef 325 | readonly asyncComputed: UnwrapRef 326 | readonly autoResetRef: UnwrapRef 327 | readonly computed: UnwrapRef 328 | readonly computedAsync: UnwrapRef 329 | readonly computedEager: UnwrapRef 330 | readonly computedInject: UnwrapRef 331 | readonly computedWithControl: UnwrapRef 332 | readonly controlledComputed: UnwrapRef 333 | readonly controlledRef: UnwrapRef 334 | readonly createApp: UnwrapRef 335 | readonly createEventHook: UnwrapRef 336 | readonly createGlobalState: UnwrapRef 337 | readonly createInjectionState: UnwrapRef 338 | readonly createReactiveFn: UnwrapRef 339 | readonly createRef: UnwrapRef 340 | readonly createReusableTemplate: UnwrapRef 341 | readonly createSharedComposable: UnwrapRef 342 | readonly createTemplatePromise: UnwrapRef 343 | readonly createUnrefFn: UnwrapRef 344 | readonly customRef: UnwrapRef 345 | readonly debouncedRef: UnwrapRef 346 | readonly debouncedWatch: UnwrapRef 347 | readonly defineAsyncComponent: UnwrapRef 348 | readonly defineComponent: UnwrapRef 349 | readonly eagerComputed: UnwrapRef 350 | readonly effectScope: UnwrapRef 351 | readonly extendRef: UnwrapRef 352 | readonly getCurrentInstance: UnwrapRef 353 | readonly getCurrentScope: UnwrapRef 354 | readonly h: UnwrapRef 355 | readonly ignorableWatch: UnwrapRef 356 | readonly inject: UnwrapRef 357 | readonly injectHead: UnwrapRef 358 | readonly injectLocal: UnwrapRef 359 | readonly isDark: UnwrapRef 360 | readonly isDefined: UnwrapRef 361 | readonly isProxy: UnwrapRef 362 | readonly isReactive: UnwrapRef 363 | readonly isReadonly: UnwrapRef 364 | readonly isRef: UnwrapRef 365 | readonly makeDestructurable: UnwrapRef 366 | readonly markRaw: UnwrapRef 367 | readonly nextTick: UnwrapRef 368 | readonly onActivated: UnwrapRef 369 | readonly onBeforeMount: UnwrapRef 370 | readonly onBeforeRouteLeave: UnwrapRef 371 | readonly onBeforeRouteUpdate: UnwrapRef 372 | readonly onBeforeUnmount: UnwrapRef 373 | readonly onBeforeUpdate: UnwrapRef 374 | readonly onClickOutside: UnwrapRef 375 | readonly onDeactivated: UnwrapRef 376 | readonly onElementRemoval: UnwrapRef 377 | readonly onErrorCaptured: UnwrapRef 378 | readonly onKeyStroke: UnwrapRef 379 | readonly onLongPress: UnwrapRef 380 | readonly onMounted: UnwrapRef 381 | readonly onRenderTracked: UnwrapRef 382 | readonly onRenderTriggered: UnwrapRef 383 | readonly onScopeDispose: UnwrapRef 384 | readonly onServerPrefetch: UnwrapRef 385 | readonly onStartTyping: UnwrapRef 386 | readonly onUnmounted: UnwrapRef 387 | readonly onUpdated: UnwrapRef 388 | readonly onWatcherCleanup: UnwrapRef 389 | readonly pausableWatch: UnwrapRef 390 | readonly preferredDark: UnwrapRef 391 | readonly provide: UnwrapRef 392 | readonly provideLocal: UnwrapRef 393 | readonly reactify: UnwrapRef 394 | readonly reactifyObject: UnwrapRef 395 | readonly reactive: UnwrapRef 396 | readonly reactiveComputed: UnwrapRef 397 | readonly reactiveOmit: UnwrapRef 398 | readonly reactivePick: UnwrapRef 399 | readonly readonly: UnwrapRef 400 | readonly ref: UnwrapRef 401 | readonly refAutoReset: UnwrapRef 402 | readonly refDebounced: UnwrapRef 403 | readonly refDefault: UnwrapRef 404 | readonly refThrottled: UnwrapRef 405 | readonly refWithControl: UnwrapRef 406 | readonly resolveComponent: UnwrapRef 407 | readonly resolveRef: UnwrapRef 408 | readonly resolveUnref: UnwrapRef 409 | readonly shallowReactive: UnwrapRef 410 | readonly shallowReadonly: UnwrapRef 411 | readonly shallowRef: UnwrapRef 412 | readonly syncRef: UnwrapRef 413 | readonly syncRefs: UnwrapRef 414 | readonly templateRef: UnwrapRef 415 | readonly throttledRef: UnwrapRef 416 | readonly throttledWatch: UnwrapRef 417 | readonly toRaw: UnwrapRef 418 | readonly toReactive: UnwrapRef 419 | readonly toRef: UnwrapRef 420 | readonly toRefs: UnwrapRef 421 | readonly toValue: UnwrapRef 422 | readonly toggleDark: UnwrapRef 423 | readonly triggerRef: UnwrapRef 424 | readonly tryOnBeforeMount: UnwrapRef 425 | readonly tryOnBeforeUnmount: UnwrapRef 426 | readonly tryOnMounted: UnwrapRef 427 | readonly tryOnScopeDispose: UnwrapRef 428 | readonly tryOnUnmounted: UnwrapRef 429 | readonly unref: UnwrapRef 430 | readonly unrefElement: UnwrapRef 431 | readonly until: UnwrapRef 432 | readonly useActiveElement: UnwrapRef 433 | readonly useAnimate: UnwrapRef 434 | readonly useArrayDifference: UnwrapRef 435 | readonly useArrayEvery: UnwrapRef 436 | readonly useArrayFilter: UnwrapRef 437 | readonly useArrayFind: UnwrapRef 438 | readonly useArrayFindIndex: UnwrapRef 439 | readonly useArrayFindLast: UnwrapRef 440 | readonly useArrayIncludes: UnwrapRef 441 | readonly useArrayJoin: UnwrapRef 442 | readonly useArrayMap: UnwrapRef 443 | readonly useArrayReduce: UnwrapRef 444 | readonly useArraySome: UnwrapRef 445 | readonly useArrayUnique: UnwrapRef 446 | readonly useAsyncQueue: UnwrapRef 447 | readonly useAsyncState: UnwrapRef 448 | readonly useAttrs: UnwrapRef 449 | readonly useBase64: UnwrapRef 450 | readonly useBattery: UnwrapRef 451 | readonly useBluetooth: UnwrapRef 452 | readonly useBreakpoints: UnwrapRef 453 | readonly useBroadcastChannel: UnwrapRef 454 | readonly useBrowserLocation: UnwrapRef 455 | readonly useCached: UnwrapRef 456 | readonly useClipboard: UnwrapRef 457 | readonly useClipboardItems: UnwrapRef 458 | readonly useCloned: UnwrapRef 459 | readonly useColorMode: UnwrapRef 460 | readonly useConfirmDialog: UnwrapRef 461 | readonly useCountdown: UnwrapRef 462 | readonly useCounter: UnwrapRef 463 | readonly useCssModule: UnwrapRef 464 | readonly useCssVar: UnwrapRef 465 | readonly useCssVars: UnwrapRef 466 | readonly useCurrentElement: UnwrapRef 467 | readonly useCycleList: UnwrapRef 468 | readonly useDark: UnwrapRef 469 | readonly useDateFormat: UnwrapRef 470 | readonly useDebounce: UnwrapRef 471 | readonly useDebounceFn: UnwrapRef 472 | readonly useDebouncedRefHistory: UnwrapRef 473 | readonly useDeviceMotion: UnwrapRef 474 | readonly useDeviceOrientation: UnwrapRef 475 | readonly useDevicePixelRatio: UnwrapRef 476 | readonly useDevicesList: UnwrapRef 477 | readonly useDisplayMedia: UnwrapRef 478 | readonly useDocumentVisibility: UnwrapRef 479 | readonly useDraggable: UnwrapRef 480 | readonly useDropZone: UnwrapRef 481 | readonly useElementBounding: UnwrapRef 482 | readonly useElementByPoint: UnwrapRef 483 | readonly useElementHover: UnwrapRef 484 | readonly useElementSize: UnwrapRef 485 | readonly useElementVisibility: UnwrapRef 486 | readonly useEventBus: UnwrapRef 487 | readonly useEventListener: UnwrapRef 488 | readonly useEventSource: UnwrapRef 489 | readonly useEyeDropper: UnwrapRef 490 | readonly useFavicon: UnwrapRef 491 | readonly useFetch: UnwrapRef 492 | readonly useFileDialog: UnwrapRef 493 | readonly useFileSystemAccess: UnwrapRef 494 | readonly useFocus: UnwrapRef 495 | readonly useFocusWithin: UnwrapRef 496 | readonly useFps: UnwrapRef 497 | readonly useFullscreen: UnwrapRef 498 | readonly useGamepad: UnwrapRef 499 | readonly useGeolocation: UnwrapRef 500 | readonly useHead: UnwrapRef 501 | readonly useHeadSafe: UnwrapRef 502 | readonly useI18n: UnwrapRef 503 | readonly useId: UnwrapRef 504 | readonly useIdle: UnwrapRef 505 | readonly useImage: UnwrapRef 506 | readonly useInfiniteScroll: UnwrapRef 507 | readonly useIntersectionObserver: UnwrapRef 508 | readonly useInterval: UnwrapRef 509 | readonly useIntervalFn: UnwrapRef 510 | readonly useKeyModifier: UnwrapRef 511 | readonly useLastChanged: UnwrapRef 512 | readonly useLink: UnwrapRef 513 | readonly useLocalStorage: UnwrapRef 514 | readonly useMagicKeys: UnwrapRef 515 | readonly useManualRefHistory: UnwrapRef 516 | readonly useMediaControls: UnwrapRef 517 | readonly useMediaQuery: UnwrapRef 518 | readonly useMemoize: UnwrapRef 519 | readonly useMemory: UnwrapRef 520 | readonly useModel: UnwrapRef 521 | readonly useMounted: UnwrapRef 522 | readonly useMouse: UnwrapRef 523 | readonly useMouseInElement: UnwrapRef 524 | readonly useMousePressed: UnwrapRef 525 | readonly useMutationObserver: UnwrapRef 526 | readonly useNavigatorLanguage: UnwrapRef 527 | readonly useNetwork: UnwrapRef 528 | readonly useNow: UnwrapRef 529 | readonly useObjectUrl: UnwrapRef 530 | readonly useOffsetPagination: UnwrapRef 531 | readonly useOnline: UnwrapRef 532 | readonly usePageLeave: UnwrapRef 533 | readonly useParallax: UnwrapRef 534 | readonly useParentElement: UnwrapRef 535 | readonly usePerformanceObserver: UnwrapRef 536 | readonly usePermission: UnwrapRef 537 | readonly usePointer: UnwrapRef 538 | readonly usePointerLock: UnwrapRef 539 | readonly usePointerSwipe: UnwrapRef 540 | readonly usePreferredColorScheme: UnwrapRef 541 | readonly usePreferredContrast: UnwrapRef 542 | readonly usePreferredDark: UnwrapRef 543 | readonly usePreferredLanguages: UnwrapRef 544 | readonly usePreferredReducedMotion: UnwrapRef 545 | readonly usePreferredReducedTransparency: UnwrapRef 546 | readonly usePrevious: UnwrapRef 547 | readonly useRafFn: UnwrapRef 548 | readonly useRefHistory: UnwrapRef 549 | readonly useResizeObserver: UnwrapRef 550 | readonly useRoute: UnwrapRef 551 | readonly useRouter: UnwrapRef 552 | readonly useSSRWidth: UnwrapRef 553 | readonly useScreenOrientation: UnwrapRef 554 | readonly useScreenSafeArea: UnwrapRef 555 | readonly useScriptTag: UnwrapRef 556 | readonly useScroll: UnwrapRef 557 | readonly useScrollLock: UnwrapRef 558 | readonly useSeoMeta: UnwrapRef 559 | readonly useServerHead: UnwrapRef 560 | readonly useServerHeadSafe: UnwrapRef 561 | readonly useServerSeoMeta: UnwrapRef 562 | readonly useSessionStorage: UnwrapRef 563 | readonly useShare: UnwrapRef 564 | readonly useSlots: UnwrapRef 565 | readonly useSorted: UnwrapRef 566 | readonly useSpeechRecognition: UnwrapRef 567 | readonly useSpeechSynthesis: UnwrapRef 568 | readonly useStepper: UnwrapRef 569 | readonly useStorage: UnwrapRef 570 | readonly useStorageAsync: UnwrapRef 571 | readonly useStyleTag: UnwrapRef 572 | readonly useSupported: UnwrapRef 573 | readonly useSwipe: UnwrapRef 574 | readonly useTemplateRef: UnwrapRef 575 | readonly useTemplateRefsList: UnwrapRef 576 | readonly useTextDirection: UnwrapRef 577 | readonly useTextSelection: UnwrapRef 578 | readonly useTextareaAutosize: UnwrapRef 579 | readonly useThrottle: UnwrapRef 580 | readonly useThrottleFn: UnwrapRef 581 | readonly useThrottledRefHistory: UnwrapRef 582 | readonly useTimeAgo: UnwrapRef 583 | readonly useTimeout: UnwrapRef 584 | readonly useTimeoutFn: UnwrapRef 585 | readonly useTimeoutPoll: UnwrapRef 586 | readonly useTimestamp: UnwrapRef 587 | readonly useTitle: UnwrapRef 588 | readonly useToNumber: UnwrapRef 589 | readonly useToString: UnwrapRef 590 | readonly useToggle: UnwrapRef 591 | readonly useTransition: UnwrapRef 592 | readonly useUrlSearchParams: UnwrapRef 593 | readonly useUserMedia: UnwrapRef 594 | readonly useUserStore: UnwrapRef 595 | readonly useVModel: UnwrapRef 596 | readonly useVModels: UnwrapRef 597 | readonly useVibrate: UnwrapRef 598 | readonly useVirtualList: UnwrapRef 599 | readonly useWakeLock: UnwrapRef 600 | readonly useWebNotification: UnwrapRef 601 | readonly useWebSocket: UnwrapRef 602 | readonly useWebWorker: UnwrapRef 603 | readonly useWebWorkerFn: UnwrapRef 604 | readonly useWindowFocus: UnwrapRef 605 | readonly useWindowScroll: UnwrapRef 606 | readonly useWindowSize: UnwrapRef 607 | readonly watch: UnwrapRef 608 | readonly watchArray: UnwrapRef 609 | readonly watchAtMost: UnwrapRef 610 | readonly watchDebounced: UnwrapRef 611 | readonly watchDeep: UnwrapRef 612 | readonly watchEffect: UnwrapRef 613 | readonly watchIgnorable: UnwrapRef 614 | readonly watchImmediate: UnwrapRef 615 | readonly watchOnce: UnwrapRef 616 | readonly watchPausable: UnwrapRef 617 | readonly watchPostEffect: UnwrapRef 618 | readonly watchSyncEffect: UnwrapRef 619 | readonly watchThrottled: UnwrapRef 620 | readonly watchTriggerable: UnwrapRef 621 | readonly watchWithFilter: UnwrapRef 622 | readonly whenever: UnwrapRef 623 | } 624 | } -------------------------------------------------------------------------------- /src/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | export {} 6 | 7 | /* prettier-ignore */ 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | README: typeof import('./components/README.md')['default'] 11 | RouterLink: typeof import('vue-router')['RouterLink'] 12 | RouterView: typeof import('vue-router')['RouterView'] 13 | TheCounter: typeof import('./components/TheCounter.vue')['default'] 14 | TheFooter: typeof import('./components/TheFooter.vue')['default'] 15 | TheInput: typeof import('./components/TheInput.vue')['default'] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/README.md: -------------------------------------------------------------------------------- 1 | ## Components 2 | 3 | Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components). 4 | 5 | ### Icons 6 | 7 | You can use icons from almost any icon sets by the power of [Iconify](https://iconify.design/). 8 | 9 | It will only bundle the icons you use. Check out [`unplugin-icons`](https://github.com/antfu/unplugin-icons) for more details. 10 | -------------------------------------------------------------------------------- /src/components/TheCounter.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /src/components/TheFooter.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 38 | -------------------------------------------------------------------------------- /src/components/TheInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /src/composables/dark.ts: -------------------------------------------------------------------------------- 1 | // these APIs are auto-imported from @vueuse/core 2 | export const isDark = useDark() 3 | export const toggleDark = useToggle(isDark) 4 | export const preferredDark = usePreferredDark() 5 | -------------------------------------------------------------------------------- /src/layouts/404.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 22 | -------------------------------------------------------------------------------- /src/layouts/README.md: -------------------------------------------------------------------------------- 1 | ## Layouts 2 | 3 | Vue components in this dir are used as layouts. 4 | 5 | By default, `default.vue` will be used unless an alternative is specified in the route meta. 6 | 7 | With [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) and [`vite-plugin-vue-layouts`](https://github.com/JohnCampionJr/vite-plugin-vue-layouts), you can specify the layout in the page's SFCs like this: 8 | 9 | ```vue 10 | 11 | meta: 12 | layout: home 13 | 14 | ``` 15 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/layouts/home.vue: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import type { UserModule } from './types' 2 | 3 | import { setupLayouts } from 'virtual:generated-layouts' 4 | import { ViteSSG } from 'vite-ssg' 5 | import { routes } from 'vue-router/auto-routes' 6 | import App from './App.vue' 7 | 8 | import '@unocss/reset/tailwind.css' 9 | import './styles/main.css' 10 | import 'uno.css' 11 | 12 | // https://github.com/antfu/vite-ssg 13 | export const createApp = ViteSSG( 14 | App, 15 | { 16 | routes: setupLayouts(routes), 17 | base: import.meta.env.BASE_URL, 18 | }, 19 | (ctx) => { 20 | // install all modules under `modules/` 21 | Object.values(import.meta.glob<{ install: UserModule }>('./modules/*.ts', { eager: true })) 22 | .forEach(i => i.install?.(ctx)) 23 | // ctx.app.use(Previewer) 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /src/modules/README.md: -------------------------------------------------------------------------------- 1 | ## Modules 2 | 3 | A custom user module system. Place a `.ts` file with the following template, it will be installed automatically. 4 | 5 | ```ts 6 | import type { UserModule } from '~/types' 7 | 8 | export const install: UserModule = ({ app, router, isClient }) => { 9 | // do something 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /src/modules/i18n.ts: -------------------------------------------------------------------------------- 1 | import type { Locale } from 'vue-i18n' 2 | import type { UserModule } from '~/types' 3 | import { createI18n } from 'vue-i18n' 4 | 5 | // Import i18n resources 6 | // https://vitejs.dev/guide/features.html#glob-import 7 | // 8 | // Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite 9 | const i18n = createI18n({ 10 | legacy: false, 11 | locale: '', 12 | messages: {}, 13 | }) 14 | 15 | const localesMap = Object.fromEntries( 16 | Object.entries(import.meta.glob('../../locales/*.yml')) 17 | .map(([path, loadLocale]) => [path.match(/([\w-]*)\.yml$/)?.[1], loadLocale]), 18 | ) as Record Promise<{ default: Record }>> 19 | 20 | export const availableLocales = Object.keys(localesMap) 21 | 22 | const loadedLanguages: string[] = [] 23 | 24 | function setI18nLanguage(lang: Locale) { 25 | i18n.global.locale.value = lang as any 26 | if (typeof document !== 'undefined') 27 | document.querySelector('html')?.setAttribute('lang', lang) 28 | return lang 29 | } 30 | 31 | export async function loadLanguageAsync(lang: string): Promise { 32 | // If the same language 33 | if (i18n.global.locale.value === lang) 34 | return setI18nLanguage(lang) 35 | 36 | // If the language was already loaded 37 | if (loadedLanguages.includes(lang)) 38 | return setI18nLanguage(lang) 39 | 40 | // If the language hasn't been loaded yet 41 | const messages = await localesMap[lang]() 42 | i18n.global.setLocaleMessage(lang, messages.default) 43 | loadedLanguages.push(lang) 44 | return setI18nLanguage(lang) 45 | } 46 | 47 | export const install: UserModule = ({ app }) => { 48 | app.use(i18n) 49 | loadLanguageAsync('en') 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/nprogress.ts: -------------------------------------------------------------------------------- 1 | import type { UserModule } from '~/types' 2 | import NProgress from 'nprogress' 3 | 4 | export const install: UserModule = ({ isClient, router }) => { 5 | if (isClient) { 6 | router.beforeEach((to, from) => { 7 | if (to.path !== from.path) 8 | NProgress.start() 9 | }) 10 | router.afterEach(() => { 11 | NProgress.done() 12 | }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/pinia.ts: -------------------------------------------------------------------------------- 1 | import type { UserModule } from '~/types' 2 | import { createPinia } from 'pinia' 3 | 4 | // Setup Pinia 5 | // https://pinia.vuejs.org/ 6 | export const install: UserModule = ({ isClient, initialState, app }) => { 7 | const pinia = createPinia() 8 | app.use(pinia) 9 | // Refer to 10 | // https://github.com/antfu/vite-ssg/blob/main/README.md#state-serialization 11 | // for other serialization strategies. 12 | if (isClient) 13 | pinia.state.value = (initialState.pinia) || {} 14 | 15 | else 16 | initialState.pinia = pinia.state.value 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/pwa.ts: -------------------------------------------------------------------------------- 1 | import type { UserModule } from '~/types' 2 | 3 | // https://github.com/antfu/vite-plugin-pwa#automatic-reload-when-new-content-available 4 | export const install: UserModule = ({ isClient, router }) => { 5 | if (!isClient) 6 | return 7 | 8 | router.isReady() 9 | .then(async () => { 10 | const { registerSW } = await import('virtual:pwa-register') 11 | registerSW({ immediate: true }) 12 | }) 13 | .catch(() => {}) 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/README.md: -------------------------------------------------------------------------------- 1 | ## File-based Routing 2 | 3 | Routes will be auto-generated for Vue files in this dir with the same file structure. 4 | Check out [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) for more details. 5 | 6 | ### Path Aliasing 7 | 8 | `~/` is aliased to `./src/` folder. 9 | 10 | For example, instead of having 11 | 12 | ```ts 13 | import { isDark } from '../../../../composables' 14 | ``` 15 | 16 | now, you can use 17 | 18 | ```ts 19 | import { isDark } from '~/composables' 20 | ``` 21 | -------------------------------------------------------------------------------- /src/pages/[...all].vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | meta: 13 | layout: 404 14 | 15 | -------------------------------------------------------------------------------- /src/pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About 3 | --- 4 | 5 | 9 | 10 |
11 | 12 |
13 |

{{ t('button.about') }}

14 |
15 | 16 | [Vitesse](https://github.com/antfu/vitesse) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **UnoCSS** for styling and icons. 17 | 18 | ```js 19 | // syntax highlighting example 20 | function vitesse() { 21 | const foo = 'bar' 22 | console.log(foo) 23 | } 24 | ``` 25 | 26 | Check out the [GitHub repo](https://github.com/antfu/vitesse) for more details. 27 | -------------------------------------------------------------------------------- /src/pages/hi/[name].vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 51 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 55 | 56 | 57 | meta: 58 | layout: home 59 | 60 | -------------------------------------------------------------------------------- /src/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Window { 2 | // extend the window 3 | } 4 | 5 | // with unplugin-vue-markdown, markdown files can be treated as Vue components 6 | declare module '*.md' { 7 | import type { DefineComponent } from 'vue' 8 | 9 | const component: DefineComponent 10 | export default component 11 | } 12 | 13 | declare module '*.vue' { 14 | import type { DefineComponent } from 'vue' 15 | 16 | const component: DefineComponent 17 | export default component 18 | } 19 | -------------------------------------------------------------------------------- /src/stores/user.ts: -------------------------------------------------------------------------------- 1 | import { acceptHMRUpdate, defineStore } from 'pinia' 2 | 3 | export const useUserStore = defineStore('user', () => { 4 | /** 5 | * Current name of the user. 6 | */ 7 | const savedName = ref('') 8 | const previousNames = ref(new Set()) 9 | 10 | const usedNames = computed(() => Array.from(previousNames.value)) 11 | const otherNames = computed(() => usedNames.value.filter(name => name !== savedName.value)) 12 | 13 | /** 14 | * Changes the current name of the user and saves the one that was used 15 | * before. 16 | * 17 | * @param name - new name to set 18 | */ 19 | function setNewName(name: string) { 20 | if (savedName.value) 21 | previousNames.value.add(savedName.value) 22 | 23 | savedName.value = name 24 | } 25 | 26 | return { 27 | setNewName, 28 | otherNames, 29 | savedName, 30 | } 31 | }) 32 | 33 | if (import.meta.hot) 34 | import.meta.hot.accept(acceptHMRUpdate(useUserStore as any, import.meta.hot)) 35 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | @import './markdown.css'; 2 | 3 | html, 4 | body, 5 | #app { 6 | height: 100%; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | html.dark { 12 | background: #121212; 13 | color-scheme: dark; 14 | } 15 | 16 | #nprogress { 17 | pointer-events: none; 18 | } 19 | 20 | #nprogress .bar { 21 | background: rgb(13, 148, 136); 22 | opacity: 0.75; 23 | position: fixed; 24 | z-index: 1031; 25 | top: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 2px; 29 | } 30 | -------------------------------------------------------------------------------- /src/styles/markdown.css: -------------------------------------------------------------------------------- 1 | .prose pre:not(.shiki) { 2 | padding: 0; 3 | } 4 | 5 | .prose .shiki { 6 | font-family: 'DM Mono', monospace; 7 | font-size: 1.2em; 8 | line-height: 1.4; 9 | } 10 | 11 | .prose img { 12 | width: 100%; 13 | } 14 | 15 | .shiki, 16 | .shiki span { 17 | color: var(--shiki-light); 18 | background: var(--shiki-light-bg); 19 | } 20 | 21 | html.dark .shiki, 22 | html.dark .shiki span { 23 | color: var(--shiki-dark); 24 | background: var(--shiki-dark-bg); 25 | } 26 | -------------------------------------------------------------------------------- /src/typed-router.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️ 5 | // It's recommended to commit this file. 6 | // Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry. 7 | 8 | declare module 'vue-router/auto-routes' { 9 | import type { 10 | RouteRecordInfo, 11 | ParamValue, 12 | ParamValueOneOrMore, 13 | ParamValueZeroOrMore, 14 | ParamValueZeroOrOne, 15 | } from 'vue-router' 16 | 17 | /** 18 | * Route name map generated by unplugin-vue-router 19 | */ 20 | export interface RouteNamedMap { 21 | '/': RouteRecordInfo<'/', '/', Record, Record>, 22 | '/[...all]': RouteRecordInfo<'/[...all]', '/:all(.*)', { all: ParamValue }, { all: ParamValue }>, 23 | '/about': RouteRecordInfo<'/about', '/about', Record, Record>, 24 | '/hi/[name]': RouteRecordInfo<'/hi/[name]', '/hi/:name', { name: ParamValue }, { name: ParamValue }>, 25 | '/README': RouteRecordInfo<'/README', '/README', Record, Record>, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ViteSSGContext } from 'vite-ssg' 2 | 3 | export type UserModule = (ctx: ViteSSGContext) => void 4 | -------------------------------------------------------------------------------- /test/__snapshots__/component.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`component TheCounter.vue > should render 1`] = `"
10
"`; 4 | -------------------------------------------------------------------------------- /test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe('tests', () => { 4 | it('should works', () => { 5 | expect(1 + 1).toEqual(2) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /test/component.test.ts: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { describe, expect, it } from 'vitest' 3 | import TheCounter from '../src/components/TheCounter.vue' 4 | 5 | describe('component TheCounter.vue', () => { 6 | it('should render', () => { 7 | const wrapper = mount(TheCounter, { props: { initial: 10 } }) 8 | expect(wrapper.text()).toContain('10') 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | 12 | it('should be interactive', async () => { 13 | const wrapper = mount(TheCounter, { props: { initial: 0 } }) 14 | expect(wrapper.text()).toContain('0') 15 | 16 | expect(wrapper.find('.inc').exists()).toBe(true) 17 | 18 | expect(wrapper.find('.dec').exists()).toBe(true) 19 | 20 | await wrapper.get('.inc').trigger('click') 21 | 22 | expect(wrapper.text()).toContain('1') 23 | 24 | await wrapper.get('.dec').trigger('click') 25 | 26 | expect(wrapper.text()).toContain('0') 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "jsx": "preserve", 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": ".", 7 | "module": "ESNext", 8 | "moduleResolution": "Bundler", 9 | "paths": { 10 | "~/*": ["src/*"] 11 | }, 12 | "resolveJsonModule": true, 13 | "types": [ 14 | "vitest", 15 | "vite/client", 16 | "vite-plugin-vue-layouts/client", 17 | "vite-plugin-pwa/client", 18 | "unplugin-vue-macros/macros-global", 19 | "unplugin-vue-router/client" 20 | ], 21 | "allowJs": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "noUnusedLocals": true, 25 | "noEmit": true, 26 | "esModuleInterop": true, 27 | "forceConsistentCasingInFileNames": true, 28 | "isolatedModules": true, 29 | "skipLibCheck": true 30 | }, 31 | "vueCompilerOptions": { 32 | "plugins": [ 33 | "@vue-macros/volar/define-models", 34 | "@vue-macros/volar/define-slots" 35 | ] 36 | }, 37 | "exclude": ["dist", "node_modules", "cypress"] 38 | } 39 | -------------------------------------------------------------------------------- /uno.config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createLocalFontProcessor, 3 | } from '@unocss/preset-web-fonts/local' 4 | import { 5 | defineConfig, 6 | presetAttributify, 7 | presetIcons, 8 | presetTypography, 9 | presetUno, 10 | presetWebFonts, 11 | transformerDirectives, 12 | transformerVariantGroup, 13 | } from 'unocss' 14 | 15 | export default defineConfig({ 16 | shortcuts: [ 17 | ['btn', 'px-4 py-1 rounded inline-block bg-teal-700 text-white cursor-pointer !outline-none hover:bg-teal-800 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'], 18 | ['icon-btn', 'inline-block cursor-pointer select-none opacity-75 transition duration-200 ease-in-out hover:opacity-100 hover:text-teal-600'], 19 | ], 20 | presets: [ 21 | presetUno(), 22 | presetAttributify(), 23 | presetIcons({ 24 | scale: 1.2, 25 | }), 26 | presetTypography(), 27 | presetWebFonts({ 28 | fonts: { 29 | sans: 'DM Sans', 30 | serif: 'DM Serif Display', 31 | mono: 'DM Mono', 32 | }, 33 | processors: createLocalFontProcessor(), 34 | }), 35 | ], 36 | transformers: [ 37 | transformerDirectives(), 38 | transformerVariantGroup(), 39 | ], 40 | safelist: 'prose prose-sm m-auto text-left'.split(' '), 41 | }) 42 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import VueI18n from '@intlify/unplugin-vue-i18n/vite' 3 | import Shiki from '@shikijs/markdown-it' 4 | import { unheadVueComposablesImports } from '@unhead/vue' 5 | import Vue from '@vitejs/plugin-vue' 6 | import LinkAttributes from 'markdown-it-link-attributes' 7 | import Unocss from 'unocss/vite' 8 | import AutoImport from 'unplugin-auto-import/vite' 9 | import Components from 'unplugin-vue-components/vite' 10 | import VueMacros from 'unplugin-vue-macros/vite' 11 | import Markdown from 'unplugin-vue-markdown/vite' 12 | import { VueRouterAutoImports } from 'unplugin-vue-router' 13 | import VueRouter from 'unplugin-vue-router/vite' 14 | import { defineConfig } from 'vite' 15 | import { VitePWA } from 'vite-plugin-pwa' 16 | import VueDevTools from 'vite-plugin-vue-devtools' 17 | import Layouts from 'vite-plugin-vue-layouts' 18 | import generateSitemap from 'vite-ssg-sitemap' 19 | import 'vitest/config' 20 | 21 | export default defineConfig({ 22 | resolve: { 23 | alias: { 24 | '~/': `${path.resolve(__dirname, 'src')}/`, 25 | }, 26 | }, 27 | 28 | plugins: [ 29 | VueMacros({ 30 | plugins: { 31 | vue: Vue({ 32 | include: [/\.vue$/, /\.md$/], 33 | }), 34 | }, 35 | }), 36 | 37 | // https://github.com/posva/unplugin-vue-router 38 | VueRouter({ 39 | extensions: ['.vue', '.md'], 40 | dts: 'src/typed-router.d.ts', 41 | }), 42 | 43 | // https://github.com/JohnCampionJr/vite-plugin-vue-layouts 44 | Layouts(), 45 | 46 | // https://github.com/antfu/unplugin-auto-import 47 | AutoImport({ 48 | include: [/\.[jt]sx?$/, /\.vue$/, /\.vue\?vue/, /\.md$/], 49 | imports: [ 50 | 'vue', 51 | 'vue-i18n', 52 | '@vueuse/core', 53 | unheadVueComposablesImports, 54 | VueRouterAutoImports, 55 | { 56 | // add any other imports you were relying on 57 | 'vue-router/auto': ['useLink'], 58 | }, 59 | ], 60 | dts: 'src/auto-imports.d.ts', 61 | dirs: [ 62 | 'src/composables', 63 | 'src/stores', 64 | ], 65 | vueTemplate: true, 66 | }), 67 | 68 | // https://github.com/antfu/unplugin-vue-components 69 | Components({ 70 | // allow auto load markdown components under `./src/components/` 71 | extensions: ['vue', 'md'], 72 | // allow auto import and register components used in markdown 73 | include: [/\.vue$/, /\.vue\?vue/, /\.md$/], 74 | dts: 'src/components.d.ts', 75 | }), 76 | 77 | // https://github.com/antfu/unocss 78 | // see uno.config.ts for config 79 | Unocss(), 80 | 81 | // https://github.com/unplugin/unplugin-vue-markdown 82 | // Don't need this? Try vitesse-lite: https://github.com/antfu/vitesse-lite 83 | Markdown({ 84 | wrapperClasses: 'prose prose-sm m-auto text-left', 85 | headEnabled: true, 86 | async markdownItSetup(md) { 87 | md.use(LinkAttributes, { 88 | matcher: (link: string) => /^https?:\/\//.test(link), 89 | attrs: { 90 | target: '_blank', 91 | rel: 'noopener', 92 | }, 93 | }) 94 | md.use(await Shiki({ 95 | defaultColor: false, 96 | themes: { 97 | light: 'vitesse-light', 98 | dark: 'vitesse-dark', 99 | }, 100 | })) 101 | }, 102 | }), 103 | 104 | // https://github.com/antfu/vite-plugin-pwa 105 | VitePWA({ 106 | registerType: 'autoUpdate', 107 | includeAssets: ['favicon.svg', 'safari-pinned-tab.svg'], 108 | manifest: { 109 | name: 'Vitesse', 110 | short_name: 'Vitesse', 111 | theme_color: '#ffffff', 112 | icons: [ 113 | { 114 | src: '/pwa-192x192.png', 115 | sizes: '192x192', 116 | type: 'image/png', 117 | }, 118 | { 119 | src: '/pwa-512x512.png', 120 | sizes: '512x512', 121 | type: 'image/png', 122 | }, 123 | { 124 | src: '/pwa-512x512.png', 125 | sizes: '512x512', 126 | type: 'image/png', 127 | purpose: 'any maskable', 128 | }, 129 | ], 130 | }, 131 | }), 132 | 133 | // https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n 134 | VueI18n({ 135 | runtimeOnly: true, 136 | compositionOnly: true, 137 | fullInstall: true, 138 | include: [path.resolve(__dirname, 'locales/**')], 139 | }), 140 | 141 | // https://github.com/webfansplz/vite-plugin-vue-devtools 142 | VueDevTools(), 143 | ], 144 | 145 | // https://github.com/vitest-dev/vitest 146 | test: { 147 | include: ['test/**/*.test.ts'], 148 | environment: 'jsdom', 149 | }, 150 | 151 | // https://github.com/antfu/vite-ssg 152 | ssgOptions: { 153 | script: 'async', 154 | formatting: 'minify', 155 | beastiesOptions: { 156 | reduceInlineStyles: false, 157 | }, 158 | onFinished() { 159 | generateSitemap() 160 | }, 161 | }, 162 | 163 | ssr: { 164 | // TODO: workaround until they support native ESM 165 | noExternal: ['workbox-window', /vue-i18n/], 166 | }, 167 | }) 168 | --------------------------------------------------------------------------------