可乐炸鸡

V1

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,这里我们选择按需引入组件样式。

  1. 安装unplugin-vue-components 插件
 # 通过 pnpm 安装
 pnpm add unplugin-vue-components -D
  1. 配置插件

    本项目基于 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()],
    }),
  ],
};

  1. 使用Vant组件
<template>
  <van-button type="primary" />
</template>
  1. 引入函数组件的样式

    Vant 中有个别组件是以函数的形式提供的,包括 ToastDialogNotifyImagePreview 组件。可以在项目的入口文件中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",
        },
      }),
    ],
    // ...
  };
});

调试工具效果

调试工具1调试工具2

代码检查-Eslint

代码检查的好处不用多说,这个地方推荐行业内主流的方案:ESLint,这里我们可以跟着官网操作。

  1. 安装依赖
npm init @eslint/config
  1. 根据提示选择:
安装提示
安装提示
  1. 修改.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",
 },
};
  1. 添加忽略文件.eslintignore
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
.local
/bin
.eslintrc.js
prettier.config.js
/src/mock/*
  1. 添加package.json命令
"scripts": {
 // ...
 "lint""eslint --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
 // ...
},

代码格式化-Pettier

代码检查添加了,为了在开发的时候主动按照规则格式化代码,那就可以搭配Pettier使用了。

  1. 安装依赖

    根据prettier官网描述,搭配ESLint使用需要安装eslint-config-prettier,避免与Prettier 冲突的 ESLint 规则。为了整合prettier和ESLint,会用到另外一个插件:eslint-plugin-prettier。

pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier
  1. 添加.prettierrc.cjs文件
module.exports = {
 semi: true,
 bracketSpacing: true,
 singleQuote: false,
 arrowParens: "always",
};
  1. 添加.prettierignore忽略文件
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*
  1. 修改.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 压缩等。

总结:到这里,基本完成了一个移动端网页开发的基础框架,后续可以按需增加需要的优化点。

分类:

前端

标签:

前端

作者介绍

可乐炸鸡
V1