├── .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 |
2 |
3 | Home |
4 | About
5 |
6 |
7 |
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 |
2 |
3 |
This is an about page
4 |
5 |
6 |
7 |
14 |
--------------------------------------------------------------------------------
/src/views/home/components/hello-world.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 |
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 |
2 |
3 |

4 |
5 |
6 |
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 |
--------------------------------------------------------------------------------