├── public ├── _redirects ├── robots.txt └── index.html ├── lighthouse-score.jpg ├── src ├── state.ts ├── shims.d.ts ├── assets │ ├── app.css │ └── logo.svg ├── main.ts ├── App.vue ├── components │ └── Anchor.vue ├── views │ ├── Home.vue │ └── About.vue └── router.ts ├── .editorconfig ├── .gitignore ├── html.config.json ├── tsconfig.json ├── package.json ├── README.md └── webpack.config.js /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /lighthouse-score.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nkoehring/vue3-typescript-app-starter/HEAD/lighthouse-score.jpg -------------------------------------------------------------------------------- /src/state.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | export default reactive({ 4 | name: 'World' 5 | }) 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /src/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import { defineComponent } from "vue" 3 | const Component: ReturnType 4 | export default Component 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/app.css: -------------------------------------------------------------------------------- 1 | html,body { 2 | display: block; 3 | margin: 0; 4 | padding: 0; 5 | text-align: center; 6 | font-family: Helvetica, Arial, sans; 7 | } 8 | 9 | header { 10 | display: block; 11 | line-height: 4em; 12 | } 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import router from './router' 3 | import state from './state' 4 | 5 | import App from './App.vue' 6 | 7 | const app = createApp(App) 8 | app.provide('state', state) 9 | app.use(router) 10 | 11 | app.mount('#app') 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/components/Anchor.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 8 | 9 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /html.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "vue3-app-starter", 3 | "meta": { 4 | "viewport": "width=device-width,initial-scale=1.0", 5 | "description": "vue3 app starter with typescript support" 6 | }, 7 | "logo": "./src/assets/logo.svg", 8 | "favicons": { 9 | "icons": { 10 | "favicons": true, 11 | "android": true, 12 | "appleIcon": true, 13 | "appleStartup": false, 14 | "coast": false, 15 | "firefox": false, 16 | "yandex": false, 17 | "windows": false 18 | } 19 | }, 20 | "template": "./public/index.html" 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "declaration": false, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "noImplicitAny": false, 11 | "noLib": false, 12 | "sourceMap": true, 13 | "strict": true, 14 | "strictPropertyInitialization": false, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "target": "es2015", 17 | "baseUrl": ".", 18 | }, 19 | "exclude": [ 20 | "./node_modules" 21 | ], 22 | "include": [ 23 | "./src/**/*.ts", 24 | "./src/**/*.vue", 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /src/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import Home from '@/views/Home.vue' 3 | 4 | // webpackPrefetch supports numbers for prioritization. Higher numbers get loaded first. 5 | const AsyncAbout = () => import(/* webpackChunkName: "about", webpackPrefetch: 1 */'./views/About.vue') 6 | 7 | /// the following lines will be useful as soon as SSR is supported 8 | // const isServer = typeof window === 'undefined' 9 | // const history = isServer ? createMemoryHistory() : createWebHistory() 10 | 11 | const history = createWebHistory() 12 | 13 | export default createRouter({ 14 | history, 15 | strict: true, 16 | routes: [ 17 | { path: '/', name: 'Home', component: Home }, 18 | // async component gets prefetched in supporting browsers 19 | { path: '/about', name: 'About', component: AsyncAbout } 20 | ] 21 | }) 22 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-typescript-app-starter", 3 | "version": "0.2.0", 4 | "description": "A pretty functional app starter for Vue3 and Typescript", 5 | "main": "src/main.ts", 6 | "author": "koehr ", 7 | "license": "MIT", 8 | "private": true, 9 | "scripts": { 10 | "dev": "webpack-dev-server", 11 | "build": "webpack --env.prod --progress" 12 | }, 13 | "dependencies": { 14 | "vue": "3.0.0-beta.15", 15 | "vue-router": "4.0.0-alpha.12" 16 | }, 17 | "devDependencies": { 18 | "@vue/compiler-sfc": "3.0.0-beta.15", 19 | "copy-webpack-plugin": "^6.0.2", 20 | "css-loader": "^3.6.0", 21 | "favicons-webpack-plugin": "^4.2.0", 22 | "file-loader": "^6.0.0", 23 | "html-webpack-plugin": "^4.3.0", 24 | "raw-loader": "^4.0.1", 25 | "style-loader": "^1.2.0", 26 | "ts-loader": "^7.0.5", 27 | "typescript": "^3.9.5", 28 | "url-loader": "^4.1.0", 29 | "vue-loader": "16.0.0-beta.3", 30 | "webpack": "^4.43.0", 31 | "webpack-cli": "^3.3.11", 32 | "webpack-dev-server": "^3.11.0", 33 | "webpack-subresource-integrity": "^1.5.1", 34 | "yarn": "^1.22.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue3-typescript-app-starter 2 | 3 | This is a starter setup for your next Vue3 / Typescript app. 4 | 5 | It uses Vue3 beta, typescript and webpack and supports some neat features: 6 | 7 | * router 8 | * lightweight global state (not Vuex) 9 | * typescript 10 | * code splitting and tree shaking 11 | * inserts base64 URLs for small assets (<=8kb) 12 | * splits large scripts into chunks to be loaded in parallel 13 | * prefetches async components (if browser supports prefetch hint) 14 | * automatically generates favicons and app icons out of your logo 15 | * SRI (adds integrity hashes to script tags, see [SRI on MDN](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)) 16 | 17 | ## Usage 18 | 19 | To use it you may simply fork this repository or use a tool like [degit](https://github.com/Rich-Harris/degit), that downloads only the code without git history for you: 20 | 21 | ```sh 22 | yarn global add degit # or npm install -g degit 23 | cd /path/to/your/project 24 | degit 'nkoehring/vue3-typescript-app-starter#main' # degit defaults to master 25 | ``` 26 | 27 | As soon as you have the code, you can go the "typical" path, assuming you're inside your new projects directory: 28 | 29 | ```sh 30 | yarn # or npm install 31 | yarn build # or npm run build # builds the application 32 | yarn dev # or npm run dev # runs a live-reload dev server 33 | ``` 34 | 35 | You'll find the running application at [localhost:8080](http://localhost:8080) or subsequent ports in case something already runs on 8080. 36 | 37 | ## Performance 38 | 39 | ![lighthouse score](./lighthouse-score.jpg) 40 | 41 | See for yourself [on lighthouse](https://googlechrome.github.io/lighthouse/viewer/?psiurl=https%3A%2F%2Fvue3-app-starter.netlify.app%2F&strategy=mobile&category=performance&category=accessibility&category=best-practices&category=seo) 42 | 43 | ## Contributing 44 | 45 | This happened over night after setting up a new Vue3 application. It might have lots of strange quirks and misconfigurations. 46 | 47 | Please help to make this starter setup even better by writing an issue or pull request. 48 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json') 2 | const { resolve } = require('path') 3 | const { VueLoaderPlugin } = require('vue-loader') 4 | const CopyWebpackPlugin = require('copy-webpack-plugin') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | const FaviconsWebpackPlugin = require('favicons-webpack-plugin') 7 | const SriPlugin = require('webpack-subresource-integrity') 8 | 9 | const htmlConfig = require('./html.config.json') || {} 10 | const outputPath = resolve(__dirname, './dist') 11 | const publicPath = resolve(__dirname, './public') 12 | 13 | module.exports = (env = {}) => ({ 14 | mode: env.prod ? 'production' : 'development', 15 | devtool: env.prod ? false : 'eval-source-map', 16 | entry: resolve(__dirname, './src/main.ts'), 17 | output: { 18 | path: outputPath, 19 | crossOriginLoading: 'anonymous' 20 | }, 21 | optimization: { 22 | splitChunks: { 23 | chunks: 'async', 24 | minSize: 32000, 25 | maxSize: 48000 26 | } 27 | }, 28 | module: { 29 | rules: [{ 30 | test: /\.vue$/i, 31 | use: 'vue-loader' 32 | }, { 33 | test: /\.ts$/i, 34 | loader: 'ts-loader', 35 | options: { appendTsSuffixTo: [/\.vue$/] } 36 | }, { 37 | test: /\.css$/i, 38 | use: ['style-loader', 'css-loader'] 39 | }, { 40 | test: /\.(png|jpg|gif)$/i, 41 | loader: 'url-loader', 42 | options: { limit: 8192 } 43 | }, { 44 | test: /\.(png|jpg|gif|svg)$/i, 45 | loader: 'file-loader', 46 | options: { 47 | name (/*resourcePath, resourceQuery*/) { 48 | // see https://github.com/webpack-contrib/file-loader 49 | // `resourcePath` - `/absolute/path/to/file.js` 50 | // `resourceQuery` - `?foo=bar` 51 | return env.prod ? '[contenthash].[ext]' : '[path][name].[ext]' 52 | } 53 | } 54 | }, { 55 | test: /\.(txt|raw)$/i, 56 | use: 'raw-loader' 57 | }] 58 | }, 59 | resolve: { 60 | extensions: ['.ts', '.js', '.vue', '.json'], 61 | alias: { 62 | 'vue': '@vue/runtime-dom', 63 | '@': resolve(__dirname, './src/'), 64 | } 65 | }, 66 | plugins: [ 67 | new VueLoaderPlugin(), 68 | new CopyWebpackPlugin({ 69 | patterns: [{ from: publicPath, to: outputPath }] 70 | }), 71 | new HtmlWebpackPlugin({ 72 | title: htmlConfig.title || pkg.name, 73 | meta: htmlConfig.meta || {}, 74 | // TODO: not setting template option kinda breaks the build 75 | template: resolve(__dirname, htmlConfig.template || './index.html'), 76 | scriptLoading: htmlConfig.scriptLoading || 'defer', 77 | hash: true 78 | }), 79 | new FaviconsWebpackPlugin({ 80 | logo: htmlConfig.logo || './logo.png', 81 | // see https://github.com/itgalaxy/favicons#usage 82 | favicons: htmlConfig.favicons || {} 83 | }), 84 | new SriPlugin({ 85 | hashFuncNames: ['sha512'], 86 | enabled: env.prod 87 | }) 88 | ], 89 | devServer: { 90 | inline: true, 91 | hot: true, 92 | stats: 'minimal', 93 | contentBase: resolve(__dirname, 'dist'), 94 | overlay: true 95 | } 96 | }) 97 | --------------------------------------------------------------------------------