Repository: bencodezen/vue-enterprise-boilerplate Branch: main Commit: afd246b1232f Files: 54 Total size: 97.6 KB Directory structure: gitextract_ewk5efrr/ ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc.json ├── .vscode/ │ ├── _sfc.code-snippets │ ├── extensions.json │ └── settings.json ├── README.md ├── auto-imports.d.ts ├── components.d.ts ├── docs/ │ ├── .vitepress/ │ │ └── config.mts │ ├── architecture.md │ ├── development.md │ ├── editors.md │ ├── index.md │ ├── linting.md │ ├── production.md │ ├── routing.md │ ├── state.md │ ├── tech.md │ ├── tests.md │ └── troubleshooting.md ├── e2e/ │ ├── tsconfig.json │ └── vue.spec.ts ├── env.d.ts ├── index.html ├── package.json ├── playwright.config.ts ├── src/ │ ├── App.vue │ ├── components/ │ │ ├── BaseButton.spec.ts │ │ ├── BaseButton.vue │ │ ├── BaseInputText.spec.ts │ │ └── BaseInputText.vue │ ├── composables/ │ │ └── useTheme.ts │ ├── design/ │ │ ├── _colors.scss │ │ ├── _durations.scss │ │ ├── _fonts.scss │ │ ├── _layers.scss │ │ ├── _sizes.scss │ │ ├── _typography.scss │ │ └── index.scss │ ├── layouts/ │ │ └── AppLayout.vue │ ├── main.ts │ ├── pages/ │ │ ├── about.vue │ │ └── index.vue │ ├── router/ │ │ ├── index.ts │ │ └── routes.ts │ ├── stores/ │ │ └── counter.ts │ └── types.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.vitest.json ├── vite.config.ts └── vitest.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.cjs ================================================ /* eslint-env node */ require('@rushstack/eslint-patch/modern-module-resolution') module.exports = { root: true, 'extends': [ 'plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier/skip-formatting' ], parserOptions: { ecmaVersion: 'latest' } } ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules .DS_Store dist dist-ssr coverage *.local # Editor directories and files .idea *.suo *.ntvs* *.njsproj *.sln *.sw? # Playwright directories test-results/ playwright-report/ # VitePress docs/.vitepress/cache docs/.vitepress/dist ================================================ FILE: .prettierrc.json ================================================ { "$schema": "https://json.schemastore.org/prettierrc", "semi": false, "tabWidth": 2, "singleQuote": true, "printWidth": 100, "trailingComma": "none" } ================================================ FILE: .vscode/_sfc.code-snippets ================================================ { "veb-sfc": { "scope": "vue", "prefix": "sfc", "body": [ "", "", "", "", "" ], "description": "Single File Component (Comp API + TS) from VEB" }, "veb-sfc-options": { "scope": "vue", "prefix": "sfc-options", "body": [ "", "", "", "", "" ], "description": "Single File Component (Options API + TS) from VEB" }, "veb-script": { "scope": "vue", "prefix": "script", "body": [""], "description": "Script block (Comp API + TS)" }, "veb-script-options": { "scope": "vue", "prefix": "script-options", "body": [ "" ], "description": "Script block (Options API + TS)" }, "veb-template": { "scope": "vue", "prefix": "template", "body": [""], "description": "Template block" }, "veb-style": { "scope": "vue", "prefix": "style", "body": [""], "description": "Scoped CSS + Sass styles block from VEB" }, "veb-style-module": { "scope": "vue", "prefix": "style-module", "body": [""], "description": "CSS Module + Sass styles block from VEB" } } ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ // Vue - Official Extension // https://github.com/vuejs/language-tools "vue.volar", // Format-on-save with Prettier // https://github.com/prettier/prettier-vscode "esbenp.prettier-vscode", // Playwright Test - Official Extension // https://github.com/microsoft/playwright-vscode "ms-playwright.playwright", // Better Comments // https://github.com/aaron-bond/better-comments "aaron-bond.better-comments", // Path Intellisense // https://github.com/ChristianKohler/PathIntellisense "christian-kohler.path-intellisense", // Peacock - Workspace Color Customizer // https://github.com/johnpapa/vscode-peacock "johnpapa.vscode-peacock" ] } ================================================ FILE: .vscode/settings.json ================================================ { // ====== // Spacing // ====== "editor.insertSpaces": true, "editor.tabSize": 2, "editor.trimAutoWhitespace": true, "files.trimTrailingWhitespace": true, "files.eol": "\n", "files.insertFinalNewline": true, "files.trimFinalNewlines": true, // ====== // Files // ====== "files.exclude": { "**/*.log": true, "**/*.log*": true, "**/dist": true, "**/coverage": true }, // ====== // Event Triggers // ====== "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.fixAll.stylelint": "explicit", "source.fixAll.markdownlint": "explicit" }, "eslint.validate": ["javascript", "javascriptreact", "vue", "vue-html", "html"], // ====== // HTML // ====== "emmet.triggerExpansionOnTab": true, // ====== // CSS // ====== "stylelint.enable": true, "css.validate": false, "scss.validate": false, // ====== // MARKDOWN // ====== "[markdown]": { "editor.wordWrap": "wordWrapColumn", "editor.wordWrapColumn": 80 } } ================================================ FILE: README.md ================================================ # Vue Enterprise Boilerplate v3 (alpha) This repo is currently in active development and considered in alpha release. > This is an ever-evolving, opinionated architecture and dev environment for new Vue 3 + Vite SPA projects using [create-vue](https://github.com/vuejs/create-vue). 🎩 A huge thanks to [Chris Fritz](https://twitter.com/chrisvfritz) for the incredible work that this work builds upon. For those looking for his version, see [this branch for the original Vue 2 enterprise boilerplate](https://github.com/bencodezen/vue-enterprise-boilerplate/tree/vue-2-version). ## Recommended IDE Setup [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). ## Type Support for `.vue` Imports in TS TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 1. Disable the built-in TypeScript Extension 1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette 2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. ## Project Setup ```sh npm install ``` ### Compile and Hot-Reload for Development ```sh npm run dev ``` ### Type-Check, Compile and Minify for Production ```sh npm run build ``` ### Run Unit Tests with [Vitest](https://vitest.dev/) ```sh npm run test:unit ``` ### Run End-to-End Tests with [Playwright](https://playwright.dev) ```sh # Install browsers for the first run npx playwright install # When testing on CI, must build the project first npm run build # Runs the end-to-end tests npm run test:e2e # Runs the tests only on Chromium npm run test:e2e -- --project=chromium # Runs the tests of a specific file npm run test:e2e -- tests/example.spec.ts # Runs the tests in debug mode npm run test:e2e -- --debug ``` ### Lint with [ESLint](https://eslint.org/) ```sh npm run lint ``` ================================================ FILE: auto-imports.d.ts ================================================ /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck // noinspection JSUnusedGlobalSymbols // Generated by unplugin-auto-import export {} declare global { const EffectScope: typeof import('vue')['EffectScope'] const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] const computed: typeof import('vue')['computed'] const computedAsync: typeof import('@vueuse/core')['computedAsync'] const computedEager: typeof import('@vueuse/core')['computedEager'] const computedInject: typeof import('@vueuse/core')['computedInject'] const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] const controlledRef: typeof import('@vueuse/core')['controlledRef'] const createApp: typeof import('vue')['createApp'] const createEventHook: typeof import('@vueuse/core')['createEventHook'] const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate'] const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] const createTemplatePromise: typeof import('@vueuse/core')['createTemplatePromise'] const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] const customRef: typeof import('vue')['customRef'] const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineComponent: typeof import('vue')['defineComponent'] const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] const effectScope: typeof import('vue')['effectScope'] const extendRef: typeof import('@vueuse/core')['extendRef'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const getCurrentScope: typeof import('vue')['getCurrentScope'] const h: typeof import('vue')['h'] const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] const inject: typeof import('vue')['inject'] const injectLocal: typeof import('@vueuse/core')['injectLocal'] const isDefined: typeof import('@vueuse/core')['isDefined'] const isProxy: typeof import('vue')['isProxy'] const isReactive: typeof import('vue')['isReactive'] const isReadonly: typeof import('vue')['isReadonly'] const isRef: typeof import('vue')['isRef'] const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] const markRaw: typeof import('vue')['markRaw'] const nextTick: typeof import('vue')['nextTick'] const onActivated: typeof import('vue')['onActivated'] const onBeforeMount: typeof import('vue')['onBeforeMount'] const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] const onDeactivated: typeof import('vue')['onDeactivated'] const onErrorCaptured: typeof import('vue')['onErrorCaptured'] const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] const onLongPress: typeof import('@vueuse/core')['onLongPress'] const onMounted: typeof import('vue')['onMounted'] const onRenderTracked: typeof import('vue')['onRenderTracked'] const onRenderTriggered: typeof import('vue')['onRenderTriggered'] const onScopeDispose: typeof import('vue')['onScopeDispose'] const onServerPrefetch: typeof import('vue')['onServerPrefetch'] const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] const onUnmounted: typeof import('vue')['onUnmounted'] const onUpdated: typeof import('vue')['onUpdated'] const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] const provide: typeof import('vue')['provide'] const provideLocal: typeof import('@vueuse/core')['provideLocal'] const reactify: typeof import('@vueuse/core')['reactify'] const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] const reactive: typeof import('vue')['reactive'] const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] const reactivePick: typeof import('@vueuse/core')['reactivePick'] const readonly: typeof import('vue')['readonly'] const ref: typeof import('vue')['ref'] const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] const refDebounced: typeof import('@vueuse/core')['refDebounced'] const refDefault: typeof import('@vueuse/core')['refDefault'] const refThrottled: typeof import('@vueuse/core')['refThrottled'] const refWithControl: typeof import('@vueuse/core')['refWithControl'] const resolveComponent: typeof import('vue')['resolveComponent'] const resolveRef: typeof import('@vueuse/core')['resolveRef'] const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] const shallowReactive: typeof import('vue')['shallowReactive'] const shallowReadonly: typeof import('vue')['shallowReadonly'] const shallowRef: typeof import('vue')['shallowRef'] const syncRef: typeof import('@vueuse/core')['syncRef'] const syncRefs: typeof import('@vueuse/core')['syncRefs'] const templateRef: typeof import('@vueuse/core')['templateRef'] const throttledRef: typeof import('@vueuse/core')['throttledRef'] const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] const toRaw: typeof import('vue')['toRaw'] const toReactive: typeof import('@vueuse/core')['toReactive'] const toRef: typeof import('vue')['toRef'] const toRefs: typeof import('vue')['toRefs'] const toValue: typeof import('vue')['toValue'] const triggerRef: typeof import('vue')['triggerRef'] const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] const unref: typeof import('vue')['unref'] const unrefElement: typeof import('@vueuse/core')['unrefElement'] const until: typeof import('@vueuse/core')['until'] const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] const useAnimate: typeof import('@vueuse/core')['useAnimate'] const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference'] const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast'] const useArrayIncludes: typeof import('@vueuse/core')['useArrayIncludes'] const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] const useArraySome: typeof import('@vueuse/core')['useArraySome'] const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique'] const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] const useAttrs: typeof import('vue')['useAttrs'] const useBase64: typeof import('@vueuse/core')['useBase64'] const useBattery: typeof import('@vueuse/core')['useBattery'] const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] const useCached: typeof import('@vueuse/core')['useCached'] const useClipboard: typeof import('@vueuse/core')['useClipboard'] const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems'] const useCloned: typeof import('@vueuse/core')['useCloned'] const useColorMode: typeof import('@vueuse/core')['useColorMode'] const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] const useCounter: typeof import('@vueuse/core')['useCounter'] const useCssModule: typeof import('vue')['useCssModule'] const useCssVar: typeof import('@vueuse/core')['useCssVar'] const useCssVars: typeof import('vue')['useCssVars'] const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] const useCycleList: typeof import('@vueuse/core')['useCycleList'] const useDark: typeof import('@vueuse/core')['useDark'] const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] const useDebounce: typeof import('@vueuse/core')['useDebounce'] const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] const useDraggable: typeof import('@vueuse/core')['useDraggable'] const useDropZone: typeof import('@vueuse/core')['useDropZone'] const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] const useElementHover: typeof import('@vueuse/core')['useElementHover'] const useElementSize: typeof import('@vueuse/core')['useElementSize'] const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] const useEventBus: typeof import('@vueuse/core')['useEventBus'] const useEventListener: typeof import('@vueuse/core')['useEventListener'] const useEventSource: typeof import('@vueuse/core')['useEventSource'] const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] const useFavicon: typeof import('@vueuse/core')['useFavicon'] const useFetch: typeof import('@vueuse/core')['useFetch'] const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] const useFocus: typeof import('@vueuse/core')['useFocus'] const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] const useFps: typeof import('@vueuse/core')['useFps'] const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] const useGamepad: typeof import('@vueuse/core')['useGamepad'] const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] const useHead: typeof import('@unhead/vue')['useHead'] const useIdle: typeof import('@vueuse/core')['useIdle'] const useImage: typeof import('@vueuse/core')['useImage'] const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] const useInterval: typeof import('@vueuse/core')['useInterval'] const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] const useLink: typeof import('vue-router')['useLink'] const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] const useMemoize: typeof import('@vueuse/core')['useMemoize'] const useMemory: typeof import('@vueuse/core')['useMemory'] const useMounted: typeof import('@vueuse/core')['useMounted'] const useMouse: typeof import('@vueuse/core')['useMouse'] const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] const useNetwork: typeof import('@vueuse/core')['useNetwork'] const useNow: typeof import('@vueuse/core')['useNow'] const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] const useOnline: typeof import('@vueuse/core')['useOnline'] const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] const useParallax: typeof import('@vueuse/core')['useParallax'] const useParentElement: typeof import('@vueuse/core')['useParentElement'] const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver'] const usePermission: typeof import('@vueuse/core')['usePermission'] const usePointer: typeof import('@vueuse/core')['usePointer'] const usePointerLock: typeof import('@vueuse/core')['usePointerLock'] const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] const usePrevious: typeof import('@vueuse/core')['usePrevious'] const useRafFn: typeof import('@vueuse/core')['useRafFn'] const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const useRoute: typeof import('vue-router')['useRoute'] const useRouter: typeof import('vue-router')['useRouter'] const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] const useScroll: typeof import('@vueuse/core')['useScroll'] const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] const useShare: typeof import('@vueuse/core')['useShare'] const useSlots: typeof import('vue')['useSlots'] const useSorted: typeof import('@vueuse/core')['useSorted'] const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] const useStepper: typeof import('@vueuse/core')['useStepper'] const useStorage: typeof import('@vueuse/core')['useStorage'] const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] const useSupported: typeof import('@vueuse/core')['useSupported'] const useSwipe: typeof import('@vueuse/core')['useSwipe'] const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] const useThrottle: typeof import('@vueuse/core')['useThrottle'] const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] const useTimeout: typeof import('@vueuse/core')['useTimeout'] const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] const useTitle: typeof import('@vueuse/core')['useTitle'] const useToNumber: typeof import('@vueuse/core')['useToNumber'] const useToString: typeof import('@vueuse/core')['useToString'] const useToggle: typeof import('@vueuse/core')['useToggle'] const useTransition: typeof import('@vueuse/core')['useTransition'] const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] const useVModel: typeof import('@vueuse/core')['useVModel'] const useVModels: typeof import('@vueuse/core')['useVModels'] const useVibrate: typeof import('@vueuse/core')['useVibrate'] const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] const watch: typeof import('vue')['watch'] const watchArray: typeof import('@vueuse/core')['watchArray'] const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] const watchDeep: typeof import('@vueuse/core')['watchDeep'] const watchEffect: typeof import('vue')['watchEffect'] const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] const watchImmediate: typeof import('@vueuse/core')['watchImmediate'] const watchOnce: typeof import('@vueuse/core')['watchOnce'] const watchPausable: typeof import('@vueuse/core')['watchPausable'] const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchSyncEffect: typeof import('vue')['watchSyncEffect'] const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] const whenever: typeof import('@vueuse/core')['whenever'] } // for type re-export declare global { // @ts-ignore export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' import('vue') } ================================================ FILE: components.d.ts ================================================ /* eslint-disable */ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { AppLayout: typeof import('./src/layouts/AppLayout.vue')['default'] BaseButton: typeof import('./src/components/BaseButton.vue')['default'] BaseInputText: typeof import('./src/components/BaseInputText.vue')['default'] BaseLink: typeof import('./src/components/BaseLink.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] } } ================================================ FILE: docs/.vitepress/config.mts ================================================ import { defineConfig } from 'vitepress' // https://vitepress.dev/reference/site-config export default defineConfig({ title: "Vue Enterprise Boilerplate", description: "Documentation for Vue Enterprise Boilerplate", themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ { text: 'Home', link: '/' }, { text: 'Examples', link: '/markdown-examples' } ], sidebar: [ { text: 'Examples', items: [ { text: 'Markdown Examples', link: '/markdown-examples' }, { text: 'Runtime API Examples', link: '/api-examples' } ] } ], socialLinks: [ { icon: 'github', link: 'https://github.com/vuejs/vitepress' } ] } }) ================================================ FILE: docs/architecture.md ================================================ # Architecture - [Architecture](#architecture) - [`.vscode`](#vscode) - [`docs`](#docs) - [`e2e`](#e2e) - [`public`](#public) - [`src`](#src) - [`assets`](#assets) - [`components`](#components) - [`composables`](#composables) - [`design`](#design) - [`layouts`](#layouts) - [`pages`](#pages) - [`router`](#router) - [`stores`](#stores) - [`App.vue`](#appvue) - [`main.ts`](#maints) - [`types.ts`](#typests) - [Configuration Files](#configuration-files) ## `.vscode` Settings and extensions specific to this project, for Visual Studio Code. See [the editors doc](editors.md) for more. ## `docs` You found me! Documentation is powered by [VitePress](https://vitepress.dev/) and can be built as a static site. ```bash # Start docs dev server npm run docs:dev # Build docs for production npm run docs:build ``` ### `.vitepress` VitePress configuration including sidebar navigation, theme settings, and build options. ## `e2e` End-to-end tests using [Playwright](https://playwright.dev/). See [the tests doc](tests.md) for more. ## `public` Static assets that will be served directly without processing. Files here are copied to the build output as-is. ## `src` Where we keep all our source files. ### `assets` Static assets like images and fonts that will be processed by Vite. Assets here can be imported in components and will be optimized during build. ### `components` Reusable Vue components, including [global base components](development.md#base-components). Components are auto-registered via `unplugin-vue-components`. Unit tests are colocated with components using the `.spec.ts` extension (e.g., `BaseButton.spec.ts`). ### `composables` Reusable composition functions that encapsulate stateful logic. These follow the `use*` naming convention (e.g., `useTheme.ts`). Composables are the Vue 3 replacement for mixins, providing better TypeScript support and explicit dependencies. ### `design` SCSS design system variables and utilities: - `_colors.scss` - Color palette - `_typography.scss` - Font scales and text styles - `_sizes.scss` - Spacing and sizing - `_fonts.scss` - Font imports - `_durations.scss` - Animation timing - `_layers.scss` - Z-index management - `index.scss` - Central import point See [the tech doc](tech.md#design-variables) for more. ### `layouts` Layout components that wrap pages with common structure (navigation, footer, etc.). Layouts receive page content via slots. ### `pages` Page components that map to routes. Each file in this directory represents a distinct route in the application. ### `router` Vue Router configuration: - `index.ts` - Router instance creation and global guards - `routes.ts` - Route definitions with lazy-loaded pages See [the routing doc](routing.md) for more. ### `stores` [Pinia](https://pinia.vuejs.org/) stores for global state management. Each store is a separate file following the composition API pattern. See [the state doc](state.md) for more. ### `App.vue` The root Vue component that renders the router view. This is typically the only component to contain global CSS. ### `main.ts` The entry point to our app, where we create the Vue application instance, register plugins (Pinia, Router, Unhead), and mount to the DOM. ### `types.ts` Shared TypeScript type definitions and utility types used across the application. ## Configuration Files | File | Purpose | |------|---------| | `vite.config.ts` | Vite build configuration, plugins, and dev server | | `tsconfig.json` | Root TypeScript configuration | | `tsconfig.app.json` | TypeScript config for application code | | `tsconfig.node.json` | TypeScript config for Node.js files | | `tsconfig.vitest.json` | TypeScript config for Vitest tests | | `vitest.config.ts` | Unit test configuration | | `playwright.config.ts` | E2E test configuration | | `.eslintrc.cjs` | ESLint linting rules | | `.prettierrc.json` | Prettier formatting rules | | `env.d.ts` | Environment variable type declarations | | `components.d.ts` | Auto-generated component type declarations | | `auto-imports.d.ts` | Auto-generated import type declarations | ================================================ FILE: docs/development.md ================================================ # Setup and Development - [Setup and Development](#setup-and-development) - [First-time setup](#first-time-setup) - [Installation](#installation) - [Dev server](#dev-server) - [Developing with a production API](#developing-with-a-production-api) - [Aliases](#aliases) - [Auto-imports](#auto-imports) - [Base components](#base-components) - [Vue APIs](#vue-apis) ## First-time setup Make sure you have the following installed: - [Node](https://nodejs.org/en/) (at least the latest LTS) - [pnpm](https://pnpm.io/) (recommended package manager) ### Why pnpm? This project recommends pnpm over npm for several reasons: - **Faster installs** - Uses symlinks and a content-addressable store for efficient caching - **Disk space efficient** - Packages are stored once globally and linked, not duplicated per project - **Strict dependencies** - Prevents "phantom dependencies" where code accidentally imports packages not listed in package.json - **Vue ecosystem standard** - Vue, Vite, and Nuxt all use pnpm for development To install pnpm: ```bash # Using npm npm install -g pnpm # Or using Corepack (included with Node 16.13+) corepack enable corepack prepare pnpm@latest --activate ``` ## Installation ```bash # Install dependencies from package.json pnpm install ``` > **Note:** npm and yarn will also work if you prefer, but pnpm is recommended for the reasons above. ## Dev server ```bash # Launch the dev server pnpm dev # Launch the dev server and automatically open it in # your default browser when ready pnpm dev --open ``` Vite's dev server starts almost instantly and features lightning-fast Hot Module Replacement (HMR). ### Developing with a production API By default, you may want to use a mock API during development. To develop against a local or production API instead, create a `.env.local` file: ```bash # .env.local VITE_API_BASE_URL=http://localhost:3000 ``` Or set the environment variable inline: ```bash # Develop against a local backend server VITE_API_BASE_URL=http://localhost:3000 pnpm dev # Develop against a production server VITE_API_BASE_URL=https://api.example.com pnpm dev ``` Access this in your code via `import.meta.env.VITE_API_BASE_URL`. ## Aliases Path aliases simplify imports and make refactoring easier. They're configured in both `vite.config.ts` and `tsconfig.app.json` for consistent behavior across dev, build, and IDE intellisense. | Alias | Path | |-------|------| | `@` | `src/` | Example usage: ```typescript // Instead of relative paths import { useTheme } from '../../../composables/useTheme' // Use aliases import { useTheme } from '@/composables/useTheme' ``` To add new aliases, update both: - `vite.config.ts` - for Vite resolution - `tsconfig.app.json` - for TypeScript and IDE support ## Auto-imports This project uses [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components) and [unplugin-auto-import](https://github.com/unplugin/unplugin-auto-import) to automatically register components and imports. ### Base components Components in `src/components/` are automatically registered globally. You don't need to import them to use them in templates: ```vue ``` [Base components](https://vuejs.org/style-guide/rules-strongly-recommended.html#base-component-names) (a.k.a. presentational, dumb, or pure components) that apply app-specific styling should begin with the `Base` prefix (e.g., `BaseButton`, `BaseInput`). ### Vue APIs Common Vue APIs are auto-imported, so you don't need to manually import them: ```vue ``` Auto-imported APIs include: - Vue: `ref`, `computed`, `watch`, `onMounted`, etc. - Vue Router: `useRouter`, `useRoute` - Pinia: `defineStore`, `storeToRefs` - VueUse: Various composables The generated type declarations are in `auto-imports.d.ts` and `components.d.ts`. ================================================ FILE: docs/editors.md ================================================ # Editor Integration - [Visual Studio Code](#visual-studio-code) - [Recommended Extensions](#recommended-extensions) - [Workspace Settings](#workspace-settings) - [FAQ](#faq) ## Visual Studio Code This project is optimized for VS Code. With the [recommended extensions](https://code.visualstudio.com/docs/editor/extension-gallery#_workspace-recommended-extensions) and workspace settings in `.vscode/`, you get: - Vue 3 syntax highlighting and intellisense (via Volar) - Format-on-save with Prettier - Lint-on-save with ESLint and Stylelint - Playwright test integration - Path autocompletion for imports ### Recommended Extensions Install recommended extensions when prompted, or run `Extensions: Show Recommended Extensions` from the command palette. | Extension | Purpose | |-----------|---------| | [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) | Vue 3 language support (syntax, intellisense, type checking) | | [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) | Code formatting | | [Playwright Test](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) | E2E test runner integration | | [Path Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense) | Autocomplete for file paths | | [Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) | Highlight TODOs, notes, and warnings in comments | | [Peacock](https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock) | Color-code workspaces (useful for multiple projects) | > **Note:** If you previously used Vetur for Vue 2, uninstall it. Volar (Vue - Official) is the recommended extension for Vue 3. ### Workspace Settings The `.vscode/settings.json` file configures: **Formatting:** - 2-space indentation - Trim trailing whitespace - Insert final newline - Unix line endings (LF) **Auto-fix on save:** - Prettier formats code - ESLint fixes JavaScript/Vue issues - Stylelint fixes CSS issues **File hiding:** - Hides `dist/`, `coverage/`, and log files from explorer These settings only apply to this workspace and won't affect your global VS Code configuration. ## FAQ **What kinds of editor settings and extensions should be added to the project?** All additions must: - Be specific to this project's needs - Not interfere with any team member's workflow For example, an extension for syntax highlighting or linting is welcome, but personal preferences like color themes or font settings should stay in your user settings, not the workspace. **Why Volar instead of Vetur?** Volar (now called "Vue - Official") is the recommended extension for Vue 3. It provides better TypeScript support, improved performance, and is actively maintained by the Vue team. Vetur was designed for Vue 2 and is no longer recommended. ================================================ FILE: docs/index.md ================================================ --- layout: home hero: name: Vue Enterprise Boilerplate text: Production-ready Vue 3 architecture tagline: An opinionated architecture and dev environment for new Vue 3 SPAs using Vite, TypeScript, and Pinia. actions: - theme: brand text: Get Started link: /development - theme: alt text: View on GitHub link: https://github.com/bencodezen/vue-enterprise-boilerplate features: - icon: 📝 title: Thorough Documentation details: Every architectural decision is documented, so even new team members can quickly understand patterns and conventions. - icon: 🔒 title: Type Safety details: Full TypeScript support with strict mode, typed props/emits, and comprehensive type inference. - icon: ⚡ title: Lightning Fast details: Powered by Vite for instant dev server startup and blazing fast HMR. - icon: 🧪 title: First-Class Testing details: Unit testing with Vitest and E2E testing with Playwright, both configured and ready to use. - icon: 🎨 title: Design System details: SCSS-based design tokens for colors, typography, spacing, and more. - icon: 🛠️ title: Developer Experience details: Auto-imports, component auto-registration, and VS Code integration out of the box. --- ## Why This Boilerplate? This boilerplate helps teams build large-scale Vue 3 applications by providing: - **Consistent patterns** - Established conventions for components, state, routing, and testing - **Scalable architecture** - Folder structure that grows with your application - **Modern tooling** - Vite, TypeScript, Pinia, Vue Router 4, Vitest, Playwright - **Documentation culture** - Every pattern documented for team alignment ## Quick Start ```bash # Clone the repository git clone https://github.com/bencodezen/vue-enterprise-boilerplate.git cd vue-enterprise-boilerplate # Install dependencies npm install # Start development server npm run dev ``` Then open [http://localhost:8080](http://localhost:8080) to see your app. ================================================ FILE: docs/linting.md ================================================ # Linting & Formatting - [Overview](#overview) - [Languages](#languages) - [Scripts](#scripts) - [Terminal](#terminal) - [Editor](#editor) - [Configuration](#configuration) - [FAQ](#faq) This project uses ESLint and Prettier to catch errors and enforce a consistent code style. ## Overview | Tool | Purpose | |------|---------| | [ESLint](https://eslint.org/) | Catches bugs and enforces code quality rules | | [Prettier](https://prettier.io/) | Formats code for consistent style | ESLint handles logic and correctness, Prettier handles formatting. They're configured to work together without conflicts. ## Languages - **TypeScript/JavaScript** — Linted by ESLint, formatted by Prettier - **Vue SFCs** — Linted by ESLint (`eslint-plugin-vue`), formatted by Prettier - **JSON/HTML/CSS/Markdown** — Formatted by Prettier ## Scripts ### Terminal ```bash # Lint and auto-fix issues pnpm lint # Format all files in src/ pnpm format ``` ### Editor With the recommended VS Code extensions, files are automatically: - Linted on save (ESLint) - Formatted on save (Prettier) See [editors.md](editors.md) for setup details. ## Configuration | Tool | Config File | Docs | |------|-------------|------| | ESLint | `.eslintrc.cjs` | [ESLint Configuration](https://eslint.org/docs/user-guide/configuring) | | Prettier | `.prettierrc.json` | [Prettier Configuration](https://prettier.io/docs/en/configuration.html) | ### ESLint Setup The ESLint config extends: - `plugin:vue/vue3-essential` — Vue 3 specific rules - `eslint:recommended` — Core ESLint rules - `@vue/eslint-config-typescript` — TypeScript support - `@vue/eslint-config-prettier/skip-formatting` — Disables formatting rules (Prettier handles those) ### Prettier Setup ```json { "semi": false, "tabWidth": 2, "singleQuote": true, "printWidth": 100, "trailingComma": "none" } ``` ## FAQ **Why separate ESLint and Prettier?** ESLint is best at catching bugs and enforcing code quality (unused variables, missing returns, etc.). Prettier is best at formatting (indentation, line breaks, quotes). Using both gives you the best of each tool. **Why no semicolons?** This is a stylistic choice. JavaScript's ASI (Automatic Semicolon Insertion) handles most cases, and omitting semicolons reduces visual noise. If you prefer semicolons, change `"semi": true` in `.prettierrc.json`. **How do I add Stylelint for CSS?** If you need more advanced CSS linting: ```bash pnpm add -D stylelint stylelint-config-standard-scss ``` Create `stylelint.config.js`: ```js export default { extends: ['stylelint-config-standard-scss'] } ``` Add to `.vscode/settings.json`: ```json { "stylelint.enable": true, "css.validate": false, "scss.validate": false } ``` **So many configuration files! Why not move more to `package.json`?** While some configs can live in `package.json`, separate files offer benefits: - Easier to find and edit specific tool configs - Supports dynamic configuration via JavaScript - Better IDE support and syntax highlighting - Cleaner `package.json` that focuses on dependencies and scripts ================================================ FILE: docs/production.md ================================================ # Building and Deploying to Production - [Building for Production](#building-for-production) - [Previewing the Build](#previewing-the-build) - [Environment Variables](#environment-variables) - [Deployment](#deployment) ## Building for Production ```bash # Build for production pnpm build ``` This runs TypeScript type checking and then builds optimized assets into the `dist/` directory. The build process: 1. Type checks with `vue-tsc` 2. Bundles and minifies with Vite 3. Outputs to `dist/` with hashed filenames for cache busting ## Previewing the Build Before deploying, you can preview the production build locally: ```bash # Preview the production build pnpm preview ``` This serves the `dist/` directory on a local server, letting you verify the build works correctly. ## Environment Variables Vite uses `.env` files for environment-specific configuration: | File | Purpose | | ----------------------- | ------------------------------------ | | `.env` | Default values (committed) | | `.env.local` | Local overrides (not committed) | | `.env.production` | Production values (committed) | | `.env.production.local` | Production overrides (not committed) | Variables must be prefixed with `VITE_` to be exposed to client code: ```bash # .env.production VITE_API_BASE_URL=https://api.example.com ``` Access in code: ```typescript const apiUrl = import.meta.env.VITE_API_BASE_URL ``` > **Security:** Never put secrets in `VITE_` variables — they're embedded in the client bundle and visible to users. ## Deployment The `dist/` directory contains static files that can be deployed to any static hosting service: ### SPA Routing If using client-side routing (Vue Router in history mode), configure your server to redirect all requests to `index.html`. See the [Vue Router deployment guide](https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations) for server-specific examples. ================================================ FILE: docs/routing.md ================================================ # Routing, Layouts, and Pages - [Overview](#overview) - [Route Configuration](#route-configuration) - [Layouts](#layouts) - [Pages](#pages) - [Navigation Guards](#navigation-guards) - [Lazy Loading](#lazy-loading) ## Overview This project uses [Vue Router 4](https://router.vuejs.org/) for client-side routing: | File | Purpose | |------|---------| | `src/router/index.ts` | Router initialization | | `src/router/routes.ts` | Route definitions | | `src/layouts/` | Layout components | | `src/pages/` | Page components | ## Route Configuration Routes are defined in `src/router/routes.ts`: ```typescript export default [ { path: '/', name: 'home', component: () => import('@/pages/index.vue') }, { path: '/about', name: 'about', component: () => import('@/pages/about.vue') } ] ``` The router is created in `src/router/index.ts`: ```typescript import { createRouter, createWebHistory } from 'vue-router' import routes from './routes' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }) export default router ``` ## Layouts Layout components provide shared structure (header, footer, navigation) around page content. They live in `src/layouts/`. ```vue ``` Use layouts by wrapping page content: ```vue ``` Or use nested routes for layout-based routing: ```typescript { path: '/', component: () => import('@/layouts/AppLayout.vue'), children: [ { path: '', component: () => import('@/pages/index.vue') }, { path: 'about', component: () => import('@/pages/about.vue') } ] } ``` ## Pages Page components are the top-level components rendered for each route. They live in `src/pages/` and typically: - Use a layout component for consistent structure - Fetch data needed for the page - Compose smaller components together Keep pages focused on orchestration — extract complex logic into composables and UI into components. ## Navigation Guards Use navigation guards for authentication, authorization, and data fetching: ```typescript // src/router/routes.ts { path: '/dashboard', component: () => import('@/pages/dashboard.vue'), meta: { requiresAuth: true } } // src/router/index.ts router.beforeEach((to, from) => { const auth = useAuth() if (to.meta.requiresAuth && !auth.isAuthenticated) { return { name: 'login', query: { redirect: to.fullPath } } } }) ``` Define guards in the router (not components) because: - Guard logic often applies to multiple routes - Keeps page components focused on rendering - Easier to test in isolation ## Lazy Loading All routes use dynamic imports for automatic code splitting: ```typescript // Each page becomes a separate chunk component: () => import('@/pages/about.vue') ``` This means users only download the code for pages they visit, improving initial load time. For related pages that are often visited together, you can group them into a named chunk: ```typescript component: () => import(/* webpackChunkName: "settings" */ '@/pages/settings/profile.vue') ``` ================================================ FILE: docs/state.md ================================================ # State Management - [Overview](#overview) - [Defining Stores](#defining-stores) - [Using Stores](#using-stores) - [Store Organization](#store-organization) - [When to Use Stores](#when-to-use-stores) ## Overview This project uses [Pinia](https://pinia.vuejs.org/) for state management. Pinia is the official state management library for Vue 3, replacing Vuex. **Why Pinia over Vuex?** - Simpler API — no mutations, just state and actions - Full TypeScript support with type inference - Composition API style with `ref` and `computed` - Devtools integration - Lighter weight Stores live in `src/stores/`. ## Defining Stores Use the Composition API style (setup stores) for consistency with components: ```typescript // src/stores/authStore.ts import { ref, computed } from 'vue' import { defineStore } from 'pinia' export const useAuthStore = defineStore('authStore', () => { // State const user = ref(null) const token = ref(null) // Getters (computed) const isAuthenticated = computed(() => !!token.value) // Actions async function login(credentials: Credentials) { const response = await api.login(credentials) user.value = response.user token.value = response.token } function logout() { user.value = null token.value = null } return { user, token, isAuthenticated, login, logout } }) ``` ### Naming Conventions | Element | Convention | Example | |---------|------------|---------| | File name | `[domain]Store.ts` | `authStore.ts`, `cartStore.ts` | | Export name | `use[Domain]Store` | `useAuthStore`, `useCartStore` | | Store ID | `[domain]Store` | `'authStore'`, `'cartStore'` | **Avoid single-word names** — Be specific about what the store manages. Use `userProfileStore` instead of `user`, `shoppingCartStore` instead of `cart` if needed for clarity. ## Using Stores Import and use stores in components: ```vue ``` ### Destructuring with Reactivity Use `storeToRefs` to destructure while maintaining reactivity: ```vue ``` ## Store Organization Keep stores focused on a single domain: ``` src/stores/ ├── authStore.ts # Authentication state ├── cartStore.ts # Shopping cart ├── userStore.ts # User profile └── productStore.ts # Product catalog ``` ### Composing Stores Stores can use other stores: ```typescript // src/stores/cartStore.ts import { defineStore } from 'pinia' import { useAuthStore } from './authStore' export const useCartStore = defineStore('cartStore', () => { const authStore = useAuthStore() async function checkout() { if (!authStore.isAuthenticated) { throw new Error('Must be logged in to checkout') } // ... checkout logic } return { checkout } }) ``` ## When to Use Stores **Use Pinia stores for:** - Authentication state (current user, tokens) - Data shared across multiple components - Data that persists across route changes - Complex state with many actions **Use local component state for:** - Form inputs - UI state (modals, dropdowns) - Data used by a single component - Temporary state that resets on navigation **Rule of thumb:** Start with local state. Extract to a store when you need to share state across components or persist it across routes. ================================================ FILE: docs/tech.md ================================================ # Languages and Technologies - [TypeScript](#typescript) - [Why TypeScript](#why-typescript) - [Configuration](#configuration) - [TypeScript FAQ](#typescript-faq) - [Vue](#vue) - [Composition API](#composition-api) - [Vue Router](#vue-router) - [Pinia](#pinia) - [HTML](#html) - [Templates](#templates) - [Render Functions](#render-functions) - [CSS](#css) - [SCSS](#scss) - [Scoped Styles](#scoped-styles) - [Design Variables](#design-variables) - [Global CSS](#global-css) - [CSS FAQ](#css-faq) ## TypeScript This project uses TypeScript for type safety and improved developer experience. All `.ts` and `.vue` files are type-checked. ### Why TypeScript - **Catch errors early** — Type errors are caught at build time, not runtime - **Better IDE support** — Autocomplete, refactoring, and inline documentation - **Self-documenting code** — Types serve as documentation for function signatures - **Vue 3 designed for it** — Vue 3 and its ecosystem have first-class TypeScript support ### Configuration TypeScript is configured in multiple files: | File | Purpose | |------|---------| | `tsconfig.json` | Base configuration, references other configs | | `tsconfig.app.json` | Application code configuration | | `tsconfig.node.json` | Node.js tooling (Vite config, etc.) | | `tsconfig.vitest.json` | Test configuration | Key settings: ```json { "compilerOptions": { "strict": true, "noUnusedLocals": true, "noUnusedParameters": true } } ``` ### Migrating from JavaScript If migrating an existing JavaScript codebase, enable `allowJs` in `tsconfig.json` for progressive migration: ```json { "compilerOptions": { "allowJs": true, "checkJs": false } } ``` This allows `.js` and `.ts` files to coexist, so you can migrate file-by-file without being blocked by TypeScript errors across the entire codebase. Once migration is complete, disable `allowJs` to enforce TypeScript everywhere. **Migration strategy:** 1. Enable `allowJs`, disable `checkJs` 2. Rename files from `.js` to `.ts` one at a time 3. Fix type errors in each file as you convert 4. Once all files are `.ts`, disable `allowJs` ### TypeScript FAQ **How strict should we be with types?** Aim for full type coverage, but pragmatism over perfection. Use `any` sparingly and document why when you do. The `unknown` type is often a better choice when the type is truly unknown. **Should I use `interface` or `type`?** - Use `interface` for object shapes that might be extended - Use `type` for unions, primitives, and utility types ```typescript // Interface for objects interface User { id: string name: string } // Type for unions type Status = 'pending' | 'active' | 'completed' ``` ## Vue This project uses [Vue 3](https://vuejs.org/) with the Composition API. ### Composition API This boilerplate uses ` ``` ### Options API The Options API is still fully supported in Vue 3 and remains a valid choice: ```vue ``` ### Which API to Choose? Both APIs are production-ready. Choose based on your team's needs: | Factor | Composition API | Options API | |--------|-----------------|-------------| | TypeScript | Better type inference | Requires `defineComponent` wrapper | | Code organization | Organize by feature/concern | Organize by option type | | Learning curve | Steeper for Vue beginners | More familiar structure | | Logic reuse | Composables | Mixins (less flexible) | | Bundle size | Better tree-shaking | Slightly larger | **This boilerplate uses Composition API because:** - Better TypeScript integration - Composables are more flexible than mixins - Aligns with Vue ecosystem direction **Options API is a good choice when:** - Team is more familiar with it - Migrating from Vue 2 incrementally - Simpler components that don't need composables For a deeper comparison, read the [Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html). ### Vue Router [Vue Router 4](https://router.vuejs.org/) handles client-side routing. See [routing.md](routing.md) for project-specific patterns. Key resources: - [Getting Started](https://router.vuejs.org/guide/) - [Navigation Guards](https://router.vuejs.org/guide/advanced/navigation-guards.html) - [Route Meta Fields](https://router.vuejs.org/guide/advanced/meta.html) ### Pinia [Pinia](https://pinia.vuejs.org/) handles state management. See [state.md](state.md) for project-specific patterns. Key resources: - [Getting Started](https://pinia.vuejs.org/getting-started.html) - [Defining a Store](https://pinia.vuejs.org/core-concepts/) - [Using Stores](https://pinia.vuejs.org/core-concepts/#using-the-store) ## HTML All HTML lives in `.vue` files, either in `