├── .github └── FUNDING.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── functions ├── .env ├── .env.production ├── api │ ├── demo.ts │ ├── error.ts │ └── users │ │ └── [id].ts ├── index.ts └── props │ ├── hi-name.ts │ └── home.ts ├── index.html ├── logo.png ├── package-lock.json ├── package.json ├── public ├── favicon.svg ├── pwa-192x192.png ├── pwa-512x512.png ├── robots.txt └── safari-pinned-tab.svg ├── src ├── App.vue ├── components │ ├── Footer.vue │ └── README.md ├── i18n │ ├── date-formats.ts │ ├── index.ts │ ├── locales.ts │ ├── number-formats.ts │ └── translations │ │ ├── README.md │ │ ├── en.json │ │ ├── es.json │ │ ├── fr.json │ │ ├── it.json │ │ ├── ja.json │ │ ├── ko.json │ │ ├── tr.json │ │ ├── vi.json │ │ └── zh-CN.json ├── layouts │ ├── 404.vue │ ├── README.md │ ├── default.vue │ └── home.vue ├── main.ts ├── modules │ ├── README.md │ └── nprogress.ts ├── pages │ ├── README.md │ ├── [...all].vue │ ├── about.md │ ├── hi │ │ └── [name].vue │ └── index.vue ├── shims.d.ts └── styles │ ├── main.css │ └── markdown.css ├── tailwind.config.ts ├── tsconfig.json ├── vite.config.ts ├── wrangler.toml └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: antfu 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.local 3 | dist 4 | dist-ssr 5 | node_modules 6 | **/_* 7 | worker-site/worker 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules/** 2 | /.cache/** 3 | /dist/** 4 | /tests/unit/coverage/** 5 | **/.temp/** -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | trailingComma: es5 -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "johnsoncodehk.volar", 4 | "lokalise.i18n-ally", 5 | "antfu.iconify", 6 | "dbaeumer.vscode-eslint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "volar.tsPlugin": true, 3 | "i18n-ally.localesPaths": "src/i18n/translations", 4 | "i18n-ally.keystyle": "nested", 5 | "i18n-ally.sortKeys": true, 6 | "cSpell.words": [ 7 | "Vitesse" 8 | ], 9 | "typescript.tsdk": "node_modules/typescript/lib", 10 | "volar.tsPluginStatus": false 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anthony Fu & Fran Dios 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 | # Vitessedge 2 | 3 |

4 | Vitessedge 5 |

6 | 7 |

8 | Deploy your fullstack SSR apps to Cloudflare Workers using Vitedge. 9 |

10 | 11 |

12 | Live Demo 13 |

14 | 15 | > Vue + Vite + SSR template based on [@antfu](https://github.com/antfu)'s [Vitesse](https://github.com/antfu/vitesse) 16 | 17 | ## Features 18 | 19 | - ⚡️ [Vue 3](https://github.com/vuejs/vue-next), [Vite 2](https://github.com/vitejs/vite), [ESBuild](https://github.com/evanw/esbuild) - born with fastness 20 | 21 | - ⚔️ Edge-side rendering in Cloudflare Workers via [Vitedge](https://github.com/frandiox/vitedge), with edge cache and HTTP/2 server push 22 | 23 | - 🗂 [File based routing](./src/pages) 24 | 25 | - 📦 [Components auto importing](./src/components) 26 | 27 | - 📑 [Layout system](./src/layouts) 28 | 29 | - 📲 [PWA](https://github.com/antfu/vite-plugin-pwa) 30 | 31 | - 🎨 [Windi CSS](https://github.com/windicss/windicss) - on-demand Tailwind CSS with speed 32 | 33 | - 😃 [Use icons from any icon sets, with no compromise](./src/components) 34 | 35 | - 🌍 [I18n ready](./src/i18n/translations) with different routes for each language. 36 | 37 | - 🗒 [Markdown Support](https://github.com/antfu/vite-plugin-md) 38 | 39 | - 🔥 Use the [new ` 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frandiox/vitessedge-template/072d2607caf57d6a4d65a11e0244db39a9c9500b/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "private": true, 4 | "type": "module", 5 | "main": "dist/worker/script.js", 6 | "engines": { 7 | "node": ">=14.17" 8 | }, 9 | "scripts": { 10 | "dev": "vitedge --port 3333 --open --ssr", 11 | "dev:spa": "vitedge --port 3333 --open", 12 | "build": "rm -rf dist && vitedge build", 13 | "preview": "vitedge preview", 14 | "preview:watch": "vitedge preview --build-watch", 15 | "preview:wrangler": "wrangler dev", 16 | "deploy": "wrangler publish" 17 | }, 18 | "dependencies": { 19 | "@vueuse/core": "^5.2.0", 20 | "@vueuse/head": "^0.6.0", 21 | "nprogress": "^0.2.0", 22 | "prism-theme-vars": "^0.2.2", 23 | "vitedge": "0.18.x", 24 | "vue": "3.2.26", 25 | "vue-i18n": "^9.1.7", 26 | "vue-router": "^4.0.10" 27 | }, 28 | "devDependencies": { 29 | "@antfu/eslint-config": "^0.7.0", 30 | "@cloudflare/workers-types": "^2.2.2", 31 | "@iconify/json": "^1.1.380", 32 | "@intlify/vite-plugin-vue-i18n": "^2.4.0", 33 | "@types/nprogress": "^0.2.0", 34 | "@typescript-eslint/eslint-plugin": "^4.28.5", 35 | "@vitejs/plugin-vue": "2.0.1", 36 | "@vue/compiler-sfc": "3.2.26", 37 | "@vue/server-renderer": "3.2.26", 38 | "cross-env": "^7.0.3", 39 | "esbuild": "^0.12.7", 40 | "eslint": "^7.31.0", 41 | "markdown-it-prism": "^2.1.8", 42 | "miniflare": "^1.3.3", 43 | "node-fetch": "^2.6.1", 44 | "rimraf": "^3.0.2", 45 | "ts-node": "^10.4.0", 46 | "typescript": "^4.3.5", 47 | "vite": "^2.7.0", 48 | "vite-plugin-components": "^0.13.2", 49 | "vite-plugin-icons": "^0.6.5", 50 | "vite-plugin-md": "^0.9.0", 51 | "vite-plugin-pages": "^0.15.1", 52 | "vite-plugin-pwa": "^0.8.2", 53 | "vite-plugin-vue-layouts": "^0.3.1", 54 | "vite-plugin-windicss": "^1.2.7" 55 | }, 56 | "eslintConfig": { 57 | "extends": "@antfu/eslint-config", 58 | "rules": { 59 | "no-unused-vars": "off", 60 | "no-console": "off", 61 | "comma-dangle": "off", 62 | "brace-style": "off", 63 | "@typescript-eslint/no-unused-vars": "off" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frandiox/vitessedge-template/072d2607caf57d6a4d65a11e0244db39a9c9500b/public/pwa-192x192.png -------------------------------------------------------------------------------- /public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frandiox/vitessedge-template/072d2607caf57d6a4d65a11e0244db39a9c9500b/public/pwa-512x512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / 3 | -------------------------------------------------------------------------------- /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 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 93 | -------------------------------------------------------------------------------- /src/components/README.md: -------------------------------------------------------------------------------- 1 | ## Components 2 | 3 | Components in this dir will be auto-registered and on-demand, powered by [`vite-plugin-components`](https://github.com/antfu/vite-plugin-components). 4 | 5 | 6 | ### Icons 7 | 8 | You can uses icons from almost any icon sets by the power of [Iconify](https://iconify.design/). 9 | 10 | It will only bundles the icons you use, check out [vite-plugin-icons](https://github.com/antfu/vite-plugin-icons) for more details. 11 | -------------------------------------------------------------------------------- /src/i18n/date-formats.ts: -------------------------------------------------------------------------------- 1 | import { SUPPORTED_LOCALES } from './locales' 2 | 3 | const DEFAULT_FORMAT = { 4 | short: { 5 | year: 'numeric', 6 | month: 'short', 7 | day: 'numeric', 8 | }, 9 | medium: { 10 | year: 'numeric', 11 | month: 'long', 12 | day: 'numeric', 13 | hour: 'numeric', 14 | minute: 'numeric', 15 | }, 16 | long: { 17 | year: 'numeric', 18 | month: 'long', 19 | day: 'numeric', 20 | weekday: 'long', 21 | hour: 'numeric', 22 | minute: 'numeric', 23 | }, 24 | month: { 25 | month: 'long', 26 | }, 27 | } 28 | 29 | export const DATE_FORMATS = Object.freeze({ 30 | ...SUPPORTED_LOCALES.reduce( 31 | (acc, l) => ({ ...acc, [l]: DEFAULT_FORMAT }), 32 | {} 33 | ), 34 | // Overwrite formats here for specific locales 35 | }) 36 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue' 2 | import { createI18n } from 'vue-i18n' 3 | import { DATE_FORMATS } from './date-formats' 4 | import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from './locales' 5 | import { NUMBER_FORMATS } from './number-formats' 6 | 7 | export { 8 | DEFAULT_LOCALE, 9 | SUPPORTED_LOCALES, 10 | SUPPORTED_LANGUAGES, 11 | extractLocaleFromPath, 12 | } from './locales' 13 | 14 | // This is a dynamic import so not all languages are bundled in frontend. 15 | // For YAML format, install `@rollup/plugin-yaml`. 16 | const messageImports = import.meta.glob('./translations/*.json') 17 | 18 | function importLocale(locale: string) { 19 | const [, importLocale] = 20 | Object.entries(messageImports).find(([key]) => 21 | key.includes(`/${locale}.`) 22 | ) || [] 23 | 24 | return importLocale && importLocale() 25 | } 26 | 27 | export async function loadAsyncLanguage(i18n: any, locale = DEFAULT_LOCALE) { 28 | try { 29 | const result = await importLocale(locale) 30 | if (result) { 31 | i18n.setLocaleMessage(locale, result.default || result) 32 | i18n.locale.value = locale 33 | } 34 | } catch (error) { 35 | console.error(error) 36 | } 37 | } 38 | 39 | export async function installI18n(app: App, locale = '') { 40 | locale = SUPPORTED_LOCALES.includes(locale) ? locale : DEFAULT_LOCALE 41 | const messages = await importLocale(locale) 42 | 43 | const i18n = createI18n({ 44 | legacy: false, 45 | locale, 46 | fallbackLocale: DEFAULT_LOCALE, 47 | messages: { 48 | [locale]: messages.default || messages, 49 | }, 50 | datetimeFormats: DATE_FORMATS, 51 | numberFormats: NUMBER_FORMATS, 52 | }) 53 | 54 | app.use(i18n) 55 | } 56 | -------------------------------------------------------------------------------- /src/i18n/locales.ts: -------------------------------------------------------------------------------- 1 | export const SUPPORTED_LANGUAGES = [ 2 | { 3 | locale: 'en', 4 | name: 'English', 5 | default: true, 6 | }, 7 | { 8 | locale: 'es', 9 | name: 'Spanish', 10 | }, 11 | { 12 | locale: 'fr', 13 | name: 'French', 14 | }, 15 | { 16 | locale: 'it', 17 | name: 'Italian', 18 | }, 19 | { 20 | locale: 'ja', 21 | name: 'Japanese', 22 | }, 23 | { 24 | locale: 'ko', 25 | name: 'Korean', 26 | }, 27 | { 28 | locale: 'tr', 29 | name: 'Turkish', 30 | }, 31 | { 32 | locale: 'vi', 33 | name: 'Vietnamese', 34 | }, 35 | { 36 | locale: 'zh-CN', 37 | name: 'Chinese', 38 | }, 39 | ] 40 | 41 | export const SUPPORTED_LOCALES = SUPPORTED_LANGUAGES.map((l) => l.locale) 42 | 43 | export const DEFAULT_LANGUAGE = SUPPORTED_LANGUAGES.find((l) => l.default) 44 | 45 | export const DEFAULT_LOCALE = DEFAULT_LANGUAGE?.locale as string 46 | 47 | export function extractLocaleFromPath(path = '') { 48 | const [_, maybeLocale] = path.split('/') 49 | return SUPPORTED_LOCALES.includes(maybeLocale) ? maybeLocale : DEFAULT_LOCALE 50 | } 51 | -------------------------------------------------------------------------------- /src/i18n/number-formats.ts: -------------------------------------------------------------------------------- 1 | import { SUPPORTED_LOCALES } from './locales' 2 | 3 | const DEFAULT_FORMAT = { 4 | USD: { 5 | style: 'currency', 6 | currency: 'USD', 7 | }, 8 | EUR: { 9 | style: 'currency', 10 | currency: 'EUR', 11 | }, 12 | JPY: { 13 | style: 'currency', 14 | currency: 'JPY', 15 | }, 16 | CAD: { 17 | style: 'currency', 18 | currency: 'CAD', 19 | }, 20 | AUD: { 21 | style: 'currency', 22 | currency: 'AUD', 23 | }, 24 | SGD: { 25 | style: 'currency', 26 | currency: 'SGD', 27 | }, 28 | GBP: { 29 | style: 'currency', 30 | currency: 'GBP', 31 | }, 32 | decimal: { 33 | style: 'decimal', 34 | }, 35 | } 36 | 37 | export const NUMBER_FORMATS = Object.freeze({ 38 | ...SUPPORTED_LOCALES.reduce( 39 | (acc, l) => ({ ...acc, [l]: DEFAULT_FORMAT }), 40 | {} 41 | ), 42 | // Overwrite formats here for specific locales 43 | }) 44 | -------------------------------------------------------------------------------- /src/i18n/translations/README.md: -------------------------------------------------------------------------------- 1 | ## i18n 2 | 3 | This directory is to serve your locale translation files. JSON under this folder would be loaded automatically and register with their filenames as locale code. If you prefer YAML format, install `@rollup/plugin-yaml` and add it to `vite.config.ts`. 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 | -------------------------------------------------------------------------------- /src/i18n/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "About", 4 | "back": "Back", 5 | "go": "GO", 6 | "home": "Home", 7 | "toggle_dark": "Toggle dark mode", 8 | "toggle_langs": "Change languages" 9 | }, 10 | "intro": { 11 | "desc": "Opinionated Vite Starter Template", 12 | "dynamic-route": "Demo of dynamic route", 13 | "hi": "Hi, {name}!", 14 | "whats-your-name": "What's your name?" 15 | }, 16 | "not-found": "Not found" 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "Acerca de", 4 | "back": "Atrás", 5 | "go": "Ir", 6 | "home": "Inicio", 7 | "toggle_dark": "Alternar modo oscuro", 8 | "toggle_langs": "Cambiar idiomas" 9 | }, 10 | "intro": { 11 | "desc": "Plantilla de Inicio de Vite Dogmática", 12 | "dynamic-route": "Demo de ruta dinámica", 13 | "hi": "¡Hola, {name}!", 14 | "whats-your-name": "¿Cómo te llamas?" 15 | }, 16 | "not-found": "No se ha encontrado" 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "À propos de", 4 | "back": "Retour", 5 | "go": "Essayer", 6 | "home": "Maison", 7 | "toggle_dark": "Basculer en mode sombre", 8 | "toggle_langs": "Changer de langue" 9 | }, 10 | "intro": { 11 | "desc": "Example d'application Vite", 12 | "dynamic-route": "Démo de route dynamique", 13 | "hi": "Salut, {name} !", 14 | "whats-your-name": "Comment t'appelles-tu ?" 15 | }, 16 | "not-found": "Page non trouvée" 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "Su di me", 4 | "back": "Indietro", 5 | "go": "Vai", 6 | "home": "Home", 7 | "toggle_dark": "Attiva/disattiva modalità scura", 8 | "toggle_langs": "Cambia lingua" 9 | }, 10 | "intro": { 11 | "desc": "Modello per una Applicazione Vite", 12 | "dynamic-route": "Demo di rotta dinamica", 13 | "hi": "Ciao, {name}!", 14 | "whats-your-name": "Come ti chiami?" 15 | }, 16 | "not-found": "Non trovato" 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/translations/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "これは?", 4 | "back": "戻る", 5 | "go": "進む", 6 | "home": "ホーム", 7 | "toggle_dark": "ダークモード切り替え", 8 | "toggle_langs": "言語切り替え" 9 | }, 10 | "intro": { 11 | "desc": "固執された Vite スターターテンプレート", 12 | "dynamic-route": "動的ルートのデモ", 13 | "hi": "こんにちは、{name}!", 14 | "whats-your-name": "あなたの名前は?" 15 | }, 16 | "not-found": "見つかりませんでした" 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/translations/ko.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "소개", 4 | "back": "뒤로가기", 5 | "go": "이동", 6 | "home": "홈", 7 | "toggle_dark": "다크모드 토글", 8 | "toggle_langs": "언어 변경" 9 | }, 10 | "intro": { 11 | "desc": "Vite 애플리케이션 템플릿", 12 | "dynamic-route": "다이나믹 라우트 데모", 13 | "hi": "안녕, {name}!", 14 | "whats-your-name": "이름이 뭐예요?" 15 | }, 16 | "not-found": "찾을 수 없습니다" 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/translations/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "Hakkımda", 4 | "back": "Geri", 5 | "go": "İLERİ", 6 | "home": "Anasayfa", 7 | "toggle_dark": "Karanlık modu değiştir", 8 | "toggle_langs": "Dilleri değiştir" 9 | }, 10 | "intro": { 11 | "desc": "Görüşlü Vite Başlangıç Şablonu", 12 | "dynamic-route": "Dinamik rota demosu", 13 | "hi": "Merhaba, {name}!", 14 | "whats-your-name": "Adınız nedir ?" 15 | }, 16 | "not-found": "Bulunamadı" 17 | } 18 | -------------------------------------------------------------------------------- /src/i18n/translations/vi.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "back": "Quay lại", 4 | "go": "Đi" 5 | }, 6 | "intro": { 7 | "desc": "Ý kiến cá nhân Vite Template để bắt đầu", 8 | "dynamic-route": "Bản giới thiệu về dynamic route", 9 | "hi": "Hi, {name}!", 10 | "whats-your-name": "Tên bạn là gì?" 11 | }, 12 | "not-found": "Không tìm thấy" 13 | } 14 | -------------------------------------------------------------------------------- /src/i18n/translations/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "button": { 3 | "about": "关于", 4 | "back": "返回", 5 | "go": "确定", 6 | "home": "首页", 7 | "toggle_dark": "切换深色模式", 8 | "toggle_langs": "切换语言" 9 | }, 10 | "intro": { 11 | "desc": "固执己见的 Vite 项目模板", 12 | "dynamic-route": "动态路由演示", 13 | "hi": "你好,{name}!", 14 | "whats-your-name": "输入你的名字" 15 | }, 16 | "not-found": "未找到页面" 17 | } 18 | -------------------------------------------------------------------------------- /src/layouts/404.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | -------------------------------------------------------------------------------- /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 [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) 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 | ```html 10 | 11 | meta: 12 | layout: home 13 | 14 | ``` 15 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/layouts/home.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import 'windi.css' 2 | import './styles/main.css' 3 | import vitedge from 'vitedge' 4 | import generatedRoutes from 'virtual:generated-pages' 5 | import { setupLayouts } from 'virtual:generated-layouts' 6 | import { installI18n, extractLocaleFromPath, DEFAULT_LOCALE } from './i18n' 7 | import App from './App.vue' 8 | 9 | const routes = setupLayouts(generatedRoutes) 10 | 11 | // https://github.com/frandiox/vitedge 12 | export default vitedge( 13 | App, 14 | { 15 | routes, 16 | // Use Router's base for i18n routes 17 | base: ({ url }) => { 18 | const locale = extractLocaleFromPath(url.pathname) 19 | return locale === DEFAULT_LOCALE ? '/' : `/${locale}/` 20 | }, 21 | }, 22 | async (ctx) => { 23 | // install all modules under `modules/` 24 | Object.values(import.meta.globEager('./modules/*.ts')).map((i) => 25 | i.install?.(ctx) 26 | ) 27 | 28 | const { app, initialRoute } = ctx 29 | 30 | // Load language asyncrhonously to avoid bundling all languages 31 | await installI18n(app, extractLocaleFromPath(initialRoute.href)) 32 | } 33 | ) 34 | -------------------------------------------------------------------------------- /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 { UserModule } from '~/types' 7 | 8 | export const install: UserModule = ({ app, router, isClient }) => { 9 | // do something 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /src/modules/nprogress.ts: -------------------------------------------------------------------------------- 1 | import NProgress from 'nprogress' 2 | import { UserModule } from '~/types' 3 | 4 | export const install: UserModule = ({ isClient, router }) => { 5 | if (isClient) { 6 | router.beforeEach(() => { NProgress.start() }) 7 | router.afterEach(() => { NProgress.done() }) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/README.md: -------------------------------------------------------------------------------- 1 | ## File-based Routing 2 | 3 | Routes will auto-generated for Vue files in this dir with the same file structure. 4 | Check out [`vite-plugin-pages`](https://github.com/hannoeru/vite-plugin-pages) 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 utils from '../../../../utils' 14 | ``` 15 | 16 | now you can use 17 | 18 | ```ts 19 | import utils from '~/utils' 20 | ``` 21 | -------------------------------------------------------------------------------- /src/pages/[...all].vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | meta: 15 | layout: 404 16 | propsGetter: false 17 | 18 | -------------------------------------------------------------------------------- /src/pages/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About 3 | --- 4 | 5 |
6 | 7 | 8 |

About

9 |
10 | 11 | [Vitessedge](https://github.com/frandiox/vitessedge-template) is an opinionated [Vite](https://github.com/vitejs/vite) starter template made by [@antfu](https://github.com/antfu) and [@frandiox](https://github.com/frandiox) for mocking apps swiftly. With **file-based routing**, **components auto importing**, **markdown support**, I18n, PWA and uses **Tailwind** v2 for UI. Its fullstack and SSR capabilities in Cloudflare Workers are added via [Vitedge](https://github.com/frandiox/vitedge). 12 | 13 | ```js 14 | // syntax highlighting example 15 | function vitesse() { 16 | const foo = 'bar' 17 | console.log(foo) 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /src/pages/hi/[name].vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | -------------------------------------------------------------------------------- /src/pages/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 61 | 62 | 63 | name: home 64 | meta: 65 | layout: home 66 | 67 | -------------------------------------------------------------------------------- /src/shims.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-duplicates */ 2 | 3 | declare interface Window { 4 | // extend the window 5 | } 6 | 7 | // with vite-plugin-md, markdowns can be treat as Vue components 8 | declare module '*.md' { 9 | import { ComponentOptions } from 'vue' 10 | const component: ComponentOptions 11 | export default component 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | @import './markdown.css'; 2 | 3 | html, 4 | body, 5 | #app { 6 | height: 100vh; 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | html.dark { 12 | background: #222; 13 | } 14 | 15 | #nprogress { 16 | pointer-events: none; 17 | } 18 | 19 | #nprogress .bar { 20 | @apply bg-teal-600 opacity-75; 21 | 22 | position: fixed; 23 | z-index: 1031; 24 | top: 0; 25 | left: 0; 26 | 27 | width: 100%; 28 | height: 2px; 29 | } 30 | 31 | .btn { 32 | @apply px-4 py-1 rounded inline-block 33 | bg-teal-600 text-white cursor-pointer 34 | hover:bg-teal-700 35 | disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50; 36 | } 37 | 38 | .icon-btn { 39 | @apply inline-block cursor-pointer select-none 40 | opacity-75 transition duration-200 ease-in-out 41 | hover:opacity-100 hover:text-teal-600; 42 | font-size: 0.9em; 43 | } 44 | -------------------------------------------------------------------------------- /src/styles/markdown.css: -------------------------------------------------------------------------------- 1 | /* https://github.com/antfu/prism-theme-vars */ 2 | @import 'prism-theme-vars/base.css'; 3 | 4 | :root { 5 | --prism-font-family: 'Input Mono', monospace; 6 | } 7 | 8 | html:not(.dark) { 9 | --prism-foreground: #393a34; 10 | --prism-background: #fbfbfb; 11 | --prism-comment: #8e8f8e; 12 | --prism-string: #a1644c; 13 | --prism-literal: #3a9c9b; 14 | --prism-keyword: #248358; 15 | --prism-function: #7e8a42; 16 | --prism-deleted: #a14f55; 17 | --prism-class: #2b91af; 18 | --prism-builtin: #a52727; 19 | --prism-property: #ad502b; 20 | --prism-namespace: #c96880; 21 | --prism-punctuation: #8e8f8b; 22 | --prism-decorator: #bd8f8f; 23 | --prism-json-property: #698c96; 24 | } 25 | 26 | html.dark { 27 | --prism-scheme: dark; 28 | --prism-foreground: #d4cfbf; 29 | --prism-background: #1e1e1e; 30 | --prism-comment: #758575; 31 | --prism-string: #ce9178; 32 | --prism-literal: #4fb09d; 33 | --prism-keyword: #4d9375; 34 | --prism-function: #c2c275; 35 | --prism-deleted: #a14f55; 36 | --prism-class: #5ebaa8; 37 | --prism-builtin: #cb7676; 38 | --prism-property: #dd8e6e; 39 | --prism-namespace: #c96880; 40 | --prism-punctuation: #d4d4d4; 41 | --prism-decorator: #bd8f8f; 42 | --prism-regex: #ab5e3f; 43 | --prism-json-property: #6b8b9e; 44 | --prism-line-number: #888888; 45 | --prism-line-number-gutter: #eeeeee; 46 | --prism-line-highlight-background: #444444; 47 | --prism-selection-background: #444444; 48 | } 49 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import colors from 'windicss/colors' 2 | import typography from 'windicss/plugin/typography' 3 | 4 | export default { 5 | darkMode: 'class', 6 | plugins: [typography()], 7 | theme: { 8 | extend: { 9 | typography: { 10 | DEFAULT: { 11 | css: { 12 | maxWidth: '65ch', 13 | color: 'inherit', 14 | a: { 15 | color: 'inherit', 16 | opacity: 0.75, 17 | fontWeight: '500', 18 | textDecoration: 'underline', 19 | '&:hover': { 20 | opacity: 1, 21 | color: colors.teal[600], 22 | }, 23 | }, 24 | b: { color: 'inherit' }, 25 | strong: { color: 'inherit' }, 26 | em: { color: 'inherit' }, 27 | h1: { color: 'inherit' }, 28 | h2: { color: 'inherit' }, 29 | h3: { color: 'inherit' }, 30 | h4: { color: 'inherit' }, 31 | code: { color: 'inherit' }, 32 | }, 33 | }, 34 | }, 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "target": "es2019", 6 | "lib": ["DOM", "ESNext", "WebWorker"], 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "incremental": true, 10 | "skipLibCheck": true, 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "noUnusedLocals": true, 14 | "strictNullChecks": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "types": [ 17 | "vite/client", 18 | "vite-plugin-pages/client", 19 | "vite-plugin-vue-layouts/client", 20 | "@cloudflare/workers-types" 21 | ], 22 | "paths": { 23 | "~/*": ["src/*", "functions/*"] 24 | } 25 | }, 26 | "exclude": ["dist", "node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { defineConfig } from 'vite' 3 | import Vue from '@vitejs/plugin-vue' 4 | import Pages from 'vite-plugin-pages' 5 | import Layouts from 'vite-plugin-vue-layouts' 6 | import ViteIcons, { ViteIconsResolver } from 'vite-plugin-icons' 7 | import ViteComponents from 'vite-plugin-components' 8 | import Markdown from 'vite-plugin-md' 9 | import WindiCSS from 'vite-plugin-windicss' 10 | import { VitePWA } from 'vite-plugin-pwa' 11 | import VueI18n from '@intlify/vite-plugin-vue-i18n' 12 | import Prism from 'markdown-it-prism' 13 | import vitedge from 'vitedge/plugin.js' 14 | 15 | export default defineConfig({ 16 | resolve: { 17 | alias: { 18 | '~/': `${path.resolve(process.cwd(), 'src')}/`, 19 | }, 20 | }, 21 | plugins: [ 22 | vitedge(), 23 | 24 | Vue({ 25 | include: [/\.vue$/, /\.md$/], 26 | }), 27 | 28 | // https://github.com/hannoeru/vite-plugin-pages 29 | // @ts-ignore 30 | Pages.default({ 31 | extensions: ['vue', 'md'], 32 | extendRoute(route: any) { 33 | if (route.component.endsWith('.md')) { 34 | return { 35 | ...route, 36 | meta: { 37 | // Disable page props for static MD routes 38 | propsGetter: false, 39 | }, 40 | } 41 | } 42 | }, 43 | }), 44 | 45 | // https://github.com/JohnCampionJr/vite-plugin-vue-layouts 46 | // @ts-ignore 47 | Layouts.default(), 48 | 49 | // https://github.com/antfu/vite-plugin-md 50 | // @ts-ignore 51 | Markdown.default({ 52 | wrapperClasses: 'prose prose-sm m-auto text-left', 53 | headEnabled: false, // This relies on useHead 54 | markdownItSetup(md) { 55 | // https://prismjs.com/ 56 | md.use(Prism) 57 | }, 58 | }), 59 | 60 | // https://github.com/antfu/vite-plugin-components 61 | // @ts-ignore 62 | ViteComponents.default({ 63 | // allow auto load markdown components under `./src/components/` 64 | extensions: ['vue', 'md'], 65 | 66 | // allow auto import and register components used in markdown 67 | customLoaderMatcher: (id: string) => id.endsWith('.md'), 68 | 69 | // auto import icons 70 | customComponentResolvers: [ 71 | // https://github.com/antfu/vite-plugin-icons 72 | ViteIconsResolver({ 73 | componentPrefix: '', 74 | // enabledCollections: ['carbon'] 75 | }), 76 | ], 77 | }), 78 | 79 | // https://github.com/antfu/vite-plugin-icons 80 | // @ts-ignore 81 | ViteIcons.default(), 82 | 83 | // https://github.com/antfu/vite-plugin-windicss 84 | // @ts-ignore 85 | WindiCSS({ 86 | // config: tailwindConfig, 87 | safelist: 'prose prose-sm m-auto', 88 | }), 89 | 90 | // https://github.com/antfu/vite-plugin-pwa 91 | VitePWA({ 92 | manifest: { 93 | name: 'Vitesse', 94 | short_name: 'Vitesse', 95 | theme_color: '#ffffff', 96 | icons: [ 97 | { 98 | src: '/pwa-192x192.png', 99 | sizes: '192x192', 100 | type: 'image/png', 101 | }, 102 | { 103 | src: '/pwa-512x512.png', 104 | sizes: '512x512', 105 | type: 'image/png', 106 | }, 107 | { 108 | src: '/pwa-512x512.png', 109 | sizes: '512x512', 110 | type: 'image/png', 111 | purpose: 'any maskable', 112 | }, 113 | ], 114 | }, 115 | }), 116 | 117 | // https://github.com/intlify/vite-plugin-vue-i18n 118 | // @ts-ignore 119 | VueI18n.default({ 120 | include: [path.resolve(process.cwd(), 'src/i18n/translations/**')], 121 | }), 122 | ], 123 | 124 | optimizeDeps: { 125 | include: ['vue', 'vue-router', '@vueuse/core'], 126 | exclude: ['vue-demi'], 127 | }, 128 | }) 129 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "vitessedge" 2 | type = "javascript" 3 | account_id = "" 4 | workers_dev = true 5 | route = "" 6 | zone_id = "" 7 | 8 | [site] 9 | bucket = "dist/client" 10 | entry-point = "." 11 | 12 | [build] 13 | command = "" 14 | watch_dir = "dist/worker" 15 | 16 | [build.upload] 17 | format = "service-worker" --------------------------------------------------------------------------------