├── .browserslistrc ├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .gitattributes ├── .gitignore ├── .npmrc ├── .prettierrc.js ├── .stylelintrc.js ├── LICENSE ├── README.md ├── babel.config.js ├── eslint.config.mjs ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico └── index.html ├── service ├── commands │ ├── build.js │ └── dev.js ├── config │ ├── base.js │ ├── css.js │ ├── dev.js │ ├── prod.js │ └── terserOptions.js ├── project.config.js └── utils │ ├── getLocalIP.js │ ├── loadEnv.js │ ├── logger.js │ ├── paths.js │ ├── resolveClientEnv.js │ └── spinner.js ├── src ├── App.vue ├── assets │ ├── fonts │ │ └── .gitkeep │ ├── images │ │ └── .gitkeep │ └── styles │ │ └── .gitkeep ├── components │ └── .gitkeep ├── main.ts ├── router │ └── index.ts ├── stores │ └── main.ts └── views │ ├── about │ └── index.vue │ └── home │ ├── components │ └── hello-world.vue │ ├── images │ └── logo.png │ └── index.vue └── tsconfig.json /.browserslistrc: -------------------------------------------------------------------------------- 1 | [production] 2 | > 1% 3 | last 2 versions 4 | not dead 5 | 6 | [development] 7 | last 1 chrome version 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset=utf-8 5 | end_of_line=lf 6 | insert_final_newline=true 7 | indent_style=space 8 | indent_size=2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VUE_APP_TITLE=Vue3 Boilerplate 2 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_API_BASE_URL=https://development.example.com 2 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_API_BASE_URL=https://production.example.com 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.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 | 23 | # Git 24 | *.diff 25 | *.patch 26 | *.bak 27 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | semi: false, 4 | singleQuote: true, 5 | } 6 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['stylelint-config-standard', 'stylelint-config-standard-scss'], 3 | plugins: ['stylelint-order'], 4 | ignoreFiles: ['node_modules/**', 'src/assets/fonts/**', 'src/assets/style/reset.css'], 5 | overrides: [ 6 | { 7 | files: ['*.vue', '**/*.vue'], 8 | customSyntax: 'postcss-html', 9 | }, 10 | ], 11 | rules: { 12 | 'at-rule-no-unknown': [ 13 | true, 14 | { ignoreAtRules: ['extends', 'ignores', 'include', 'mixin', 'if', 'else', 'media', 'for'] }, 15 | ], 16 | 'order/order': ['custom-properties', 'declarations'], 17 | 'order/properties-order': ['width', 'height'], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jamie Yang 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 | # vue3-boilerplate 2 | 3 | A Vue 3 Starter Boilerplate with Vue Router 4, Pinia 2, Typescript 5, Webpack 5, Prettier and More. 4 | 5 | **And not using the Vue CLI.** 6 | 7 | ## Architecture 8 | 9 | ```text 10 | ├─ public // static assets. 11 | ├─ service // commands and webpack configurations. 12 | ├─ src 13 | │ ├─ assets // assets such as images or font files. 14 | │ ├─ components // universal Vue components. 15 | │ ├─ router // view's routers config. 16 | │ ├─ stores // Pinia stores. 17 | │ ├─ typings // typescript .d.ts files. 18 | │ └─ views // pages. 19 | ``` 20 | 21 | ## Commands 22 | 23 | ```bash 24 | # Start development server. 25 | yarn dev 26 | 27 | # Compile production bundle. 28 | yarn build 29 | ``` 30 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | useBuiltIns: 'usage', // adds specific imports for polyfills when they are used in each file. 7 | modules: false, // preserve ES modules. 8 | corejs: { version: 3, proposals: true }, // enable polyfilling of every proposal supported by core-js. 9 | }, 10 | ], 11 | ], 12 | plugins: [ 13 | '@babel/plugin-transform-runtime', // enables the re-use of Babel's injected helper code to save on codesize. 14 | ], 15 | exclude: [/core-js/], 16 | } 17 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals' 2 | import js from '@eslint/js' 3 | import pluginTs from 'typescript-eslint' 4 | import pluginVue from 'eslint-plugin-vue' 5 | import configPrettier from 'eslint-config-prettier' 6 | 7 | export default [ 8 | js.configs.recommended, 9 | ...pluginTs.configs.recommended, 10 | ...pluginVue.configs['flat/recommended'], 11 | configPrettier, 12 | 13 | { 14 | languageOptions: { 15 | globals: { 16 | ...globals.browser, 17 | }, 18 | ecmaVersion: 2024, 19 | parserOptions: { 20 | ecmaFeatures: { jsx: true }, 21 | }, 22 | }, 23 | 24 | rules: { 25 | 'arrow-body-style': ['error', 'as-needed'], 26 | 'prefer-arrow-callback': ['error', { allowNamedFunctions: true }], 27 | }, 28 | }, 29 | 30 | { 31 | files: ['service/**/*.js', '.prettierrc.js', '.stylelintrc.js', 'babel.config.js'], 32 | 33 | languageOptions: { 34 | globals: { 35 | ...globals.node, 36 | }, 37 | }, 38 | 39 | rules: { 40 | '@typescript-eslint/no-var-requires': 'off', 41 | '@typescript-eslint/no-require-imports': 'off', 42 | }, 43 | }, 44 | 45 | { 46 | ignores: ['dist'], 47 | }, 48 | ] 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-boilerplate", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "node service/commands/dev.js", 7 | "build": "node service/commands/build.js", 8 | "lint": "npm run lint:prettier && npm run lint:eslint && npm run lint:stylelint", 9 | "lint:prettier": "prettier . --write --log-level warn", 10 | "lint:eslint": "eslint --fix", 11 | "lint:stylelint": "stylelint \"./src/**/*.vue\" \"./src/**/*.scss\" --fix" 12 | }, 13 | "dependencies": { 14 | "@babel/runtime": "^7.26.9", 15 | "core-js": "^3.41.0", 16 | "pinia": "^3.0.1", 17 | "vue": "^3.5.13", 18 | "vue-router": "^4.5.0" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "^7.26.9", 22 | "@babel/plugin-transform-runtime": "^7.26.9", 23 | "@babel/preset-env": "^7.26.9", 24 | "@eslint/js": "^9.22.0", 25 | "@types/webpack-env": "^1.18.8", 26 | "@typescript-eslint/eslint-plugin": "8.26.0", 27 | "@typescript-eslint/parser": "8.26.0", 28 | "@vue/compiler-sfc": "^3.5.13", 29 | "autoprefixer": "^10.4.20", 30 | "babel-loader": "^10.0.0", 31 | "case-sensitive-paths-webpack-plugin": "^2.4.0", 32 | "chalk": "^4.1.2", 33 | "copy-webpack-plugin": "^13.0.0", 34 | "css-loader": "^7.1.2", 35 | "dotenv": "^16.4.7", 36 | "dotenv-expand": "^12.0.1", 37 | "eslint": "^9.22.0", 38 | "eslint-config-prettier": "^10.1.1", 39 | "eslint-formatter-friendly": "^7.0.0", 40 | "eslint-plugin-vue": "^10.0.0", 41 | "eslint-webpack-plugin": "^5.0.0", 42 | "globals": "^16.0.0", 43 | "html-webpack-plugin": "^5.6.3", 44 | "mini-css-extract-plugin": "^2.9.2", 45 | "ora": "^5.4.1", 46 | "postcss": "^8.5.3", 47 | "postcss-html": "^1.8.0", 48 | "postcss-loader": "^8.1.1", 49 | "prettier": "3.5.3", 50 | "rimraf": "^6.0.1", 51 | "sass": "^1.85.1", 52 | "sass-loader": "^16.0.5", 53 | "strip-ansi": "^6.0.0", 54 | "style-loader": "^4.0.0", 55 | "stylelint": "^16.15.0", 56 | "stylelint-config-standard": "^37.0.0", 57 | "stylelint-config-standard-scss": "^14.0.0", 58 | "stylelint-order": "^6.0.4", 59 | "terser-webpack-plugin": "^5.3.14", 60 | "thread-loader": "^4.0.4", 61 | "ts-loader": "^9.5.2", 62 | "typescript": "^5.8.2", 63 | "typescript-eslint": "8.26.0", 64 | "vue-loader": "^17.4.2", 65 | "vue-style-loader": "^4.1.3", 66 | "webpack": "^5.98.0", 67 | "webpack-dev-server": "^5.2.0", 68 | "webpack-merge": "^6.0.1" 69 | }, 70 | "engines": { 71 | "node": ">=18.18.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jamie-Yang/vue3-boilerplate/cd07bf271102f888e2921fba0a7ceacd8a1c85e5/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= VUE_APP_TITLE %> 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /service/commands/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const loadEnv = require('../utils/loadEnv') 4 | loadEnv() 5 | loadEnv('production') 6 | 7 | const { rimraf } = require('rimraf') 8 | const webpack = require('webpack') 9 | 10 | const { error, done } = require('../utils/logger') 11 | const { logWithSpinner, stopSpinner } = require('../utils/spinner') 12 | const paths = require('../utils/paths') 13 | 14 | const webpackConfig = require('../config/prod') 15 | const config = require('../project.config') 16 | 17 | logWithSpinner('Building for production...\n') 18 | 19 | rimraf(paths.resolve(config.outputDir)).then(() => { 20 | webpack(webpackConfig, (err, stats) => { 21 | stopSpinner(false) 22 | 23 | if (err) throw err 24 | 25 | process.stdout.write( 26 | stats.toString({ 27 | colors: true, 28 | modules: false, 29 | children: false, 30 | chunks: false, 31 | chunkModules: false, 32 | }) + '\n\n', 33 | ) 34 | 35 | if (stats.hasErrors()) { 36 | error('Build failed with errors.\n') 37 | process.exit(1) 38 | } 39 | 40 | done('Build complete.\n') 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /service/commands/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const loadEnv = require('../utils/loadEnv') 4 | loadEnv() 5 | loadEnv('development') 6 | 7 | const chalk = require('chalk') 8 | const webpack = require('webpack') 9 | const WebpackDevServer = require('webpack-dev-server') 10 | 11 | const { info } = require('../utils/logger') 12 | const getLocalIP = require('../utils/getLocalIP') 13 | 14 | const devWebpackConfig = require('../config/dev') 15 | 16 | const devServerOptions = devWebpackConfig.devServer 17 | const protocol = devServerOptions.https ? 'https' : 'http' 18 | const host = devServerOptions.host || '0.0.0.0' 19 | const port = devServerOptions.port || 8080 20 | 21 | info('Starting development server...') 22 | 23 | const compiler = webpack(devWebpackConfig) 24 | const server = new WebpackDevServer(devServerOptions, compiler) 25 | 26 | compiler.hooks.done.tap('serve', (stats) => { 27 | if (stats.hasErrors()) { 28 | return 29 | } 30 | console.log() 31 | console.log() 32 | console.log(`App running at:`) 33 | console.log(` - Local: ${chalk.cyan(`${protocol}://${host}:${port}`)}`) 34 | console.log(` - Network: ${chalk.cyan(`${protocol}://${getLocalIP()}:${port}`)}`) 35 | console.log() 36 | }) 37 | 38 | server.start(port, host, (err) => { 39 | if (err) { 40 | process.exit(0) 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /service/config/base.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { DefinePlugin } = require('webpack') 4 | const { VueLoaderPlugin } = require('vue-loader') 5 | const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin') 6 | const ESLintPlugin = require('eslint-webpack-plugin') 7 | const HTMLPlugin = require('html-webpack-plugin') 8 | const CopyPlugin = require('copy-webpack-plugin') 9 | 10 | const resolveClientEnv = require('../utils/resolveClientEnv') 11 | const paths = require('../utils/paths') 12 | 13 | const config = require('../project.config') 14 | 15 | const isProd = process.env.NODE_ENV === 'production' 16 | const outputFileName = `js/[name]${isProd ? '.[contenthash:8]' : ''}.js` 17 | 18 | module.exports = { 19 | context: process.cwd(), 20 | 21 | entry: { 22 | app: './src/main.ts', 23 | }, 24 | 25 | output: { 26 | path: paths.resolve(config.outputDir), 27 | publicPath: config.dev.publicPath, 28 | filename: outputFileName, 29 | chunkFilename: outputFileName, 30 | }, 31 | 32 | resolve: { 33 | alias: { 34 | '@': paths.resolve('src'), 35 | }, 36 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue', '.json'], 37 | }, 38 | 39 | plugins: [ 40 | new ESLintPlugin({ 41 | emitError: true, 42 | emitWarning: true, 43 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue'], 44 | formatter: require('eslint-formatter-friendly'), 45 | configType: 'flat', 46 | eslintPath: 'eslint/use-at-your-own-risk', 47 | }), 48 | new VueLoaderPlugin(), 49 | new CaseSensitivePathsPlugin(), 50 | new HTMLPlugin({ 51 | template: paths.resolve('public/index.html'), 52 | templateParameters: { 53 | ...resolveClientEnv( 54 | { publicPath: isProd ? config.build.publicPath : config.dev.publicPath }, 55 | true /* raw */, 56 | ), 57 | }, 58 | }), 59 | new CopyPlugin({ 60 | patterns: [ 61 | { 62 | from: paths.resolve('public'), 63 | toType: 'dir', 64 | globOptions: { 65 | ignore: ['.DS_Store', '**/index.html'], 66 | }, 67 | noErrorOnMissing: true, 68 | }, 69 | ], 70 | }), 71 | new DefinePlugin({ 72 | // vue3 feature flags 73 | __VUE_OPTIONS_API__: 'true', 74 | __VUE_PROD_DEVTOOLS__: 'false', 75 | __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false', 76 | 77 | ...resolveClientEnv({ 78 | publicPath: isProd ? config.build.publicPath : config.dev.publicPath, 79 | }), 80 | }), 81 | ], 82 | 83 | module: { 84 | noParse: /^(vue|vue-router|pinia)$/, 85 | 86 | rules: [ 87 | { 88 | test: /\.vue$/, 89 | loader: 'vue-loader', 90 | }, 91 | 92 | // babel 93 | { 94 | test: /\.m?jsx?$/, 95 | exclude: (file) => { 96 | // always transpile js in vue files 97 | if (/\.vue\.jsx?$/.test(file)) { 98 | return false 99 | } 100 | // Don't transpile node_modules 101 | return /node_modules/.test(file) 102 | }, 103 | use: ['thread-loader', 'babel-loader'], 104 | }, 105 | 106 | // ts 107 | { 108 | test: /\.tsx?$/, 109 | use: [ 110 | 'thread-loader', 111 | 'babel-loader', 112 | { 113 | loader: 'ts-loader', 114 | options: { 115 | transpileOnly: true, 116 | appendTsSuffixTo: ['\\.vue$'], 117 | happyPackMode: true, 118 | }, 119 | }, 120 | ], 121 | }, 122 | 123 | // images 124 | { 125 | test: /\.(png|jpe?g|gif|webp)(\?.*)?$/, 126 | type: 'asset', 127 | generator: { filename: 'img/[contenthash:8][ext][query]' }, 128 | }, 129 | 130 | // do not base64-inline SVGs. 131 | // https://github.com/facebookincubator/create-react-app/pull/1180 132 | { 133 | test: /\.(svg)(\?.*)?$/, 134 | type: 'asset/resource', 135 | generator: { filename: 'img/[contenthash:8][ext][query]' }, 136 | }, 137 | 138 | // media 139 | { 140 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 141 | type: 'asset', 142 | generator: { filename: 'media/[contenthash:8][ext][query]' }, 143 | }, 144 | 145 | // fonts 146 | { 147 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, 148 | type: 'asset', 149 | generator: { filename: 'fonts/[contenthash:8][ext][query]' }, 150 | }, 151 | ], 152 | }, 153 | } 154 | -------------------------------------------------------------------------------- /service/config/css.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 4 | 5 | const isProd = process.env.NODE_ENV === 'production' 6 | 7 | const plugins = [] 8 | if (isProd) { 9 | const filename = 'css/[name].[contenthash:8].css' 10 | 11 | plugins.push( 12 | new MiniCssExtractPlugin({ 13 | filename, 14 | chunkFilename: filename, 15 | }), 16 | ) 17 | } 18 | 19 | const genStyleRules = () => { 20 | const cssLoader = { 21 | loader: 'css-loader', 22 | options: { 23 | // how many loaders before css-loader should be applied to [@import]ed resources. 24 | // stylePostLoader injected by vue-loader + postcss-loader 25 | importLoaders: 1 + 1, 26 | }, 27 | } 28 | const postcssLoader = { 29 | loader: 'postcss-loader', 30 | options: { 31 | postcssOptions: { 32 | plugins: [require('autoprefixer')], 33 | }, 34 | }, 35 | } 36 | const extractPluginLoader = { 37 | loader: MiniCssExtractPlugin.loader, 38 | } 39 | const vueStyleLoader = { 40 | loader: 'vue-style-loader', 41 | } 42 | 43 | function createCSSRule(test, loader, loaderOptions) { 44 | const loaders = [cssLoader, postcssLoader] 45 | 46 | if (isProd) { 47 | loaders.unshift(extractPluginLoader) 48 | } else { 49 | loaders.unshift(vueStyleLoader) 50 | } 51 | 52 | if (loader) { 53 | loaders.push({ loader, options: loaderOptions }) 54 | } 55 | 56 | return { test, use: loaders } 57 | } 58 | 59 | return [ 60 | createCSSRule(/\.css$/), 61 | createCSSRule(/\.p(ost)?css$/), 62 | createCSSRule(/\.scss$/, 'sass-loader'), 63 | ] 64 | } 65 | 66 | module.exports = { 67 | plugins, 68 | module: { 69 | rules: genStyleRules(), 70 | }, 71 | } 72 | -------------------------------------------------------------------------------- /service/config/dev.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { merge } = require('webpack-merge') 4 | 5 | const baseWebpackConfig = require('./base') 6 | const cssWebpackConfig = require('./css') 7 | const config = require('../project.config') 8 | 9 | module.exports = merge(baseWebpackConfig, cssWebpackConfig, { 10 | mode: 'development', 11 | 12 | devtool: 'eval-cheap-module-source-map', 13 | 14 | devServer: { 15 | historyApiFallback: { 16 | rewrites: [{ from: /./, to: '/index.html' }], 17 | }, 18 | devMiddleware: { 19 | publicPath: config.dev.publicPath, 20 | }, 21 | open: false, 22 | host: '0.0.0.0', 23 | port: config.dev.port, 24 | liveReload: false, 25 | }, 26 | 27 | infrastructureLogging: { 28 | level: 'warn', 29 | }, 30 | 31 | stats: { 32 | assets: false, 33 | modules: false, 34 | }, 35 | }) 36 | -------------------------------------------------------------------------------- /service/config/prod.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { merge } = require('webpack-merge') 4 | const TerserPlugin = require('terser-webpack-plugin') 5 | 6 | const baseWebpackConfig = require('./base') 7 | const cssWebpackConfig = require('./css') 8 | const config = require('../project.config') 9 | const terserOptions = require('./terserOptions') 10 | 11 | module.exports = merge(baseWebpackConfig, cssWebpackConfig, { 12 | mode: 'production', 13 | 14 | output: { 15 | publicPath: config.build.publicPath, 16 | }, 17 | 18 | optimization: { 19 | minimize: true, 20 | minimizer: [new TerserPlugin(terserOptions())], 21 | moduleIds: 'deterministic', 22 | splitChunks: { 23 | cacheGroups: { 24 | defaultVendors: { 25 | name: `chunk-vendors`, 26 | test: /[\\/]node_modules[\\/]/, 27 | priority: -10, 28 | chunks: 'initial', 29 | }, 30 | common: { 31 | name: `chunk-common`, 32 | minChunks: 2, 33 | priority: -20, 34 | chunks: 'initial', 35 | reuseExistingChunk: true, 36 | }, 37 | }, 38 | }, 39 | }, 40 | }) 41 | -------------------------------------------------------------------------------- /service/config/terserOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = () => ({ 4 | terserOptions: { 5 | compress: { 6 | // turn off flags with small gains to speed up minification 7 | arrows: false, 8 | collapse_vars: false, // 0.3kb 9 | comparisons: false, 10 | computed_props: false, 11 | hoist_funs: false, 12 | hoist_props: false, 13 | hoist_vars: false, 14 | inline: false, 15 | loops: false, 16 | negate_iife: false, 17 | properties: false, 18 | reduce_funcs: false, 19 | reduce_vars: false, 20 | switches: false, 21 | toplevel: false, 22 | typeofs: false, 23 | 24 | // a few flags with noticable gains/speed ratio 25 | // numbers based on out of the box vendor bundle 26 | booleans: true, // 0.7kb 27 | if_return: true, // 0.4kb 28 | sequences: true, // 0.7kb 29 | unused: true, // 2.3kb 30 | 31 | // required features to drop conditional branches 32 | conditionals: true, 33 | dead_code: true, 34 | evaluate: true, 35 | }, 36 | mangle: { 37 | safari10: true, 38 | }, 39 | }, 40 | // parallel: options.parallel, 41 | extractComments: false, 42 | }) 43 | -------------------------------------------------------------------------------- /service/project.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | outputDir: 'dist', 5 | 6 | dev: { 7 | publicPath: '/', 8 | port: 8080, 9 | }, 10 | 11 | build: { 12 | publicPath: '/', 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /service/utils/getLocalIP.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const os = require('os') 4 | 5 | module.exports = function getLocalIP() { 6 | const interfaces = os.networkInterfaces() 7 | 8 | for (const devName in interfaces) { 9 | const iface = interfaces[devName] 10 | for (let i = 0; i < iface.length; i++) { 11 | const alias = iface[i] 12 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 13 | return alias.address 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /service/utils/loadEnv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const dotenv = require('dotenv') 5 | const dotenvExpand = require('dotenv-expand') 6 | const { error } = require('./logger') 7 | 8 | module.exports = function loadEnv(mode) { 9 | const basePath = path.resolve(process.cwd(), `.env${mode ? `.${mode}` : ``}`) 10 | const localPath = `${basePath}.local` 11 | 12 | const load = (envPath) => { 13 | try { 14 | const env = dotenv.config({ path: envPath, debug: process.env.DEBUG }) 15 | dotenvExpand.expand(env) 16 | } catch (err) { 17 | // only ignore error if file is not found 18 | if (err.toString().indexOf('ENOENT') < 0) { 19 | error(err) 20 | } 21 | } 22 | } 23 | 24 | load(localPath) 25 | load(basePath) 26 | 27 | // by default, NODE_ENV and BABEL_ENV are set to "development" unless mode 28 | // is production or test. However the value in .env files will take higher 29 | // priority. 30 | if (mode) { 31 | const defaultNodeEnv = mode === 'production' || mode === 'test' ? mode : 'development' 32 | if (process.env.NODE_ENV == null) { 33 | process.env.NODE_ENV = defaultNodeEnv 34 | } 35 | if (process.env.BABEL_ENV == null) { 36 | process.env.BABEL_ENV = defaultNodeEnv 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /service/utils/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const stripAnsi = require('strip-ansi') 5 | const readline = require('readline') 6 | const EventEmitter = require('events') 7 | 8 | exports.events = new EventEmitter() 9 | 10 | function _log(type, tag, message) { 11 | if (process.env.VUE_CLI_API_MODE && message) { 12 | exports.events.emit('log', { message, type, tag }) 13 | } 14 | } 15 | 16 | const format = (label, msg) => 17 | msg 18 | .split('\n') 19 | .map((line, i) => (i === 0 ? `${label} ${line}` : line.padStart(stripAnsi(label).length))) 20 | .join('\n') 21 | 22 | const chalkTag = (msg) => chalk.bgBlackBright.white.dim(` ${msg} `) 23 | 24 | exports.log = (msg = '', tag = null) => { 25 | console.log(tag ? format(chalkTag(tag), msg) : msg) 26 | _log('log', tag, msg) 27 | } 28 | 29 | exports.info = (msg, tag = null) => { 30 | console.log(format(chalk.bgBlue.black(' INFO ') + (tag ? chalkTag(tag) : ''), msg)) 31 | _log('info', tag, msg) 32 | } 33 | 34 | exports.done = (msg, tag = null) => { 35 | console.log(format(chalk.bgGreen.black(' DONE ') + (tag ? chalkTag(tag) : ''), msg)) 36 | _log('done', tag, msg) 37 | } 38 | 39 | exports.warn = (msg, tag = null) => { 40 | console.warn( 41 | format(chalk.bgYellow.black(' WARN ') + (tag ? chalkTag(tag) : ''), chalk.yellow(msg)), 42 | ) 43 | _log('warn', tag, msg) 44 | } 45 | 46 | exports.error = (msg, tag = null) => { 47 | console.error(format(chalk.bgRed(' ERROR ') + (tag ? chalkTag(tag) : ''), chalk.red(msg))) 48 | _log('error', tag, msg) 49 | if (msg instanceof Error) { 50 | console.error(msg.stack) 51 | _log('error', tag, msg.stack) 52 | } 53 | } 54 | 55 | exports.clearConsole = (title) => { 56 | if (process.stdout.isTTY) { 57 | const blank = '\n'.repeat(process.stdout.rows) 58 | console.log(blank) 59 | readline.cursorTo(process.stdout, 0, 0) 60 | readline.clearScreenDown(process.stdout) 61 | if (title) { 62 | console.log(title) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /service/utils/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | // gen static file path 6 | exports.getAssetPath = (...args) => path.posix.join('static', ...args) 7 | 8 | // gen absolute path 9 | exports.resolve = (...args) => path.posix.join(process.cwd(), ...args) 10 | -------------------------------------------------------------------------------- /service/utils/resolveClientEnv.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const prefixRE = /^VUE_APP_/ 4 | 5 | module.exports = function resolveClientEnv(options, raw) { 6 | const env = {} 7 | Object.keys(process.env).forEach((key) => { 8 | if (prefixRE.test(key) || key === 'NODE_ENV') { 9 | env[key] = process.env[key] 10 | } 11 | }) 12 | env.PUBLIC_PATH = options.publicPath 13 | 14 | if (raw) { 15 | return env 16 | } 17 | 18 | for (const key in env) { 19 | env[key] = JSON.stringify(env[key]) 20 | } 21 | return { 22 | 'process.env': env, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /service/utils/spinner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ora = require('ora') 4 | const chalk = require('chalk') 5 | 6 | const spinner = ora() 7 | let lastMsg = null 8 | let isPaused = false 9 | 10 | exports.logWithSpinner = (symbol, msg) => { 11 | if (!msg) { 12 | msg = symbol 13 | symbol = chalk.green('✔') 14 | } 15 | if (lastMsg) { 16 | spinner.stopAndPersist({ 17 | symbol: lastMsg.symbol, 18 | text: lastMsg.text, 19 | }) 20 | } 21 | spinner.text = ' ' + msg 22 | lastMsg = { 23 | symbol: symbol + ' ', 24 | text: msg, 25 | } 26 | spinner.start() 27 | } 28 | 29 | exports.stopSpinner = (persist) => { 30 | if (lastMsg && persist !== false) { 31 | spinner.stopAndPersist({ 32 | symbol: lastMsg.symbol, 33 | text: lastMsg.text, 34 | }) 35 | } else { 36 | spinner.stop() 37 | } 38 | lastMsg = null 39 | } 40 | 41 | exports.pauseSpinner = () => { 42 | if (spinner.isSpinning) { 43 | spinner.stop() 44 | isPaused = true 45 | } 46 | } 47 | 48 | exports.resumeSpinner = () => { 49 | if (isPaused) { 50 | spinner.start() 51 | isPaused = false 52 | } 53 | } 54 | 55 | exports.failSpinner = (text) => { 56 | spinner.fail(text) 57 | } 58 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 31 | -------------------------------------------------------------------------------- /src/assets/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jamie-Yang/vue3-boilerplate/cd07bf271102f888e2921fba0a7ceacd8a1c85e5/src/assets/fonts/.gitkeep -------------------------------------------------------------------------------- /src/assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jamie-Yang/vue3-boilerplate/cd07bf271102f888e2921fba0a7ceacd8a1c85e5/src/assets/images/.gitkeep -------------------------------------------------------------------------------- /src/assets/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jamie-Yang/vue3-boilerplate/cd07bf271102f888e2921fba0a7ceacd8a1c85e5/src/assets/styles/.gitkeep -------------------------------------------------------------------------------- /src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jamie-Yang/vue3-boilerplate/cd07bf271102f888e2921fba0a7ceacd8a1c85e5/src/components/.gitkeep -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import { createPinia } from 'pinia' 5 | 6 | createApp(App).use(router).use(createPinia()).mount('#app') 7 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw, createRouter, createWebHashHistory } from 'vue-router' 2 | 3 | const routes: Array = [ 4 | { 5 | path: '/', 6 | name: 'Home', 7 | component: () => import(/* webpackChunkName: "home" */ '../views/home/index.vue'), 8 | }, 9 | { 10 | path: '/about', 11 | name: 'About', 12 | component: () => import(/* webpackChunkName: "about" */ '../views/about/index.vue'), 13 | }, 14 | ] 15 | 16 | const router = createRouter({ 17 | history: createWebHashHistory(), 18 | routes, 19 | }) 20 | 21 | export default router 22 | -------------------------------------------------------------------------------- /src/stores/main.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useMainStore = defineStore('main', { 4 | state: () => ({}), 5 | actions: {}, 6 | }) 7 | -------------------------------------------------------------------------------- /src/views/about/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/views/home/components/hello-world.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /src/views/home/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jamie-Yang/vue3-boilerplate/cd07bf271102f888e2921fba0a7ceacd8a1c85e5/src/views/home/images/logo.png -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "sourceMap": true, 12 | "baseUrl": ".", 13 | "types": ["webpack-env"], 14 | "paths": { 15 | "@/*": ["src/*"] 16 | }, 17 | "lib": ["ESNext", "DOM", "DOM.Iterable", "ScriptHost"] 18 | }, 19 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"], 20 | "exclude": ["node_modules", "dist"] 21 | } 22 | --------------------------------------------------------------------------------