使用 Vite 开发项目,build 后发现所有的逻辑文件都被打包在一个入口 js 文件中,如图:
这样的打包结果显示不是合理的,当项目大了之后,js 入口文件会变得非常大,影响加载速度。还有就是对于项目中长期不变的 js 引入文件,我们不应该使其每次打包都重新生成 hash 名,这样会导致每次重新 build 后,客户端因为文件 hash 值的变化,都要重新下载这些固定文件内容,在网速不好的情况会影响使用体验。所以针对这点,需要做 build 优化,拆分模块文件。
但是 Vite 文档中没有找到这种功能的配置,其实 Vite 没有实现这个功能,它的打包是依赖 rollup 的,所以我们可以查找 rollup 的相关功能。Vite 打包是支持配置 rollup 功能以改变打包结果的,在vite.config.js 中配置 build.rollupOptions即可。
在查看 rollup 文档后得知,拆分包功能需要配置 output.manualChunks。manualChunks 是一个对象,对象的每一个属性名都是要生成的 chunk 名前缀,而属性值则是需要打入的模块包名。例如我们需要将 vue 文件单独拆分出来,则可以配置:
{
// 忽略其他配置
build: {
rollupOptions: {
output: {
manualChunks: {
vue: ['vue']
}
}
}
}
}
此时再执行打包,结果如下:
可以看到 vue 文件是被单独拆分出来了,且后续再修改项目代码时,这个文件指纹是不会再变的,因为开发中不可能修改 vue 源码。同样的道理,对于引用的第三方库,我们都应该拆分出来,可以放在一个 chunk 中,也可以单独拆分,如下:
{
// 忽略其他配置
build: {
rollupOptions: {
output: {
manualChunks: {
// 放在一个 chunk 中
chunk: ['vue','vue-router','vuex'],
// 也可以单独细分
vue:['vue'],
vuex:['vuex'],
'vue-router':['vue-router'],
}
}
}
}
}
至此,我们解决了基本需求。还有一个问题就是如果项目中需要拆分的模块很多,我们一个个去列举模块也不太现实,这时候就可以换个配置模式。manualChunks 是支持配置成一个函数的,函数的入参是每一个被打包的模块id,我们可以判断这个 id 是否是来自node_modules 路径的,如果是则代表是第三方库,就可以统一打包进一个chunk中,配置如下:
{
// 忽略其他配置
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes("node_modules")) {
return "vendor";
}
},
},
},
}
}
更多关于 manualChunks 的配置,可参考文档:output-manualchunks
在写自己的一个 Vite+Vue 项目时,考虑加入自动路由和自动导入功能。分别使用插件:unplugin-vue-router 和 unplugin-auto-import。
unplugin-vue-router 实现自动路由
自动路由要达到的效果是:无需手动添加路由路径,而是根据文件路径自动生成路由配置。
这里以 Vite 项目为例,安装好插件后,在 vite.config.ts 中配置如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 引入 vite 适配插件
import VueRouter from 'unplugin-vue-router/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [VueRouter(), vue()] // VueRouter 插件必须放在 Vue 插件之前
})
如果是 Typescript 项目,运行项目后,会在项目中自动生成一个 typed-router.d.ts文件,这个文件在后续开发中会自动生更新路由映射类型,为防止 ts 报错,所以需要将这个文件引入到 tsconfig.json 文件中,同时需要将 moduleResolution 项设置为 bundler :
{
"include": [
// other files...
+ "./typed-router.d.ts"
],
"compilerOptions": {
// ...
+ "moduleResolution": "bundler",
// ...
}
}
另外,如果项目中有 env.d.ts文件,例如 Vite + Typescript 创建的项目就会有 vite-env.d.ts文件,则需要添加类型到其中:
/// <reference types="vite/client" />
+ /// <reference types="unplugin-vue-router/client" />
配置完成后,需要修改 router.ts 文件,不再直接使用 vue-router 模块:
- import { createRouter, createWebHistory } from 'vue-router'
+ import {createRouter,createWebHistory} from 'vue-router/auto'
+ import {routes} from 'vue-router/auto-routes'
const router = createRouter({
history:createWebHistory(),
- routes: [
- {
- path: '/',
- component: () => import('src/pages/Home.vue'),
- }
- ]
+ routes,
})
export default router
上面代码中的 routes 变成自动生成的,它是基于文件系统的,默认路由文件映射目录是 src/pages,测试创建 pages/index.vue文件,可以看到 typed-router.d.ts文件中,已自动生成路由的映射:
排除目录
有些时候,pages目录中可能会有一些其他的非页面内容的目录,比如 components目录,我们不希望它生成路由映射。unplugin-vue-router 支持排除目录设置,在 exclude数组中,列出你不想要生成路由的文件即可:
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import VueRouter from "unplugin-vue-router/vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
VueRouter({
+ routesFolder: "src/pages", // 这里可指定页面目录
+ exclude: ["src/pages/components/**/*.vue"], // 这里排除了pages目录中components目录文件的路由映射
}),
vue(),
],
});
至此,自动路由配置完成,更多配置功能,详见文档:configuration。
unplugin-auto-import 实现自动导入
自动导入要达到的效果是:使用过一些公共库或者是频繁导入使用的模块时,无需手动添加导入就可直接使用。
还是 Vite 项目为例,安装插件后,在 vite.config.ts 中配置:
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
/* options */
})
]
})
想要自动导出模块,需要在插件中具体配置。不是所有的插件或库都支持自动导入,首先官方默认提供了一些预设 presets,在这个预设表中的插件,都可以直接配置成字符串即可。比如我希望自动导入 vue 和 vue-router中的方法,即可配置成:
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router']
})
]
})
此时如果是 Typescript 项目,运行项目后,会自动生成 auto-imports.d.ts文件,文件中会根据配置自动生成类型声明,防止 ts 报错,同样我们需要将此文件包含到 tsconfig.json 文件中:
{
"include": [
// other files...
+ "./auto-imports.d.ts"
],
}
观察 auto-imports.d.ts文件,会发现已经有vue和 vue-router的内置模块的类型声明了:
现在使用 vue 模块开发时,就不需要再手动导入了:
<template>
<div>{{ str }}</div>
</template>
<script setup lang="ts">
- import { ref } from "vue";
const str = ref("hello world!");
</script>
结合 unplugin-vue-router 使用
如果使用的插件库不在官方预设内,有些插件会自己实现,例如 unplugin-vue-router。
在使用 unplugin-vue-router 时,我们不直接使用 vue-router,希望是自动导入插件中的方法,而此插件刚好也暴露了自己实现的自动导入预设,所以可修改配置如下:
port { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import VueRouter from "unplugin-vue-router/vite";
import AutoImport from "unplugin-auto-import/vite";
+ import { VueRouterAutoImports } from 'unplugin-vue-router'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
VueRouter({
routesFolder: "src/pages",
exclude: ["src/pages/components/**/*.vue"],
}),
vue(),
AutoImport({
imports: [
"vue",
- "vue-router",
+ VueRouterAutoImports
],
}),
],
});
再查看auto-imports.d.ts文件,发现也已经有了对应方法的类型声明:
自动导入自己写的工具模块
有时候我们也希望可以将自己写的工具方法实现自动导入,好在插件中可以配置。
举个例子,假设我们有一个 utils目录,其中有文件date.ts是封装了一些日期方法,有文件storage.ts文件封装了缓存方法,再来一个 index.ts作为入口文件:
export function getTime() {
return new Date().getTime()
}
export function getStorage(key: string) {
return localStorage.getItem(key) || sessionStorage.getItem(key)
}
export { getTime } from './date'
export { getStorage } from './storage'
这个时候,我们希望 utils目录中所有的方法都能自动导入,就可以配置 dirs项:
AutoImport({
imports: ['vue', 'vue-router'],
dirs: [
'src/utils' // 配置工具类的目录即可
]
})
配置完成后,观察 auto-imports.d.ts文件,可以发现所有的方法都已声明了类型,可以直接使用了。
去年我用 uniapp 帮公司开发了一个跨平台 App,最近又改版优化了一版。App 虽然简单,但是也踩了不少坑,所以准备用此文记录一下。
初始化项目
uniapp 支持两种初始化项目方式,一个是用他们的编辑器 HBuilderX(后简称 HBX) 可视化创建,一个就是利用 vue-cli 命令行方式创建。但是开发 APP 始终需要用 HBX 来进行云端打包,所以我就直接用 HBX 来初始化项目的。
但是我觉得这个编辑器不好用,所有开发时用 HBX 运行项目,然后在 VSCode 中写代码,并配置了 prettier,保存自动格式化代码。
现在 uniapp 搞了个 HBuilderX cli 命令来运行或者打包 app,我浅看了下,本质还是需要安装 HBuilderX 的,只是可以通过命令来操作 HBX ,没啥意义。文档在这里:HBuilderX cli
我个人更希望 uniapp 官方拥抱 VSCode 这些成熟的编辑器,只专注做 sdk。
开发运行
关于基座
开发 app 时,可以使用模拟器或者真机来调试,此时安装的是基座包。uniapp 提供标准基座,也可以自定义基座。
标准基座使用的是 DCloud 的包名、证书和三方 SDK 配置,是可以直接安装调试的,但是 ios 的真机调试用不了。
只推荐自定义基座,或者只能使用自定义基座,因为可以使所有的 app 配置生效,比如 App 名称和图标,权限设置,三方 sdk 配置等等,都需要生成自定义基座来生效。参考文档:自定义基座
关于证书
开发 App 就一定涉及证书,打自定义基座时也需要证书。只有安卓提供了测试证书,但是最后发布还是需要自定义证书的。安卓和苹果的证书教程:
ios 证书,文档:https://ask.dcloud.net.cn/article/152
android 证书,文档:https://ask.dcloud.net.cn/article/35777
关于组件
开发跨平台 App,需要考虑兼容性。官方自带的组件一般是可以兼容 android 和 ios 的。但是如果使用插件时,需要注意下有的插件是针对某一平台开发的,就不会兼容另一个平台。
另外 UI 组件的选择,我这里推荐 uview-ui,比官方出的 uni-ui 好用些,但是只兼容 Vue2。所以开发 App 我建议使用 Vue2 语法版本,因为很多插件都没更新到 Vue3 语法,即使出了也有不少问题。
指定页面调试
有时候开发的页面层级比较深,调试时需要能够直达页面比较快捷。此时可以设置 condition 这个配置项,在 pages.json 中配置页面路径列表,开发时即可选择打开指定页面。如图:
其他
别的关于 app 的配置,参考文档就好,没啥大坑的。
还要提及下,如果新勾选了一些要用到的模块,需要重新打自定义基座并重新安装,这样真机调试才可以看到效果,当然这些都会有提示。
另一个就是配置文件 manifest.json支持可视化修改,也可以支持源码视图修改。如果你用别的编辑器,直接源码视图修改的话,要注意自动格式化的问题。因为这份配置文件里有很多注释,本来 json 文件是不支持这些注释的,在别的编辑器上可能会报错,保存时可能会格式化掉一些内容。
组件自动引用
HBuilderX 提供 easycom 组件模式,即使用组件前不需要手动引用,可以直接使用,只要符合规范的组件都会自动引用。那么这里就有一个坑,一定要严格遵循它的路径规则,即:
安装在项目根目录的 components 目录下,并符合components/组件名称/组件名称.vue
安装在 uni_modules 下,路径为uni_modules/插件ID/components/组件名称/组件名称.vue
我最开始自定义组件目录下用的是 index.vue,导致引用失败。当然这是他们的默认规则,你也可以去 pages.json 中自定义规则。
路由拦截
由于用的不是 vue-router,所以路由拦截就不好实现了。但好在 uniapp 提供了一个 api 拦截器,即 addInterceptor。它的作用是可以拦截 uniapp 中的一些 api,比如 request,navigateTo等。
如果需要拦截路由,拦截 navigateTo 或者 redirectTo 即可,可以在执行 api 之前进行一些判断。示例代码:
function addInterceptor() {
// 需要拦截的 api 列表
let list = ['navigateTo', 'redirectTo']
// 遍历执行拦截操作
list.forEach((item) => {
uni.addInterceptor(item, {
invoke(res) {
// return true 表示放行,return false 表示不继续执行
if (res.url === '/pages/index/index') {
return true
} else {
// return false 之前可以进行一些操作,比如重定向
return false
}
},
fail(err) {
// 失败回调拦截
console.log(err)
}
})
})
}
图层层级
这是最坑的一点。开发 App 时,有些组件是原生渲染的,这就意味着无法使用 z-index 控制它的层级,就会出现遮挡内容的情况。
如果你使用 nvue 开发的页面,就没有关系,因为此时页面全是原生渲染的,可以覆盖层级。但是如果你页面已经用 vue 开发完,但是后来加了原生组件的需求,那么就有点难受了。
如果只想在原生组件上覆盖一些内容,那还好做,使用 cover-view 组件可以覆盖内容到原生组件上,具体看文档。
还有一类场景就是原生组件遮挡了页面中的内容,比如页面滑动时遮挡了底部操作栏,popup 弹窗被原生组件遮挡等。这种场景优点难办,但也有办法。一个就是使用 cover-view组件重写被遮挡内容,页面布局有点受限,但基本能实现。另一个办法就是使用原生子窗体 subNVue,关于它的介绍,引用官方定义:
subNvue,是 vue 页面的原生子窗体,把 weex 渲染的原生界面当做 vue 页面的子窗体覆盖在页面上。它不是全屏页面,它给 App 平台 vue 页面中的层级覆盖和原生界面自定义提供了更强大和灵活的解决方案。它也不是组件,就是一个原生子窗体。
其实就是一个局部的 nvue 内容,可以覆盖住原生组件。关于它的使用就是需要在项目中创建一个 nvue 组件,然后去 pages.json 中对应的页面配置中配置style -> app-plus -> subNVues即可,可以参考这篇文档:subNVue 原生子窗体开发指南。
待续
后面想起来再加~