使用vite创建uni-app开发环境,集成eslint+stylelint+typescript。适用全端。

Property
Mar 8, 2022 09:35 AM

uniApp-template

前言

因为每次在开发新项目时,都需要一个开箱即用的基础框架,避免重新开始搭建而浪费时间,遂记录下从零开始搭建一个开箱即用的框架。随着前端的发展,未来版本的更新需要重新搭建框架时也可以作个参考。
实现功能
  • 使用 Vue3 进行开发
  • 构建工具 使用 Vite
  • 使用 Vuex
  • 集成 Typescript
  • 集成 Scss 来编写 css
  • 集成 Eslint + Stylelint + Prettier 来规范和格式化代码
  • 环境区分
  • 封装 uni-request 请求
  • 集成 Mock 辅助开发
  • 集成 uni-ui
项目整体目录
├── dist/ // 打包文件的目录
├── env/ // 环境配置目录
| ├── .env.development // 开发环境
| ├── .env.production // 生产环境
├── mock/ // mock
| ├── index.ts
├── src/
| ├── assets/ // 存放图片
| ├── components/ // 自定义组件
| ├── pages/ // 页面
| ├── store/
| | ├── index.ts // store 配置文件
| | ├── index.d.ts // 声明文件
| | └── modules
| | └── system.ts // 自己的业务模块,这里写|个示例
| ├── styles/ // 样式文件
| ├── App.vue
| ├── env.d.ts
| ├── main.ts
| ├── manifest.json
| ├── pages.json
| ├── shims-vue.d.ts
| └── uni.scss
├── .eslintignore // eslint忽略文件
├── .eslintrc.js // eslint配置文件
├── .gitignore // git忽略文件
├── .prettierrc // prettier配置文件
├── .stylelintignore // stylelint忽略文件
├── index.html
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── README.md
├── stylelint.config.js // stylelint配置文件
├── tsconfig.json
└── vite.config.ts

生成基本框架

使用官方提供 Vue3/Vite 版本的模板来生成我们的基础项目。
npx degit dcloudio/uni-preset-vue#vite-ts uniApp-template
或者直接从 gitee 上下载。

做一些简单的配置

基础模板中功能比较少,我们对生成的基础框架添加一些自定义的配置。
  1. 规范目录
  1. 配置别名 @ 来表示 src 目录
  1. 配置代理解决开发环境跨域的问题
  1. 打包调整生成规范的文件
修改 vite.config.ts 文件
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import { resolve } from 'path'

// https://vitejs.dev/config/
export default defineConfig({
 plugins: [uni()],
 resolve: {
 // 配置别名
 alias: {
 '@': resolve(__dirname, 'src')
 }
 },
 css: {
 // css预处理器
 preprocessorOptions: {
 scss: {
 // 因为uni.scss可以全局使用,这里根据自己的需求调整
 additionalData: '@import "./src/styles/global.scss";'
 }
 }
 },
 // 开发服务器配置
 server: {
 host: '0.0.0.0',
 port: 8080,
 // 请求代理
 proxy: {
 // 个人习惯,这里就用/dev作为前缀了
 '/dev': {
 target: 'https://xxx.com/api',
 changeOrigin: true,
 // 路径重写,去掉/dev
 rewrite: (path) => path.replace(/^\/dev/, '')
 }
 }
 },
 build: {
 // 禁用 gzip 压缩大小报告,以提升构建性能
 brotliSize: false,
 /** 配置h5打包js,css,img分别在不同文件夹start */
 assetsDir: 'static/img/',
 rollupOptions: {
 output: {
 chunkFileNames: 'static/js/[name]-[hash].js',
 entryFileNames: 'static/js/[name]-[hash].js',
 assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
 }
 }
 /** 配置h5打包js,css,img分别在不同文件夹end */
 }
})
tsconfig.json 中添加配置,使编辑器可以识别我们的别名。
{
 "baseUrl": "./",
 "paths": {
 "@/*": ["src/*"]
 }
}
最后使用 pnpm 安装依赖,然后运行到 H5 看看是否成功。
# 安装依赖
pnpm i

# 运行
npm run dev:h5
notion image

配置 vuex

因为基础模板中已经给我们依赖了 vuex,所以我们这里就不用再安装了,我们需要新建一个 src/store 文件夹来管理我们的 store
└── src/
 ├── store/
 ├── index.ts // store 配置文件
 ├── index.d.ts // 声明文件
 ├── modules
 ├── system.ts // 自己的业务模块,这里写一个示例
首先编写我们的声明文件,这里对我们所有的 store 添加一个声明,以便我们在使用的使用编辑器有提示。
src/store/index.d.ts
import { rootStateType } from './index'
import { systemStateType } from './modules/system'

export interface StateType extends rootStateType {
 system: systemStateType
}
然后在配置文件中来实例化 store
src/store/index.ts
import { createStore } from 'vuex'
import { StateType } from './index.d'

// 批量引入其他module,
const files = import.meta.globEager('./modules/*.ts') // vite的写法
const keys = Object.keys(files)

const modules: any = {}

keys.forEach((key) => {
 if (Object.prototype.hasOwnProperty.call(files, key)) {
 // 提取文件的名字作为模块名
 modules[key.replace(/(\.\/modules\/|\.ts)/g, '')] = files[key].default
 }
})

/** 全局的state,这个看自己的需求,如果有用到就在createStore中添加 */
export interface rootStateType {}

export default createStore <
 StateType >
 {
 modules
 }
在 modules 文件夹中根据自己的业务来创建模块,同时在 index.d.ts 中加入声明。例如:src/store/modules/system.ts
import { Module } from 'vuex'
import { rootStateType } from '@/store'

export interface systemStateType {
 title: string
}

const systemModule: Module<systemStateType, rootStateType> = {
 namespaced: true,
 state: () => ({
 title: '你好,我是uni-app'
 })
}

export default systemModule
main.ts 文件中挂载 vuex
import { createSSRApp } from 'vue'
import App from './App.vue'
import store from './store'

// eslint-disable-next-line import/prefer-default-export
export function createApp() {
 const app = createSSRApp(App).use(store)
 return {
 app
 }
}
最后使用 vuex,常见的两种用法。
// 使用this
this.$store.state.system.title

// 使用useStore
import { useStore } from 'vuex'
const store = useStore()
console.log(store.state.system.title)
notion image
到这一步我们已经可以正常使用 vuex 了,但是此时会发现编辑器对 store 没有检测到 ts 类型声明。需要我们扩展下 ts 类型声明。
创建 src/shims-vue.d.ts。名字其实无所谓,只要是在 src 下的.d.ts 文件就行,这里延续 vue 风格的命名。
// import 'vue' // 必须要引入vue,否则就成了覆盖
import { StateType } from '@/store/index.d'
import { InjectionKey } from 'vue'
import { Store } from 'vuex'

/**
 * 这里为什么用vue,而不用@vue/runtime-core,是因为使用pnpm安装依赖是,node_modules中没有@vue/runtime-core,
 * 会导致找不到模块而类型声明失败。
 */
// declare module '@vue/runtime-core' {
declare module 'vue' {
 interface ComponentCustomProperties {
 // 这里扩展this.$store,还可以在这里对this添加其他的声明
 $store: Store<StateType>
 }
}

// 扩展useStore声明
declare module 'vuex' {
 export function useStore<S = StateType>(injectKey?: InjectionKey<Store<S>> | string): Store<S>
}

// 这个导出一个东西也可以,或者上面引入vue
export {}
notion image

集成 eslint

安装 eslint
pnpm add eslint -D
生成 eslint 配置文件,这一块参考是参考这篇文件写的。原文地址
npx eslint --init
    • How would you like to use ESLint? (你想如何使用 ESLint?)
      notion image
      我们这里选择 To check syntax, find problems, and enforce code style(检查语法、发现问题并强制执行代码风格)
    • What type of modules does your project use?(你的项目使用哪种类型的模块?)
      我们这里选择 JavaScript modules (import/export)
      notion image
    • Which framework does your project use? (你的项目使用哪种框架?)
      notion image
      我们这里选择 Vue.js
    • Does your project use TypeScript?(你的项目是否使用 TypeScript?)
      notion image
      我们这里选择 Yes
    • Where does your code run?(你的代码在哪里运行?)
      notion image
      我们这里选择 Browser 和 Node(按空格键进行选择,选完按回车键确定)
    • How would you like to define a style for your project?(你想怎样为你的项目定义风格?)
      notion image
      我们这里选择 Use a popular style guide(使用一种流行的风格指南)
    • Which style guide do you want to follow?(你想遵循哪一种风格指南?)
      我们这里选择 Airbnb(github 上 star 最高)
      notion image
    • What format do you want your config file to be in?(你希望你的配置文件是什么格式?)
      notion image
      我们这里选择 JavaScript
    • Would you like to install them now with npm?(你想现在就用 NPM 安装它们吗?)
      notion image
      我们这里选择 No,根据提示需要安装的依赖包,我们自己使用 pnpm 安装。注意 eslint 的版本,之前我们安装过可以不再安装了。
pnpm add -D eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest eslint-config-airbnb-base@latest eslint@^8.2.0 eslint-plugin-import@^2.25.2 @typescript-eslint/parser@latest
修改.eslintrc.js文件
// 因为我们使用的是 vue3,所以使用 vue3 的校验规则
plugin:vue/essential 修改成 plugin:vue/vue3-recommended

// 增加uni的声明
globals: {
 /** 避免uni报错 */
 uni: true,
 UniApp: true
},
增加 eslint 忽略文件 src/.eslintignore
index.html
*.d.ts

集成 stylelint

我们可以使用 stylelint 来规范我们的 css 写法。
pnpm add -D stylelint stylelint-config-rational-order stylelint-config-recommended-scss stylelint-config-recommended-vue stylelint-config-standard-scss stylelint-order
根目录下新增配置文件 stylelint.config.js
module.exports = {
 extends: [
 'stylelint-config-standard-scss',
 'stylelint-config-recommended-vue',
 'stylelint-config-recommended-vue/scss',
 'stylelint-config-rational-order'
 ],
 rules: {
 // 使用4格缩进
 indentation: 4,
 // 可以使用rpx单位
 'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }]
 }
}
增加 stylelint 的忽略文件 src/.stylelintignore
**/*.js
**/*.ts
dist
node_modules
index.html
*.md

集成 prettier

我们使用 prettier 来搭配 eslint 和 stylelint 使用。
安装依赖
pnpm add -D prettier eslint-config-prettier eslint-plugin-prettier stylelint-config-prettier
根目录下添加 prettier 的配置文件 .prettierrc
{
 "trailingComma": "none",
 "printWidth": 100,
 "tabWidth": 4,
 "semi": false,
 "singleQuote": true,
 "endOfLine": "auto"
}

修改 .eslintrc.js 来解决与 eslint 的冲突
extends: [
 // ...
 'plugin:prettier/recommended' // 一定要放在最后一项
],
rules: {
 'prettier/prettier': [
 'error',
 {
 trailingComma: 'none',
 printWidth: 100,
 tabWidth: 4,
 semi: false,
 singleQuote: true,
 endOfLine: 'auto'
 }
 ]
}
修改 stylelint.config.js 来解决与 stylelint 的冲突
extends: [
 'stylelint-config-prettier' // 一定要放在最后一项
]
完成以上步骤,就可以愉快的敲代码了。

你可能遇到的问题:

eslint 报:使用别名报错 import/no-unresolved

notion image
解决办法:
安装依赖
pnpm add eslint-import-resolver-alias -D
修改 .eslintrc.js
settings: {
 'import/resolver': {
 alias: {
 map: [['@', './src']],
 extensions: ['.js', '.jsx', '.ts', '.tsx']
 }
 }
},
rules: {
 // 解决vite+airbnb导致eslint报错import/extensions
 'import/extensions': [
 'error',
 'ignorePackages',
 {
 js: 'never',
 jsx: 'never',
 ts: 'never',
 tsx: 'never'
 }
 ]
}

stylelint 报错:Unknown word

notion image
解决办法:将报错的文件添加到忽略文件(.stylelintignore)即可

vite.config.ts 文件报错

notion image
解决办法:配置 eslint 规则
rules: {
 'import/no-extraneous-dependencies': ['error', { devDependencies: true }]
}
编译器宏,如 defineProps 和 defineEmits 生成 no-undef 警告
notion image
修改 .eslintrc.js
env: {
 'vue/setup-compiler-macros': true
}

环境区分

实现功能:
  • 可以直接区分开发环境和生产环境
  • 自定义环境变量增加 typescript 提示
在根目录下新建 env 文件夹用来存放环境变量配置文件,同时修改 vite 配置(环境变量的根目录)。
修改 vite.config.js
export default defineConfig({
 envDir: resolve(__dirname, 'config')
})
同时新增 env 文件夹
├── env/
 ├── .env.development // 开发环境
 ├── .env.production // 生产环境
 ├── index.d.ts // 声明文件
需要检查下 tsconfig.json 文件是否包含了 env/index.d.ts,如果没有需要我们添加一下。
"include": ["env/index.d.ts"]
编辑 src/.env.d.ts 文件增加自定义变量的声明
/** 扩展环境变量import.meta.env */
interface ImportMetaEnv {
 /** 这里增加自定义的声明 */
 VITE_REQUEST_BASE_URL: string
}

封装 uni-request 请求

实现功能:
  • 统一配置接口地址
  • 统一设置超时时间/报文格式/报文加密
  • 统一身份认证
  • 统一处理登录超时/接口异常提示
  • 统一返回接口格式
新建 src/utils/request/index.ts 用来存放我们的代码。
/**
 * uni-request请求封装
 * 1. 统一配置接口地址
 * 2. 统一设置超时时间/报文格式/报文加密
 * 3. 统一身份认证
 * 4. 统一处理登录超时/接口异常提示
 * 5. 统一返回接口格式
 */

type responseType = {
 code: number
 success: boolean
 msg: string
 result: any
}

const request = (config: UniApp.RequestOptions) => {
 let url: string
 if (/^(http|https):\/\/.*/.test(config.url)) {
 // 如果是以http/https开头的则不添加VITE_REQUEST_BASE_URL
 url = config.url
 } else {
 url = import.meta.env.VITE_REQUEST_BASE_URL + config.url
 }
 return new Promise<responseType>((resolve, reject) => {
 uni.request({
 ...config,
 url,
 /** 统一设置超时时间 */
 timeout: config.timeout || 60000,
 header: {
 ...config.header,
 /** 统一报文格式 */
 'Content-Type': 'application/json;charset=UTF-8'
 /** 统一身份认证 */
 // Authorization: Token
 },
 success(res) {
 // 200状态码表示成功
 if (res.statusCode === 200) {
 resolve(res.data as any)
 return
 }
 /**
 * 这里可以做一些登录超时/接口异常提示等处理
 */
 reject(res.data)
 },
 fail(result) {
 reject(result)
 }
 })
 })
}

export default {
 /**
 * get请求
 * @param url 请求地址
 * @param data 请求的参数
 * @param options 其他请求配置
 */
 get: (url: string, data?: UniApp.RequestOptions['data'], options?: UniApp.RequestOptions) => {
 return request({
 ...options,
 url,
 data,
 method: 'GET'
 })
 },
 /**
 * post请求
 * @param url 请求地址
 * @param data 请求的参数
 * @param options 其他请求配置
 */
 post: (url: string, data?: UniApp.RequestOptions['data'], options?: UniApp.RequestOptions) => {
 return request({
 ...options,
 url,
 data,
 method: 'POST'
 })
 }
}
使用方式
import request from '@/utils/request'

request
 .get('/api/getList', {
 page: 1,
 size: 20
 })
 .then((res) => {
 console.log(res)
 })

集成 Mock 辅助开发

实现功能:
  • 统一管理我们想要 mock 的接口
  • 便捷切换是否 mock
  • 自由控制哪些接口 mock,哪些接口真实请求
  • 对于调用接口的地方是否 mock 是无感知的
比如:
import request from '@/utils/request'
/**
 * 这样写,既可以是mock数据,也可以是调用接口。
 */
request.get('/getUserInfo')
安装 mock
pnpm add mockjs
pnpm add -D @types/mockjs
前面我们在环境变量里添加了统一接口地址。我们先制定如下规则:
# 请求接口地址
VITE_REQUEST_BASE_URL = /dev # 这样可以直接使用代理请求
VITE_REQUEST_BASE_URL = /dev/mock # 这样就可以开启mock
VITE_REQUEST_BASE_URL = https://xxx.com/api # 这样就是直接请求接口
根目录下创建 mock/index.ts 文件来存放我们的 Mock 规则。
import Mock from 'mockjs'

// 基于我们制定的规则,这里必须做下判断,这个很重要。
if (/\/mock$/.test(import.meta.env.VITE_REQUEST_BASE_URL)) {
 // 这里添加 /getUserInfo 这个接口mock数据
 Mock.mock(`${import.meta.env.VITE_REQUEST_BASE_URL}/getUserInfo`, {
 code: 200,
 success: true,
 msg: '',
 result: {
 name: Mock.Random.cname()
 }
 })
}
在 main.js 中引入下。(为什么要引入?不引入代码怎么执行啊)
import '../mock'
接下来改造我们封装的请求。
修改 src/utils/request/index.ts
import Mock from 'mockjs'

if (/^(http|https):\/\/.*/.test(config.url)) {
 // 如果是以http/https开头的则不添加VITE_REQUEST_BASE_URL
 url = config.url
 // eslint-disable-next-line no-underscore-dangle
} else if (Mock._mocked[import.meta.env.VITE_REQUEST_BASE_URL + config.url]) {
 // 如果是mock数据,Mock._mocked上记录有所有已设置的mock规则。
 url = import.meta.env.VITE_REQUEST_BASE_URL + config.url
} else {
 /**
 * 开启mock时需要去掉mock路径,不能影响正常接口了。
 * 如果碰巧你接口是 /api/mock/xxx这种,那VITE_REQUEST_BASE_URL就配置/api/mock/mock吧
 */
 url = import.meta.env.VITE_REQUEST_BASE_URL.replace(/\/mock$/, '') + config.url
}
// src/shims-vue.d.ts

// 扩展mock
declare module 'mockjs' {
 /** 所有已注册的mock规则 */
 const _mocked: Record<string, any>
}