
可乐炸鸡
2023/05/26阅读:23主题:橙心
从零开始搭建移动端基础开发框架
从零开始搭建移动端基础开发框架(H5)
本基础开发框架基于Vite 4 + Vue 3 + TypeScript + Vant 4 + vw 适配方案 + axios 封装 + vconsole移动端调试搭建,后期会不断丰富。
项目体验地址:H5基础框架

项目目录
.
├── .env.development
├── .env.production
├── .env.staging
├── .eslintignore
├── .eslintrc.cjs
├── .git
├── .gitignore
├── .prettierignore
├── .prettierrc.cjs
├── .vscode
├── README.md
├── components.d.ts
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│ └── vite.svg
├── src
│ ├── App.vue
│ ├── api
│ ├── assets
│ ├── components
│ ├── layouts
│ ├── main.ts
│ ├── router
│ ├── stores
│ ├── style.css
│ ├── utils
│ ├── views
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
初始化项目
项目要求:
-
Node.js 版本 -
pnpm 包管理
兼容性注意:
Vite 需要 Node.js 版本 14.18+,16+。然而,有些模板需要依赖更高的 Node 版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
确定Node.js版本:v16.20.0
搭建项目
使用 NPM:
npm create vite@latest
使用 Yarn:
yarn create vite
使用 PNPM:
pnpm create vite
我们选择使用pnpm,然后提示操作。
# 创建项目
pnpm create vite
# 项目名 - vant-template
# 框架 - Vue
# Js/Ts - TypeScript
# 创建成功
# cd vant-template
# pnpm install
# pnpm run dev
安装Vant
# 通过 pnpm 安装
pnpm add vant
引入Vant组件
Vant 官网提供了两种引入方法,组件默认支持 Tree Shaking,但是CSS 样式无法 Tree Shaking,这里我们选择按需引入组件样式。
# 通过 pnpm 安装
pnpm add unplugin-vue-components -D
-
配置插件
本项目基于
vite
,在vite.config.js
文件中配置插件:
import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';
export default {
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
}),
],
};
-
使用Vant组件
<template>
<van-button type="primary" />
</template>
-
引入函数组件的样式
Vant 中有个别组件是以函数的形式提供的,包括
Toast
,Dialog
,Notify
和ImagePreview
组件。可以在项目的入口文件中main.ts
引入以上组件的样式。
import { createApp } from "vue";
import "./style.css";
import "vant/es/toast/style";
import "vant/es/dialog/style";
import "vant/es/notify/style";
import "vant/es/image-preview/style";
浏览器适配
Viewport 布局
Vant 默认使用 px
作为样式单位,如果需要使用 viewport
单位 (vw, vh, vmin, vmax),推荐使用 postcss-px-to-viewport 进行转换。
postcss-px-to-viewport 的缺点:无法把行内样式中的 px 转换成视口单位(vw, vh, vmin, vmax)。尽可能避免行内样式。
# pnpm 安装
pnpm add -D postcss-px-to-viewport
因为Vite内置了PostCSS,所以可以这样修改vite.config.ts
。
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import Components from "unplugin-vue-components/vite";
import { VantResolver } from "unplugin-vue-components/resolvers";
import PostcssPxToViewport from "postcss-px-to-viewport";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
Components({
resolvers: [VantResolver()],
}),
],
css: {
postcss: {
plugins: [
PostcssPxToViewport({
unitToConvert: "px", // 要转化的单位
viewportWidth: 375, // UI设计稿的宽度,一般是375/750
unitPrecision: 6, // 转换后的精度,即小数点位数
propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
selectorBlackList: ["ignore-"], // 指定不转换为视窗单位的类名
minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
replace: true, // 是否转换后直接更换属性值
landscape: false, // 是否处理横屏情况
}),
],
},
},
});
底部安全区适配
iPhone X 等机型底部存在底部指示条,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行安全区适配。
<!-- 在 head 标签中添加 meta 标签,并设置 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"
/>
<!-- 开启顶部安全区适配 -->
<van-nav-bar safe-area-inset-top />
<!-- 开启底部安全区适配 -->
<van-number-keyboard safe-area-inset-bottom />
多环境配置
-
添加 .env.development
开发环境配置文件
VITE_ENV = 'development'
-
添加 .env.staging
测试环境配置文件
VITE_ENV = 'staging'
-
添加 .env.production
生产环境配置文件
VITE_ENV = 'production'
-
修改 package.json
文件中的scripts
:
"scripts": {
"dev": "vite --mode development",
"staging": "vite --mode staging",
"prod": "vite --mode production",
"build": "vue-tsc && vite build --mode production",
"preview": "vite preview"
},
配置 alias 别名
resolve.alias
的作用是为模块的导入路径创建一个别名,使开发者可以使用更简洁、易懂的路径来引用模块,而不必担心实际文件的位置或复杂的相对路径。
修改vite.config.ts
配置文件
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
同时修改tsconfig.json
配置文件
{
// ...
"compilerOptions": {
// ...
"paths": {
"@/*": ["./src/*"]
}
}
// ...
}
CSS 预处理器
CSS预处理器提供了更多的编程特性,使CSS更易维护和扩展,提高了开发效率,在这里我们选择Sass。由于Vite 同时提供了对 .scss
, .sass
, .less
, .styl
和 .stylus
文件的内置支持。没有必要为它们安装特定的 Vite 插件,但必须安装相应的预处理器依赖:
# .scss and .sass
pnpm add -D sass
在使用时通过<style lang="scss">
开启
<style lang="scss" scoped>
// ...
</style>
Pinia 状态管理
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持。
安装pinia
依赖
# pnpm
pnpm add pinia
创建和使用
-
新增 src/stores/index.ts
文件
import { createPinia } from "pinia";
const pinia = createPinia();
export default pinia;
-
在 main.ts
文件引入
import { createApp } from "vue";
import "./style.css";
import "vant/es/toast/style";
import "vant/es/dialog/style";
import "vant/es/notify/style";
import "vant/es/image-preview/style";
import App from "./App.vue";
import pinia from "./store";
createApp(App).use(pinia).mount("#app");
-
定义State
// src/stores/counter.ts
import { defineStore } from "pinia";
export const useCounterStore = defineStore({
id: "counter",
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});
-
组件中使用
<script setup lang="ts">
import { useCounterStore } from "@/store/counter";
const counterStore = useCounterStore();
const increment = () => counterStore.increment();
</script>
<template>
<div>
<van-button type="primary" @click="increment">累加</van-button>
<h3>{{ counterStore.count }}</h3>
<h3>{{ counterStore.doubleCount }}</h3>
</div>
</template>
-
页面效果

持久化处理
# 安装插件
pnpm add pinia-plugin-persistedstate
修改src/stores/index.ts
文件
import { createPinia } from "pinia";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;
修改src/stores/counter.ts
文件
export const useCounterStore = defineStore({
// ...
persist: true,
});
Vue Router路由管理
Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。
安装Vue Router依赖
pnpm add vue-router@4
Vue Router配置
// src/router/index.ts
import { createRouter, createWebHashHistory } from "vue-router";
const routes = [
{
path: "/",
name: "home",
component: () => import("@/views/home/index.vue"),
meta: {
title: "首页",
keepAlive: false,
},
},
{
path: "/about",
name: "about",
component: () => import("@/views/about/index.vue"),
meta: {
title: "关于",
keepAlive: false,
},
},
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;
修改main.ts
文件
// main.ts
import { createApp } from "vue";
import "./style.css";
import "vant/es/toast/style";
import "vant/es/dialog/style";
import "vant/es/notify/style";
import "vant/es/image-preview/style";
import App from "./App.vue";
import pinia from "./store";
import router from "./router";
createApp(App).use(pinia).use(router).mount("#app");
设置动态标题
通过路由守卫实现动态修改标题,修改src/router/index.ts
文件。
// src/router/index.ts
// ...
router.beforeEach((to, from, next) => {
const { title } = to.meta;
if (title) {
document.title = `${title}`;
}
next();
});
// ...
请求和接口管理
请求使用当下主流库Axios。Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js。
安装依赖
pnpm add axios
封装Axios
// src/utils/request.ts
import axios from "axios";
import { showLoadingToast, closeToast } from "vant";
const service = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 5000,
});
service.interceptors.request.use(
(config) => {
showLoadingToast({
message: "加载中...",
forbidClick: true,
});
return config;
},
(error) => {
return Promise.reject(error);
}
);
service.interceptors.response.use(
(response) => {
closeToast();
const res = response.data;
if (res.code !== 20000) {
return Promise.reject(new Error(res.message || "Error"));
} else {
return res;
}
},
(error) => {
closeToast();
return Promise.reject(error);
}
);
export default service;
接口管理
在src/api/*
目录下统一管理接口,举个🌰:
// src/api/home
import request from "@/utils/request";
export function getHomeData() {
return request({
url: "/home/data",
method: "get",
});
}
设置跨域处理
由于浏览器的同源策略限制,跨域请求会被浏览器阻止,以防止恶意网站访问用户的敏感数据。为了在开发过程中解决跨域问题,Vite 提供了一个代理配置选项,允许将特定请求转发到后端 API 服务器,以便前端应用程序可以与 API 进行交互。
// vite.config.ts
export default defineConfig({
// ...
server: {
proxy: {
"/api": {
target: "http://api.example.com",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
// ...
});
功能补充
深色模式
Vant 提供了深色模式、主题定制等能力。
-
全局样式调整
/* style.css */
.van-theme-light body {
background-color: #eff2f5;
}
.van-theme-dark body {
background-color: black;
color: #fff;
}
-
ConfigProvider 全局配置
App.vue
文件
<script setup lang="ts">
import type { ConfigProviderTheme } from "vant";
import { computed } from "vue";
import { useThemeStore } from "@/stores/theme";
const themeStore = useThemeStore();
const theme = computed(() => themeStore.theme as ConfigProviderTheme);
</script>
<template>
<van-config-provider :theme="theme">
<router-view />
</van-config-provider>
</template>
<style lang="scss" scoped></style>
-
创建主题状态
// src/stores/theme.ts
import { defineStore } from "pinia";
export const useThemeStore = defineStore("theme", {
state: () => ({
theme: "light",
}),
getters: {},
actions: {
setTheme(theme: string) {
this.theme = theme;
},
},
persist: true,
});
-
切换深色模式🌰
views/home/index.vue
文件
<script setup lang="ts">
import { ref, watch } from "vue";
import { useThemeStore } from "@/stores/theme";
const themeStore = useThemeStore();
const checked = ref(themeStore.theme === "dark");
watch(checked, (val) => {
if (val) {
themeStore.setTheme("dark");
} else {
themeStore.setTheme("light");
}
});
</script>
<template>
<van-cell-group title="基础的H5开发框架" inset>
<van-cell title="暗黑模式">
<template #right-icon>
<van-switch v-model="checked" />
</template>
</van-cell>
<van-cell title="关于" is-link to="about" />
</van-cell-group>
</template>
<style lang="scss" scoped></style>
-
页面效果

页面进度条
作为用户体验提升,网页加载过程中显示进度条是不可或缺的,这里也是选择主流方案:NProgress
。
pnpm add -D nprogress @types/nprogress
修改路由src/router/index.ts
文件
import { createRouter, createWebHashHistory } from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
// ...
const router = createRouter({
history: createWebHashHistory(),
routes,
});
router.beforeEach((to, from, next) => {
NProgress.start();
console.log(from);
const { title } = to.meta;
if (title) {
document.title = `${title}`;
}
next();
});
router.afterEach(() => {
NProgress.done();
});
export default router;
性能优化
移动端网页调试工具(vConsole)
移动端网页调试不像PC端可以打开控制台,所以需要借助插件实现类似功能。
# 安装vconsole
pnpm add -D vconsole
# 安装插件
pnpm add -D vite-plugin-vconsole
配置vite.config.ts
// ...
import { viteVConsole } from "vite-plugin-vconsole";
export default defineConfig(({ command, mode }) => {
const root = process.cwd();
const env = loadEnv(mode, root);
console.log(command, mode, env);
return {
plugins: [
// ...
viteVConsole({
entry: [resolve(__dirname, "./src/main.ts")],
localEnabled: command === "serve",
enabled: command === "serve",
config: {
maxLogNumber: 1000,
theme: "dark",
},
}),
],
// ...
};
});
调试工具效果
代码检查-Eslint
代码检查的好处不用多说,这个地方推荐行业内主流的方案:ESLint,这里我们可以跟着官网操作。
-
安装依赖
npm init @eslint/config
-
根据提示选择:

-
修改
.eslintrc.cjs
文件这里会自动生成
.eslintrc.cjs
文件,需要进行部分调整,后续规则可自行追加。
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:vue/vue3-essential",
"plugin:@typescript-eslint/recommended",
],
overrides: [],
parser: "vue-eslint-parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
parser: "@typescript-eslint/parser",
},
plugins: ["vue", "@typescript-eslint"],
rules: {
"vue/multi-word-component-names": "off",
},
};
-
添加忽略文件 .eslintignore
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
.eslintrc.js
prettier.config.js
/src/mock/*
-
添加 package.json
命令
"scripts": {
// ...
"lint": "eslint --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
// ...
},
代码格式化-Pettier
代码检查添加了,为了在开发的时候主动按照规则格式化代码,那就可以搭配Pettier使用了。
-
安装依赖
根据prettier官网描述,搭配ESLint使用需要安装eslint-config-prettier,避免与Prettier 冲突的 ESLint 规则。为了整合prettier和ESLint,会用到另外一个插件:eslint-plugin-prettier。
pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier
-
添加 .prettierrc.cjs
文件
module.exports = {
semi: true,
bracketSpacing: true,
singleQuote: false,
arrowParens: "always",
};
-
添加 .prettierignore
忽略文件
/dist/*
.local
.output.js
/node_modules/**
**/*.svg
**/*.sh
/public/*
-
修改 .eslintrc.cjs
文件
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:vue/vue3-essential",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
overrides: [],
parser: "vue-eslint-parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
parser: "@typescript-eslint/parser",
},
plugins: ["vue", "@typescript-eslint"],
rules: {
"vue/multi-word-component-names": "off",
},
};
TODO List
Git Hook 工具、打包分析、日志监控、去掉 console.log、splitChunks分包、gzip 压缩等。
总结:到这里,基本完成了一个移动端网页开发的基础框架,后续可以按需增加需要的优化点。
作者介绍
