夜雨聆风学习资料网

ARTICLE · 1117394

uni-app 项目初始化:unibest 上手实录

uni-app 项目初始化:unibest 上手实录

写 uni-app 的第一个项目,光花在基建上的时间可能比写业务还多:pages.json 手写、请求封装自己撸、登录拦截自己写、三个端各配一遍环境变量、HBuilderX 和命令行来回切……每个新项目重来一遍。这两年 uni-app 自己也出了官方的 uni-app x 工程化方案,但在 Vue 3 + Vite 这条路线上,unibest 是目前我看到的完成度最高的模板。这篇记的是我实际用它跑通一个项目的过程:它替你做了什么、怎么装、三端怎么跑、以及几个我真踩过的坑。

一、它到底是什么

unibest(作者菲鸽)不是一个 UI 库,也不是 uni-app 的升级版,它是一个跨端项目模板 + 配套 CLI。技术栈本身现在都不算新鲜:

  • uni-app + Vue 3 + TypeScript
  • Vite 5 构建(不依赖 HBuilderX 写代码)
  • UnoCSS 原子化样式
  • Pinia + pinia-plugin-persistedstate 状态管理
  • alova 请求库(适配 uni.request)
  • z-paging 列表分页、vue-i18n 多语言、dayjs
  • Vitest、ESLint、husky、commitlint、lint-staged

它真正的价值不在这些依赖本身,而在把这些东西按一套约定接好,并且暴露为你能改的配置文件。核心是三层:

最关键的一层是最上面那层:pages.json 和 manifest.json 是构建产物,不是你手写的源文件

提示:一句话概括它替你做的事:把 uni-app 里「每个项目都要重来一遍」的基建,全部预置好,并且每一条都留了修改入口。你要改,就改 .config.ts,不用去翻 pages.json。

二、环境准备:先确认版本

这一步我被绊了一下。网上包括不少 CSDN 教程写的都是 node>=18、pnpm>=7.30,但 v4 版的 package.json 里 engines 字段已经收紧了,官方「快速开始」页也是新口径:

// package.json(v4.x)
"engines": {
  "node": ">=20",
  "pnpm": ">=9"
}
依赖
要求
说明
Node.js
>=20
18 已不满足 engines 声明
pnpm
>=9
没有就先 npm i -g pnpm
Git
必需
没装的话 husky 安装阶段会直接报错
VS Code
可选
WebStorm / Trae / Cursor 也行;要用 v3 代码块,建议 VS Code
HBuilderX
按需
只有跑 App 端和云打包时离不开
注意:unibest 的 preinstall 里是 npx only-allow pnpm——如果你顺手用 npm install 或 yarn,会被直接拦下来。这不是 bug,是刻意的:uni-app 的依赖对包管理器行为敏感,别混用。

三、创建项目:三种方式

推荐用 CLI 创建,因为可以顺便把平台、UI 库、登录策略、多语言一起定下来。

# 方式一:交互式(第一次用推荐,能看到所有选项)
pnpm create unibest my-project
cd my-project
pnpm install
pnpm dev
# 方式二:命令行参数直接指定,不进交互
pnpm create unibest my-project --i18n --login
# 方式三:项目建好后再补功能
cd my-project
pnpm create unibest add i18n
pnpm create unibest add login
pnpm create unibest add i18n login lime-echart ucharts

CLI 底层是从仓库的 base 分支克隆模板,所以给你的一定是最新基线版本。参数表:

参数
可选值
作用
-p, --platformh5mp-weixinappmp-alipaymp-toutiao
 等
目标平台
-u, --uinonewot-uiwot-ui-v2uview-plusuv-uisard-uniappuview-protdesign
UI 库
-l, --login
开关
登录策略
-i, --i18n
开关
多语言
# 一次性指定平台和 UI 库(v4 新增 wot-ui-v2)
pnpm create unibest my-project -u wot-ui-v2 -p h5,mp-weixin
# 补加功能时想覆盖已有配置
pnpm create unibest add i18n --force
提示:有个和旧版文档不一样的地方:UI 库现在是创建时选的,不是内置固定的。选 wot-ui-v2 时,CLI 会自动注入 wot-ui-resolver.ts 并改好 vite.config.ts,避免 wd-button 没样式。如果你照老文章以为「装了 unibest 就一定有 wot-ui」,到 pnpm dev 时才发现组件全是方的,就是版本认知的问题。

四、目录结构:约定式路由怎么算的

建出来的项目长这样,源码全在 src/ 下:

路由是「文件位置」推导出来的,新建页面只要建目录,不用去注册

页面的导航栏标题这类配置,不写在一个集中式 JSON 里,而是写在页面文件自己的 definePage 块里:

<script setup lang="ts">
// src/pages/detail/index.vue
definePage({
  style: {
    navigationBarTitleText: '商品详情',
    // 需要自定义导航栏时打开(H5/小程序表现不同,慎用)
    // navigationStyle: 'custom',
  },
})
</script>

全局级的页面配置写在 pages.config.ts:

// pages.config.ts
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
export default defineUniPages({
  globalStyle: {
    navigationBarTextStyle: 'black',
    navigationBarBackgroundColor: '#FFFFFF',
  },
})

应用级配置(appid、平台配置、h5 的 router base)写在 manifest.config.ts:

// manifest.config.ts
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
export default defineManifestConfig({
  'mp-weixin': {
    appid: VITE_WX_APPID,
    setting: {
      // 开发期关掉合法域名校验,方便本地联调
      urlCheck: false,
    },
    usingComponents: true,
  },
  h5: {
    // 部署在子目录时改这里,比如 /admin/
    router: { base: '/' },
  },
  locale: VITE_FALLBACK_LOCALE,
})
提示:这三个文件配合环境变量目录 env/,就是 unibest 的配置体系:pages.config.ts 管路由,manifest.config.ts 管应用,env/ 管多环境。构建时 loadEnv 按 mode 读对应文件,变量通过 import.meta.env 消费。想区分微信的 develop / trial / release 环境,官方给了平台级变量命名约定,比如 VITE_SERVER_BASEURL__WEIXIN_DEVELOP、..._WEIXIN_TRIAL、..._WEIXIN_RELEASE,不用自己写判断逻辑。

五、几个我最先用上的内置能力

1. 请求层已经是适配好的

unibest v4 用的是 alova 而不是自己撸 uni.request 包装,配了 uniapp 适配器。简化后大致是:

// src/http/http.ts(示意)
import { createAlova } from 'alova'
import AdapterUniapp from '@alova/adapter-uniapp'
export const alovaInstance = createAlova({
  baseURL: import.meta.env.VITE_SERVER_BASEURL,
  ...AdapterUniapp(),
})

配套的 src/http/interceptor.ts 是请求拦截器,注入 token、开 loading 计数、统一错误提示都挂在这里;src/router/interceptor.ts 是路由拦截器,登录守卫写在这里——它有个「登录策略」开关,两种模式:DEFAULT_NO_NEED_LOGIN 是黑名单策略(默认,可以直接进 APP),DEFAULT_NEED_LOGIN 是白名单策略(默认进不去,必须先登录),配套一个 EXCLUDE_PAGE_LIST 排除列表,语义跟着模式走。这两个文件是「业务相关」的部分,它只给你骨架和位置,token 怎么存、错误怎么提示、登录态怎么刷新都得自己写——不然容易以为「用了 unibest 就自动有登录功能」。

2. 启动顺序有讲究,别乱改

// src/main.ts(示意)
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import { routeInterceptor } from './router/interceptor'
import { requestInterceptor } from './http/interceptor'
export function createApp() {
  const app = createSSRApp(App)
  app.use(createPinia())
  // 顺序必须:store → 路由拦截 → 请求拦截
  // 两个拦截器都要读 store,不先注册 pinia 会拿不到 token
  app.use(routeInterceptor)
  app.use(requestInterceptor)
  return { app }
}
注意:这是我看源码时最想强调的一点:Pinia 必须先于两个拦截器注册。注册顺序反了,拦截器里 useTokenStore() 拿到的是空的,表现为「首屏所有请求都不带 token」,而且控制台不一定报明显的错,很难查。

3. 状态持久化是白名单式的

token、user 这类 store 内置了 persist,直接落盘:

// src/store/token.ts(示意)
import { defineStore } from 'pinia'
export const useTokenStore = defineStore('token', {
  state: () => ({
    token: '',
  }),
  actions: {
    setToken(token: string) { this.token = token },
  },
  persist: true,
})

具体存哪些字段、怎么存,还是得按自己业务定。这块我在专栏的 Pinia 持久化那篇里展开讲过——核心是别把 loading、弹窗开关这类临时状态也 persist 进去,不然会出现「带着上次的 loading 启动」这类诡异问题。

4. v3 代码块和自动导入

在 .vue 里输入 v3 按 Tab,能直接生成页面骨架(script setup + template + style)。另外 unplugin-auto-import 配好了,uni-app 的 API 可以直接用,不用手写 import:

<script setup lang="ts">
// 不用 import,直接用
const list = ref([])
onLoad((options) => {
  console.log(options)
})
onShow(() => {})
</script>

六、三端运行与发布

命令是固定的,我整理成一张表,实际项目里就是照这个跑:

目标
开发(热更新)
构建
产物位置
H5
pnpm dev
 / pnpm dev:h5
pnpm build:h5dist/build/h5
微信小程序
pnpm dev:mppnpm build:mpdist/build/mp-weixin
App
pnpm dev:apppnpm build:appdist/build/app

几个容易搞混的细节:

  • H5
    pnpm dev 之后开 http://localhost:9000/,端口在 env/ 里配(VITE_APP_PORT)。
  • 微信小程序
    先跑 pnpm dev:mp,然后在微信开发者工具里导入 dist/dev/mp-weixin,不是导入项目根目录。构建完是 dist/build/mp-weixin,导入后点「上传」。
  • App
    跑 pnpm dev:app 之后,用 HBuilderX 导入 dist/dev/app,选运行到模拟器或基座。安卓和鸿蒙有更省事的做法——把整个项目导入 HBuilderX,用它的菜单直接运行,不用走产物目录。
  • 其他平台
    支付宝、抖音、快手、小红书、飞书、QQ、百度、快应用都有对应脚本,dev:mp-alipay、dev:mp-toutiao、dev:mp-xhs……命名规律就是 dev:<平台>,用之前记得在 package.json 的 scripts 里确认一下。
# 其他常用脚本
pnpm type-check     # vue-tsc 类型检查
pnpm lint           # ESLint
pnpm lint:fix       # 自动修
pnpm test           # Vitest 跑单测
pnpm openapi        # 用 openapi-ts-request 从接口文档生成类型化请求代码
pnpm upload:mp      # miniprogram-ci 脚本,配合 CI 发布用
提示:内置了一组 Git hooks:husky + commitlint 走 Conventional Commits 规范,提交信息格式不对会被拦。第一次提交记得带上 feat: / fix: 这类前缀。顺便说一句,模板里 lint-staged 的配置是作者写的一句彩蛋——所有文件匹配到的命令是 echo 祝菲鸽身体健康,所以装完之后 lint-staged 实际上什么都不检查。你大概率要自己把这块改成 eslint --fix。

七、踩过的坑

坑一:照着老教程装,环境版本对不上

搜到的多数第三方教程(包括几篇 CSDN 的)还写着 node 18 / pnpm 7.30。v4 已经收紧了,pnpm 8 装完会有 engines 警告。直接照官方文档的 >=20 / >=9 走,别信第三方教程里的版本号。

坑二:以为 UI 库是内置的

老文章说 unibest = uniapp + wot-ui。现在 v4 的 CLI 是在创建时选 UI 库的,-u none 就是纯 UnoCSS 不带组件库。选完之后 node_modules 里有没有那套组件,取决于你创建时的选择——装完记得先确认这一点,再 pnpm dev。

坑三:H5 首屏底部 tabbar 没出来

官方注意事项里第一条就写了:代码跑起来后 H5 底部没有 tabbar,刷新浏览器或者重跑一次 pnpm dev 即可。属于首屏编译时序的小问题,不用排查。

坑四:自动导入的 API 报「未定义」

同样在官方注意事项里:跑一次 pnpm dev 就好。这些 API 的类型声明是构建时生成的,刚 clone 完仓库还没生成,第一次 dev 会报一片红,看着吓人,实际重启就好。

坑五:没有 Git,install 阶段就挂了

husky 需要 Git 仓库。如果你是下载 zip 而不是 clone,pnpm install 会在 prepare 阶段报错。git init 一下就行。

坑六:H5 部署到子目录后白屏

产物路径变了但 base 还是 /,资源全 404。改 manifest.config.ts 里的 h5.router.base,和 nginx 的 location 对齐。

坑七:把 unibest 当成了「零配置框架」

这个是观念上的坑。它替你做的是工程约定和接线,业务决策一件没替你做——登录怎么登、token 怎么刷新、接口怎么组织、错误怎么提示、多语言文案翻哪几份,都得自己写。可以省掉的基建,省不掉业务。想清楚这点,才不会觉得「用模板就等于什么都没做」。

八、什么情况下适合用、什么情况下别用

适合
不太适合
用 Vue 3 + Vite 路线的 uni-app 新项目
HBuilderX 里已经建了好几年的传统工程,迁移成本高于收益
一个人 / 小团队,要快、要规范,不想每次重配基建
团队已有成熟的私有模板和构建约定,替换成本高
需要 H5 + 小程序 + App 多端并行交付
目标是 uni-app x(UTS)——那是另一套编译链路,不通用
希望代码在 VS Code 里写,命令行管多端构建
强依赖 HBuilderX 特有能力的项目
注意:表格里 uni-app x 那一行特别提醒:unibest 是 Vue 3 + Vite 方案,不是 uni-app x。uni-app x 用 UTS 编译原生代码,页面里不是 .vue,两套东西的工程结构、组件写法、构建流程都不一样,网上很多「uni-app 工程化」文章把两者混着讲,看的时候注意区分。

九、上线前过一遍的清单

  • engines
     和本机 Node / pnpm 版本对得上,别停在 18 / 8;
  • lint-staged
     那句彩蛋换成真正的检查命令,否则 hooks 形同虚设;
  • manifest.config.ts
     里 urlCheck: false只在开发期开,发版前确认关掉;
  • 微信小程序的合法域名、App 端的证书和备案,配齐了再提审;
  • H5 部署子目录的 h5.router.base 和服务器配置一致;
  • persist
     的字段是白名单还是全量,临时状态排干净了没;
  • 拦截器的注册顺序没被后人改乱(store → route → request);
  • pnpm type-check
     和 pnpm lint 在 CI 里跑一遍,别只靠 IDE 提示。

· · ·

unibest 解决的是一个很朴素的问题:让每个 uni-app 项目的起点是一样的。它不折腾花样,把 CLI 化开发、多端构建、约定式路由、TS 配置、hooks 检查串成了能跑通的闭环;一个人要同时顾 H5 / 小程序 / App,省下来的时间相当可观。

边界也很清楚——业务还是自己的,uni-app x 也不在范围内。

后续我打算再写一篇实战的:把 unibest 模板改造成自己项目的私脚手架过程(换 UI 库、删不需要的 feature、调整目录约定),如果你也在用,评论区可以交流你踩了哪些坑。

觉得有用的话,点赞、分享、推荐三连支持一下。

相关学习资料