vue3-h5-template
Based on Vue3+TypeScript+ VUUE-CLI4.0 + Vant UI + Sass + REM adaptation scheme + AXIOS packaging + JSSDK configuration + VConsole mobile debugging, the mobile end template scaffolding is constructed
Project address: Github
You are advised to view demo on a mobile phone
Node Version Requirements
Vue CLI requires Node.js version 8.9 or later (8.11.0+ recommended). You can use NVM or NVM-Windows to manage multiple versions of Node on the same computer.
This example node.js 12.14.0
The project structure
Voe-h5-template -- UI Home Directory ├─ Public -- Static Resources ├─ FavIcon ├─ index.html -- Home ├─ SRC -- Source Directory ├─ API ├─ Assets - Static Resources Directory ├─ CSS ├ ├─ Index. SCSS - Everything ├ ├─ Mixin. SCSS - Everything Mixin ├ ├─ Variables. SCSS - Global Variables ├ exercises - Components - Package Component ├ Exercises - Config - Environment Configuration ├ Exercises - Hooks - Vue3 Hooks ├ Exercises - Model - Type Statement File ├ Exercises ├ ─ ─const│ ├── Route │ ├── Route │ ├── Route │ ├── Route │ ├── Router Store -- VUEX ├ exercises ── Utils -- Tool Kit ├ Exercises ── Request.js -- Axios ├ Exercises ─ Storage.js -- Local Store ├ Exercises ── Views -- Business Vue page ├ ├ ├ ─ ─ layouts, route layout page (whether the cached page) ├ ├ ├ ─ ─ tabBar - menu at the bottom of the page ├ ├ └ ─ ─ orther - other pages ├ ├ ─ ─ App. Vue - the root component ├ ├ ─ ─ main. Ts - Entrance ts ├ ├ ─ ─ shims - axios. Which s - axios declaration file ├ └ ─ ─ shims - vue. Which s - vue component declaration file ├ ─ ─ the env. The development, the development environment ├ ─ ─ Env. Production - Production ├─.env.staging - Test Environment ├─.eslintrc.js - ESLint Configure ├─.gitignore - git Ignore ├─ .postCSRC. Js - CSS Pre-Processing (REM Adaptation) ├─ Babel.config. js - Barbel Configuration entry ├─ tsconfig.json - VSCode Path Import Configuration ├─ Package. json -- Dependency Management ├ ─ vue.config.js -- Webpack configuration for vue Cli4Copy the code
Start the project
git clone https://github.com/ynzy/vue3-h5-template.git
cd vue3-h5-template
npm install
npm run serve
Copy the code
directory
- √ Configure multiple environment variables
- √ REM adaptation
- √ Load VantUI components as required
- √Sass global style
- √ Fit apple bottom safe distance
- √ Use Mock data
- √Axios encapsulation and interface management
- √Vuex status management
- Tick the Vue – the router
- √Webpack 4 Vue.config. js Basic configuration
- √ Configure the alias
- √ Configure proxy across domains
- √ Configure package analysis
- √ Externals Imports CDN resources
- Tick off the console. The log
- √splitChunks package third-party modules separately
- Square root gzip compression
- Square root uglifyjs compression
- √ VConsole Mobile debugging
- √ Dynamically set title
- √ Local storage encapsulation
- Square root configuration Jssdk
- √Eslint + Pettier Unified development specification
✅ Configure multiple environment variables
The scripts in package.json are configured to serve stage builds, using –mode XXX to execute different environments
- through
npm run serve
Start the local PC and rundevelopment
- through
npm run stage
Start the test and executedevelopment
- through
npm run prod
Start development, executedevelopment
- through
npm run stageBuild
Package the tests and execute themstaging
- through
npm run build
Package formally, executeproduction
"scripts": {
"serve": "vue-cli-service serve --open"."stage": "cross-env NODE_ENV=dev vue-cli-service serve --mode staging"."prod": "cross-env NODE_ENV=dev vue-cli-service serve --mode production"."stageBuild": "vue-cli-service build --mode staging"."build": "vue-cli-service build",}Copy the code
Configuration is introduced
Variables that begin with VUE_APP_ are accessible in code through process.env.vue_app_. For example,VUE_APP_ENV = ‘development’ is accessed through process.env.vue_app_env. In addition to the VUE_APP_* variable, two special variables NODE_ENV and BASE_URL are always available in your application code to create.env.* in the project root directory
- .env.development Local development environment configuration
NODE_ENV='development'
# must start with VUE_APP_
VUE_APP_ENV = 'development'
Copy the code
- Env.staging test environment configuration
NODE_ENV='production'
# must start with VUE_APP_
VUE_APP_ENV = 'staging'
Copy the code
- .env.production Specifies the environment configuration
NODE_ENV='production'
# must start with VUE_APP_
VUE_APP_ENV = 'production'
Copy the code
There are not many variables defined here, just the basic VUE_APP_ENV development staging Production variables which we manage in SRC /config/env.*.ts.
There is a problem here, since there is a file for setting variables for different environments, why go to config and create three corresponding files? Easy to modify, do not need to restart the project, in line with the development habit.
config/index.js
exportinterface IConfig { env? : string// Development environmenttitle? : string/ / project titlebaseUrl? : string// Project addressbaseApi? : string// the API requests the addressAPPID? : string// Public appId is usually stored on the server sideAPPSECRET? : string// The public account appScript is stored on the server side
$cdn: string // CDN public resource path
}
// Introduce different configurations according to the environment process.env.node_env
const config = require('./env.' + process.env.VUE_APP_ENV)
module.exports = config
Copy the code
And defines the interface type, convenient when we can automatically identify the parameters
Configure environment variables. The local environment file env.develop. js is used as an example. You can modify the variables as required
// Local environment configuration
module.exports = {
title: 'vue-h5-template'.baseUrl: 'http://localhost:9018'.// Project address
baseApi: 'https://test.xxx.com/api'.// The local API requests the address
APPID: 'xxx'.APPSECRET: 'xxx'
}
Copy the code
Call the config
import config from '@/config/index'
setup() {
console.log('Environment Configuration', config)
}
Copy the code
Bring back to the top
✅ REM adaptation scheme
Don’t worry, rem adaptation has been configured for the project. Here’s a brief introduction:
By default, styles in Vant use px units. If you want to use REM units, the following two tools are recommended:
- postcss-pxtoremIs a
postcss
Plug-in for converting units torem
- amfe-flexibleUsed to set the
rem
At baseline
yarn add postcss-pxtorem --dev
yarn add amfe-flexible --save
Copy the code
PostCSS configuration
The following provides a basic PostCSS configuration that can be modified based on project requirements
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: {
autoprefixer: {
overrideBrowserslist: ['the Android 4.1'.'iOS 7.1'.'Chrome > 31'.'ff > 31'.'ie >= 8']},'postcss-pxtorem': {
rootValue: 37.5.propList: [The '*']}}}Copy the code
I used amFE-flexible to set rem, see Github says this is better, which one to use for reference
// main.ts
// Mobile adapter
import 'amfe-flexible'
Copy the code
More details: Vant
Beginners must see, old birds skip
A lot of people ask me about fit.
We know that 1rem is equal to the font-size PX value of the HTML root element. Vant UI set rootValue: 37.5, which you can see on iPhone 6 (1rem equals 37.5px) :
<html data-dpr="1" style="The font - size: 37.5 px;"></html>
Copy the code
The root element may have a different font size when switching between different models. When you write CSS PX, the program converts it to REM to match.
Since we are using Vant components, we need to write the style as rootValue: 37.5.
For example: the design gives you a 750px by 1334px image that covers the screen on the iPhone6 and other models.
- when
rootValue: 70
, the stylewidth: 750px; height: 1334px;
The picture will fill up the iPhone6 screen, and when you switch to another model, the picture will fill up as well. - when
RootValue: 37.5
When stylewidth: 375px; height: 667px;
Images will fill the iPhone6 screen.
That’s 375px CSS for iPhone 6. Other you can according to your design, to write the corresponding style can be.
Of course, you can use 100% if you want to fill the screen, but this is just an example.
<img class="image" src="https://imgs.solui.cn/weapp/logo.png" />
<style>
/* rootValue: 75 */
.image {
width: 750px;
height: 1334px;
}
/ * rootValue: 37.5 * /
.image {
width: 375px;
height: 667px;
}
</style>
Copy the code
Bring back to the top
✅ VantUI components are loaded on demand
The project adopts Vant to automatically introduce components on demand (recommended)
Generally speaking, TS uses plan 2, but I have some problems in the process of using it, so I use Plan 1
Solution a:
Babel-plugin-import is a Babel plug-in that automatically converts the way import is written to import on demand during compilation
Installing a plug-in
npm i babel-plugin-import -D
Copy the code
In the Babel. Config. Js Settings
// For users using babel7, this can be configured in babel.config.js
const plugins = [
[
'import',
{
libraryName: 'vant'.libraryDirectory: 'es'.style: true
},
'vant']]module.exports = {
presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage'.corejs: 3 }]],
plugins
}
Copy the code
Ts-import-plugin is a modular import plugin for TypeScript
Yarn add ts-import-plugin –dev then add it in vue. Config.js
const merge = require('webpack-merge')
const tsImportPluginFactory = require('ts-import-plugin')
// * Implementation of tripartite UI loading on demand under TS
const mergeConfig = config= > {
config.module
.rule('ts')
.use('ts-loader')
.tap(options= > {
options = merge(options, {
transpileOnly: true.getCustomTransformers: () = > ({
before: [
tsImportPluginFactory({
libraryName: 'vant'.libraryDirectory: 'es'.style: true}})]),compilerOptions: {
module: 'es2015'}})return options
})
}
Copy the code
Using the component
The project manages components in a unified way under SRC /plugins/ ant
// Globally import vant components as needed
import { App as VM } from 'vue'
import { Button, Cell, CellGroup, Icon } from 'vant'
const plugins = [Button, Icon, Cell, CellGroup]
export const vantPlugins = {
install: function(vm: VM) {
plugins.forEach(item= > {
vm.component(item.name, item)
})
}
}
Copy the code
Bring back to the top
✅ Sass global style
Dart-sass ensures fast installation and high probability of installation failure
Each page’s own style is written in its own.vue file scoped adds the concept of a domain to CSS as its name suggests.
<style lang="scss">
/* global styles */
</style>
<style lang="scss" scoped>
/* local styles */
</style>
Copy the code
The directory structure
Vue-h5-template All global styles are set in the @/ SRC /assets/ CSS directory
├── Assets │ ├─ CSS │ ├─ Index# Global universal style│ │ ├ ─ ─ reset. SCSSClear the browser default style│ │ ├ ─ ─ a mixin. SCSS# global mixin│ │ └ ─ ─ the variables. SCSS# global variables
Copy the code
Vue.config. js adds global style configuration
css: {
loaderOptions: {
scss: {
// Pass the shared global variable to the global SASS style. $SRC can be configured with the image CDN prefix
/ / details: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders
prependData: `
@import "assets/css/mixin.scss";
@import "assets/css/variables.scss";
`
// $cdn: "${defaultSettings.$cdn}";}}},Copy the code
$CDN can be accessed in.vue files using this.$CDN
// Introduce global styles
import '@/assets/css/index.scss'
// set js to access $CDN
/ / into the CDN
import { $cdn } from '@/config'
Vue.prototype.$cdn = $cdn
Copy the code
Use in CSS and JS
<script>
console.log(this.$cdn)
</script>
<style lang="scss" scoped>
.logo {
width: 120px;
height: 120px;
background: url($cdn+'/weapp/logo.png') center / contain no-repeat;
}
</style>
Copy the code
Bring back to the top
Custom Vant-UI styles
Now let’s talk about how to rewrite the vuant-UI style. Since the styles of the Ant-UI are introduced globally, you can’t add scoped if you want to cover only the vant styles of the page. If you want to cover only the Vant styles of the page, you can add a class to its parent and use the namespace to solve the problem.
.about-container {
/* Your namespace */
.van-button {
/* Vant-ui element */
margin-right: 0px; }}Copy the code
The parent component changes the child component style depth selector
When you want to style a child component using scoped in the parent component you can do this by using ::v-deep:
<style scoped>
::v-deep .a {
.b { /* ... */ }
}
</style>
Copy the code
Bring back to the top
✅ for Apple bottom safe distance
The meta of index.html specifies viewport-fit=cover
The bottom safe distance parameter comes with vant
<! Add meta tag to head tag and set viewport-fit=cover --><meta
name="viewport"
content="Width =device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"
/><! -- Enable the top safety zone adaptation --><van-nav-bar safe-area-inset-top /><! -- Enable bottom safety zone adaptation --><van-number-keyboard safe-area-inset-bottom />
Copy the code
If you don’t use an adaptation in Vant, you can write it yourself. I wrote a generic style in SCSS
.fixIphonex {
padding-bottom: $safe-bottom ! important;
&::after {
content: ' ';
position: fixed;
bottom: 0 ! important;
left: 0;
height: calc(#{$safe-bottom} + 1px);
width: 100%;
background: #ffffff; }}Copy the code
Bring back to the top
✅ Use Mock data
Mock requests are wrapped with vue-elemental-admin mock requests, and can be used directly
- mock.js
const Mock = require('mockjs')
const user = require('./user')
// const role = require('./role')
// const article = require('./article')
// const search = require('./remote-search')
// const mocks = [...user, ...role, ...article, ...search]
const mocks = [...user]
// for front mock
// please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event).
function mockXHR() {
// mock patch
// https://github.com/nuysoft/Mock/issues/300
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function() {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
if (this.responseType) {
this.custom.xhr.responseType = this.responseType
}
}
this.proxy_send(... arguments) }function XHR2ExpressReqWrap(respond) {
return function(options) {
let result = null
if (respond instanceof Function) {
const { body, type, url } = options
// https://expressjs.com/en/4x/api.html#req
result = respond({
method: type,
body: JSON.parse(body),
query: url
})
} else {
result = respond
}
return Mock.mock(result)
}
}
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
module.exports = {
mocks,
mockXHR
}
Copy the code
- user.js
const tokens = {
admin: {
token: 'admin-token'
},
editor: {
token: 'editor-token'}}const users = {
'admin-token': {
roles: ['admin'].introduction: 'I am a super administrator'.avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif'.name: 'Super Admin'
},
'editor-token': {
roles: ['editor'].introduction: 'I am an editor'.avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif'.name: 'Normal Editor'}}module.exports = [
// user login
{
url: '/vue-h5/user/login'.type: 'post'.response: config= > {
const { username } = config.body
const token = tokens[username]
// mock error
// if (! token) {
// return {
// code: 60204,
// message: 'Account and password are incorrect.'
/ /}
// }
return {
code: 20000.data: token,
msg: 'Login successful'}}},// get user info
{
url: '/vue-h5/user/info.*'.type: 'get'.response: config= > {
const { token } = config.query
const info = users['admin-token']
// mock error
// if (! info) {
// return {
// code: 50008,
// message: 'Login failed, unable to get user details.'
/ /}
// }
return {
code: 20000.data: info,
msg: 'Login successful'}}},// user logout
{
url: '/vue-h5/user/logout'.type: 'post'.response: _= > {
return {
code: 20000.data: 'success'}}}]Copy the code
- If you don’t need main.js, just get rid of this code
// Use mock data
if (config.mock) {
const { mockXHR } = require('.. /mock')
mockXHR()
}
Copy the code
- Interface request
onMounted(() = > {
axios
.get('/vue-h5/user/info')
.then(res= > {
console.log(res)
})
.catch(err= > {
console.error(err)
})
})
Copy the code
✅ Axios encapsulation and interface management
Utils /request.js encapsulates AXIOS and developers need to make changes based on the background interface.
service.interceptors.request.use
Request headers can be set, such as Settingstoken
config.hideloading
It is set in the interface parameters in the API folder, as described belowservice.interceptors.response.use
In the interface can return data processing, such as 401 delete local information, login again
/ * * *@description [axios requests encapsulation] */
import store from '@/store'
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios'
// import {Message, Modal} from 'view-design' // UI component library
import { Dialog, Toast } from 'vant'
import router from '@/router'
// Introduce different API addresses according to the environment
import config from '@/config'
const service = axios.create({
baseURL: config.baseApi + '/vue-h5'.// url = base url + request url
timeout: 5000.withCredentials: false // send cookies when cross-domain requests
// headers: {
// // clear cors
// 'Cache-Control': 'no-cache',
// Pragma: 'no-cache'
// }
})
// Request interceptors
service.interceptors.request.use(
(config: AxiosRequestConfig) = > {
// Load the animation
if (config.loading) {
Toast.loading({
message: 'Loading... '.forbidClick: true})}// Add request headers here, such as token
// if (store.state.token) {
// config.headers['Authorization'] = `Bearer ${store.state.token}`
// }
return config
},
(error: any) = > {
Promise.reject(error)
}
)
// Response interceptors
service.interceptors.response.use(
async (response: AxiosResponse) => {
// await new Promise(resovle => setTimeout(resovle, 3000))
Toast.clear()
const res = response.data
if(res.code ! = =0) {
/ / token expired
if (res.code === 401) {
// Warning window
return
}
if (res.code == 403) {
Dialog.alert({
title: 'warning'.message: res.msg
}).then(() = > {})
return
}
// If the background returns an error value, the corresponding error object is returned here, and the following error is received
return Promise.reject(new Error(res.msg || 'Error'))}else {
// Note the return value
return response.data
}
},
(error: any) = > {
Toast.clear()
if (error && error.response) {
switch (error.response.status) {
case 400:
error.message = 'Request error (400)'
break
case 401:
error.message = 'Not authorized, please log in to (401)'
break
case 403:
error.message = 'Access denied (403)'
break
case 404:
error.message = 'Error requesting address:${error.response.config.url}`
break
case 405:
error.message = 'Requested method not allowed (405)'
break
case 408:
error.message = 'Request timed out (408)'
break
case 500:
error.message = 'Server internal error (500)'
break
case 501:
error.message = 'Service Not realized (501)'
break
case 502:
error.message = 'Network Error (502)'
break
case 503:
error.message = 'Service unavailable (503)'
break
case 504:
error.message = 'Network Timeout (504)'
break
case 505:
error.message = 'HTTP version not supported (505)'
break
default:
error.message = 'Connection error:${error.message}`}}else {
if (error.message == 'Network Error') {
error.message == 'Network exception, please check and try again! '
}
error.message = 'Failed to connect to server, please contact administrator'
}
Toast(error.message)
// store.auth.clearAuth()
store.dispatch('clearAuth')
return Promise.reject(error)
}
)
export default service
Copy the code
Interface management
Unify the management interface in the SRC/API folder
- You can set up multiple module docking interfaces, such as
home.ts
Here is the interface of the home pageauthController.ts
url
Interface address, which will be concatenated when requestedconfig
Under thebaseApi
method
Request methoddata
Request parametersqs.stringify(params)
Is the data serialization operationloading
The defaultfalse
, is set totrue
Some interfaces in the loading UI interaction need to be perceived by the user
import request from '@/utils/request'
export interface IResponseType<P = {}> {
code: number
msg: string
data: P
}
interface IUserInfo {
id: string
avator: string
}
interface IError {
code: string
}
export const fetchUserInfo = () = > {
return request<IResponseType<IUserInfo>>({
url: '/user/info'.method: 'get'.loading: true})}Copy the code
How to call
Because the awaitWrap type derivation is cumbersome, try catch is used to catch errors, both interface errors and business logic errors
onMounted(async() = > {try {
let res = await fetchUserInfo()
console.log(res)
} catch (error) {
console.log(error)
}
})
Copy the code
Bring back to the top
✅ Vuex status management
The directory structure
├ ─ ─ store │ ├ ─ ─ modules │ ├ ─ ─ | ─ ─ Auth │ ├ ─ ─ ├ ─ ─ ├ ─ ─ but ts │ ├ ─ ─ ├ ─ ─ ├ ─ ─ interface. The ts │ ├ ─ ─ ├ ─ ─ └ ─ ─ types. The ts │ ├ ─ ─ Index. Ts │ ├ ─ ─ getters. TsCopy the code
The type definition
- Module type
interface.ts
import { IUserInfo } from '@/api/interface'
/** * User information */
export interface IAuthState {
userInfo: IUserInfo
}
Copy the code
index.ts
import { Module } from 'vuex'
import { IGlobalState } from '@/store/index'
import { IAuthState } from '@/store/modules/Auth/interface'
import * as Types from '@/store/modules/Auth/types'
const state: IAuthState = {
userInfo: {}}const login: Module<IAuthState, IGlobalState> = {
namespaced: true,
state,
mutations: {
[Types.SAVE_USER_INFO](state, data) {
state.userInfo = data
}
},
actions: {
async [Types.SAVE_USER_INFO]({ commit }, data) {
return commit(Types.SAVE_USER_INFO, data)
}
}
}
export default login
Copy the code
- Global Store type
Import the module type into index.ts to define the global type
import { IAuthState } from './modules/Auth/interface'
export interface IGlobalState {
auth: IAuthState
}
const store = createStore<IGlobalState>({
getters,
modules: {
auth
}
})
export default store
Copy the code
The main ts introduced
import { createApp } from 'vue'
import store from './store'
const app = createApp(App)
app.use(store)
app.mount('#app')
Copy the code
use
import { fetchUserInfo } from '@/api/authController.ts'
import { useStore } from 'vuex'
import * as Types from '@/store/modules/Auth/types'
import { IGlobalState } from '@/store'
export default defineComponent({
name: 'about'.props: {},
setup(props) {
const store = useStore<IGlobalState>()
const userInfo = computed(() = > {
return store.state.auth.userInfo
})
onMounted(async() = > {try {
let res = await fetchUserInfo()
if(res.code ! = =0) return new Error(res.msg)
// Action is triggered by the store.dispatch method
store.dispatch(`auth/${Types.SAVE_USER_INFO}`, res.data)
} catch (error) {
console.log(error)
}
})
return {
userInfo
}
}
})
Copy the code
Bring back to the top
✅ Vue – the router
This case mainly adopts the history mode, and the developer modifies the Mode Base as required
Go to :vue.config.js basic configuration
import { createRouter, createWebHistory } from 'vue-router'
import { constantRouterMap } from './router.config'
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
// When the back/forward button is pressed, it behaves like the browser's native behavior
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { top: 0}}},routes: constantRouterMap
})
export default router
Copy the code
import { RouteRecordRaw } from 'vue-router'
export const constantRouterMap: Array<RouteRecordRaw> = [
{
path: '/'.name: 'Home'.component: () = > import('@/views/layouts/index.vue'),
redirect: '/home'.meta: {
title: 'home'.keepAlive: false
},
children: [{path: '/home'.name: 'Home'.component: () = > import(/* webpackChunkName: "tabbar" */ '@/views/tabBar/home/index.vue'),
meta: { title: 'home'.keepAlive: false.showTab: true}}, {path: '/demo'.name: 'Dome'.component: () = > import(/* webpackChunkName: "tabbar" */ '@/views/tabBar/dome/index.vue'),
meta: { title: 'home'.keepAlive: false.showTab: true}}, {path: '/about'.name: 'About'.component: () = > import(/* webpackChunkName: "tabbar" */ '@/views/tabBar/about/index.vue'),
meta: { title: 'About me'.keepAlive: false.showTab: true}}]}]Copy the code
More: Vue Router
Bring back to the top
✅ Webpack 4 Vue.config. js Basic configuration
If your Vue Router mode is hash
publicPath: '/'.Copy the code
If your Vue Router mode is History, the publicPath here is consistent with your Vue Router Base
publicPath: '/app/'.Copy the code
const IS_PROD = ['production'.'prod'].includes(process.env.NODE_ENV)
module.exports = {
// publicPath: './', // The basic URL of the application package. Vue-router Hash mode is used
publicPath: '/app/'.// The basic URL of the application package. Vue-router history is used
outputDir: 'dist'.// Directory of the production environment build files
assetsDir: 'static'.// outputDir static resources (js, CSS, img, fonts) directory
lintOnSave: !IS_PROD,
productionSourceMap: false.// If you don't need the source map for production, set it to false to speed up production builds.
devServer: {
port: 9020./ / the port number
open: false.// Open the browser after startup
overlay: {
Display full screen overlay in the browser when compiler errors or warnings occur
warnings: false.errors: true
}
// ...}}Copy the code
Bring back to the top
✅ Configure the alias
const path = require('path')
const resolve = dir= > path.join(__dirname, dir)
const IS_PROD = ['production'.'prod'].includes(process.env.NODE_ENV)
module.exports = {
chainWebpack: config= > {
// Add an alias
config.resolve.alias
.set(The '@', resolve('src'))
.set('assets', resolve('src/assets'))
.set('api', resolve('src/api'))
.set('views', resolve('src/views'))
.set('components', resolve('src/components'))}}Copy the code
Bring back to the top
✅ Configure proxy across domains
If your project requires cross-domain Settings, you need to annotate the vue.config.js proxy and configure the parameters accordingly
!!!!!!!!! Note: You will also need to addsrc/config/env.development.js
In thebaseApi
Set to ‘/’
module.exports = {
devServer: {
/ /...
proxy: {
// Configure cross-domain
'/api': {
target: 'https://test.xxx.com'.// Interface domain name
// ws: true, // Whether webSockets are enabled
changOrigin: true.// Enable the proxy to create a virtual server locally
pathRewrite: {
'^/api': '/'
}
}
}
}
}
Copy the code
Use for example: SRC/API /home.js
export function getUserInfo(params) {
return request({
url: '/api/userinfo'.method: 'post'.data: qs.stringify(params)
})
}
Copy the code
Bring back to the top
✅ Configure package analysis
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
chainWebpack: config= > {
// Package analysis
if (IS_PROD) {
config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
{
analyzerMode: 'static'}}}Copy the code
npm run build
Copy the code
Bring back to the top
✅ Configure externals to import CDN resources
In this version, CDN is no longer introduced. I tested using CDN and not using CDN. Not using CDN takes less time than using CDN. Many articles on the web test CDN speed block, this developer can be tested in practice.
In addition, the public CDN used in the project is unstable, and it takes time to resolve the domain name (please try to use the same domain name if you want to use it).
Because the page stops parsing every time it encounters a
The research has not been put on its own CDN server.
const defaultSettings = require('./src/config/index.js')
const name = defaultSettings.title || 'vue mobile template'
const IS_PROD = ['production'.'prod'].includes(process.env.NODE_ENV)
// externals
const externals = {
vue: 'Vue'.'vue-router': 'VueRouter'.vuex: 'Vuex'.vant: 'vant'.axios: 'axios'
}
// CDN external chain will be inserted into index.html
const cdn = {
// Development environment
dev: {
css: [].js: []},// Production environment
build: {
css: ['https://cdn.jsdelivr.net/npm/[email protected]/lib/index.css'].js: [
'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js'.'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.min.js'.'https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js'.'https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.min.js'.'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.min.js']}}module.exports = {
configureWebpack: config= > {
config.name = name
// Modify the configuration for the production environment...
if (IS_PROD) {
// externals
config.externals = externals
}
},
chainWebpack: config= > {
/** * Add CDN parameters to htmlWebpackPlugin configuration */
config.plugin('html').tap(args= > {
if (IS_PROD) {
args[0].cdn = cdn.build
} else {
args[0].cdn = cdn.dev
}
return args
})
}
}
Copy the code
Add to public/index.html
<! -- CSS files using CDN --> <%for (var i in
htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
<link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />The < %} % > <! -- Use CDN accelerated JS file, configured under vue.config.js --> <%for (var i in
htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>The < %} % >Copy the code
Bring back to the top
✅ remove the console log
Console. log for the test environment and local environment are retained
npm i -D babel-plugin-transform-remove-console
Copy the code
Configure in babel.config.js
VUE_APP_ENV is not NODE_ENV. The test environment remains console
const IS_PROD = ['production'.'prod'].includes(process.env.VUE_APP_ENV)
const plugins = [
[
'import',
{
libraryName: 'vant'.libraryDirectory: 'es'.style: true
},
'vant']]/ / remove the console log
if (IS_PROD) {
plugins.push('transform-remove-console')}module.exports = {
presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'entry' }]],
plugins
}
Copy the code
Bring back to the top
✅ splitChunks package third-party modules separately
module.exports = {
chainWebpack: config= > {
config.when(IS_PROD, config= > {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin'[{// The runtime is introduced as inline and does not stand alone
inline: /runtime\.. *\.js$/
}
])
.end()
config.optimization.splitChunks({
chunks: 'all'.cacheGroups: {
// Multiple groups can be configured under cacheGroups. Each group is conditional on test and the module that matches the test condition
commons: {
name: 'chunk-commons'.test: resolve('src/components'),
minChunks: 3.// Have been used at least three times to pack and separate
priority: 5./ / priority
reuseExistingChunk: true // Indicates whether to use the existing chunk. True indicates that if the chunk contains modules that have been extracted, no new chunk will be generated.
},
node_vendors: {
name: 'chunk-libs'.chunks: 'initial'.// Package only the third party that you originally relied on
test: /[\\/]node_modules[\\/]/,
priority: 10
},
vantUI: {
name: 'chunk-vantUI'.// Unpack vantUI separately
priority: 20.// The number is heavily weighted to the one with the highest weight when multiple cacheGroups are met
test: /[\\/]node_modules[\\/]_? vant(.*)/
}
}
})
config.optimization.runtimeChunk('single')}}}Copy the code
Bring back to the top
✅ gzip compression
May be an error, low installation version Reference address www.cnblogs.com/wuzhiquan/p…
// * Pack gzip
const assetsGzip = config= > {
config.plugin('compression-webpack-plugin').use(require('compression-webpack-plugin'), [{filename: '[path].gz[query]'.algorithm: 'gzip'.test: /\.js$|\.html$|\.json$|\.css/,
threshold: 10240.// Only resources larger than this value will be processed
minRatio: 0.8.// Only resources whose compression ratio is less than this value will be processed
deleteOriginalAssets: true // Delete the original file})}Copy the code
Bring back to the top
✅ uglifyjs compression
Note that using this plug-in requires converting ES6 code into ES5 code, which is not used in this project
// * Code compression
const codeUglify = config= > {
config.plugin('uglifyjs-webpack-plugin').use(require('uglifyjs-webpack-plugin'), [{uglifyOptions: {
// The production environment automatically deletes the console
compress: {
drop_debugger: true.drop_console: false.pure_funcs: ['console.log']}},sourceMap: false.parallel: true})}Copy the code
Bring back to the top
✅ VConsole mobile debugging
Reference address: github.com/AlloyTeam/A… Refer to the address: www.cnblogs.com/liyinSakura…
<! -- MobileConsole --><template>
<teleport to="#vconsole">
<div class="vc-tigger" @click="toggleVc"></div>
</teleport>
</template>
<script lang="ts">
import { defineComponent, onUnmounted, reactive } from 'vue'
import VConsole from 'vconsole'
import config from '@/config'
import { useDOMCreate } from '@/hooks/useDOMCreate'
interface IState {
lastClickTime: number
count: number
limit: number
vConsole: any
}
export default defineComponent({
name: 'MobileConsole'.props: {},
setup() {
useDOMCreate('vconsole')
const state = reactive<IState>({
lastClickTime: 0.count: 0.limit: ['production'.'prod'].includes(config.env || ' ')?5 : 0.vConsole: null
})
const hasClass = (obj: HTMLElement | null, cls: string) = > {
returnobj? .className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'))}const addClass = (obj: HTMLElement | null, cls: string) = > {
if(! hasClass(obj, cls)) obj? .classList.add(cls) }const removeClass = (obj: HTMLElement | null, cls: string) = > {
if(hasClass(obj, cls)) { obj? .classList.remove(cls) } }const toggleClass = (obj: HTMLElement | null, cls: string) = > {
if (hasClass(obj, cls)) {
removeClass(obj, cls)
} else {
addClass(obj, cls)
}
}
const toggleVc = () = > {
const nowTime = new Date().getTime()
if (nowTime - state.lastClickTime < 3000) {
state.count++
} else {
state.count = 0
}
state.lastClickTime = nowTime
if (state.count >= state.limit) {
if(! state.vConsole) { state.vConsole =new VConsole()
}
let vconDom = document.getElementById('__vconsole')
toggleClass(vconDom, 'vconsole_show')
state.count = 0
}
}
onUnmounted(() = > {
state.vConsole = null
})
return {
toggleVc
}
}
})
</script>
<style lang="scss" scoped>
.vc-tigger {
position: fixed;
top: 0;
left: 0;
width: 20px;
height: 20px;
background: red;
}
</style>
Copy the code
- Set the trap door in the component and click a few times to show the VConsole
- Set by limit in app.vue
- Development test environment can be displayed with one click
- Production Environment click 5 times
teleport
The official document: v3.cn.vuejs.org/guide/telep…
The DOM element is there where the popbox and other components are referenced, which helps us separate the code from the component code, so we can see the DOM element composition better. Okay
UseDOMCreate makes it easy to create DOM elements without having to create the DOM elements needed by Teleport in index. HTML
Bring back to the top
✅ Dynamically sets the title
export const useDocumentTitle = (title: string) = > {
document.title = title
}
Copy the code
The router/index. Ts is used
router.beforeEach((to, from, next) = > {
useDocumentTitle(to.meta.title)
next()
})
Copy the code
Bring back to the top
✅ Local storage storage encapsulation
Examples are under dome/storage/index.vue
Reference:
import { storage } from '@/utils/storage'
Copy the code
Call:
storage.set('data', originalData.value)
storageData.value = storage.get('data')
Copy the code
Bring back to the top
✅ configuration Jssdk
TODO: To be updated
Installation:
yarn add weixin-js-sdk
Copy the code
The type declaration is written in model/ weixin-js-sdK.d. ts
Since Apple Browser only recognizes the route entered for the first time, you need to configure the URL used first
- router.ts
The JSSDK configuration here is for demonstration only. Normal service logic needs to be written with the back-end
import { isWeChat } from '.. /utils/index'
import { fetchWeChatAuth } from '@/api/WxController'
import { getQueryParams, phoneModel } from '@/utils'
import store from '@/store'
// The route starts to enter
router.beforeEach((to, from, next) = > {
/ /! Solve the problem of unsuccessful share signature in ios wechat, cache the url entered for the first time.
if (window.entryUrl === undefined) {
window.entryUrl = location.href.split(The '#') [0]}const { code } = getQueryParams<IQueryParams>()
// Wechat authorized login in wechat browser
/ / &&! store.state.auth.userInfo.name
if (isWeChat()) {
if (code) {
store.commit('auth/STE_ISAUTH'.true)
store.commit('auth/STE_CODE', code)
}
if(! store.state.auth.isAuth) { location.href = fetchWeChatAuth() } } next() }) router.afterEach((to, from, next) = > {
let url
if (phoneModel() === 'ios') {
url = window.entryUrl
} else {
url = window.location.href
}
/ / save the url
store.commit('link/SET_INIT_LINK', url)
})
Copy the code
store/Link
import { Module } from 'vuex'
import { IGlobalState } from '@/store/index'
import { ILinkState } from '@/store/modules/Link/interface'
const state: ILinkState = {
initLink: ' '
}
const login: Module<ILinkState, IGlobalState> = {
namespaced: true,
state,
mutations: {['SET_INIT_LINK'](state, data) {
console.log(data)
state.initLink = data
}
},
actions: {}}export default login
Copy the code
Since the window does not have an entryUrl variable, you need to declare it in a declaration file
typings.ts
declare interface Window {
entryUrl: any
}
Copy the code
Create the hooks function
hooks/useWxJsSdk.ts
UseWxJsSdk needs to be called once for each page that uses JSSDK, and then the other wrapped functions are used
Call:
Copy the code
Bring back to the top
✅ Eslint + Pettier unified development specification
Refer to Typescript code reviews
Install esLint prettier vetur in VScode
Prettierrc: write your own Pettier rule or prettier.config.js in the. Prettierrc file
module.exports = {
"wrap_line_length": 120."wrap_attributes": "auto"."eslintIntegration":true."overrides": [{"files": ".prettierrc"."options": {
"parser": "json"}}].// A line of up to 100 characters
printWidth: 100.// Use 4 Spaces for indentation
tabWidth: 2.// Instead of indentation, use Spaces
useTabs: false.// A semicolon is required at the end of the line
semi: true.// Use single quotes
singleQuote: true.// The key of the object is quoted only when necessary
quoteProps: 'as-needed'.JSX uses double quotes instead of single quotes
jsxSingleQuote: false.// Do not need a comma at the end
trailingComma: 'none'.// Spaces are required at the beginning and end of braces
bracketSpacing: true.// JSX tag Angle brackets require line breaks
jsxBracketSameLine: false.// The arrow function, which has only one argument, also needs the parentheses avoid
arrowParens: 'always'.// The range in which each file is formatted is the entire content of the file
rangeStart: 0.rangeEnd: Infinity.// There is no need to write @prettier at the beginning of the file
requirePragma: false.// There is no need to automatically insert @prettier at the beginning of a file
insertPragma: false.// Use the default line folding standard always
proseWrap: 'preserve'.// Depending on the display style, HTML should be folded or not
htmlWhitespaceSensitivity: 'css'.// Use lf auto for newline
endOfLine: 'lf'
}
Copy the code
. Eslintrc. Js configuration
module.exports = {
root: true.env: {
browser: true.node: true.es6: true
},
extends: [
'plugin:vue/vue3-essential'.'eslint:recommended'.'@vue/typescript/recommended'.'@vue/prettier'.'@vue/prettier/@typescript-eslint'].parserOptions: {
ecmaVersion: 2020
},
rules: {
// Do not use var
'no-var': 'error'.'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off'.'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'.'@typescript-eslint/no-empty-function': 0.'@typescript-eslint/no-var-requires': 0.'@typescript-eslint/interface-name-prefix': 0.'@typescript-eslint/no-explicit-any': 0 // TODO}};Copy the code
Vscode setting. Json Settings
{
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[tavascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Format with eslint when saving
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
// The two will conflict when formatting JS, so you need to disable the default JS formatter
"javascript.format.enable": false."typescript.format.enable": false."vetur.format.defaultFormatter.html": "none".// js/ts uses eslint to prevent prettier in vetur from formatting in conflict with ESLint
"vetur.format.defaultFormatter.js": "none"."vetur.format.defaultFormatter.ts": "none"."files.eol": "\n"."editor.tabSize": 2."editor.formatOnSave": true.// "editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.autoFixOnSave": true."eslint.validate": [
"javascript"."javascriptreact",
{
"language": "typescript"."autoFix": true}]."typescript.tsdk": "node_modules/typescript/lib"
}
Copy the code
Bring back to the top
thanks
vue-h5-template vue-cli4-config vue-element-admin