Appearance
手动安装
说明
新项目必须使用脚手架 @ifanrx/scaffolder
创建,其中已经默认包含了下述配置,无需从头开始。
以下内容仅针对需要在现有项目中接入相关工具,或需要了解并修改项目配置等细节的情况。
小程序
安装
bash
pnpm install @ifanrx/uni-mp
配置 .npmrc
默认项目通过 pnpm 的 workspace 进行组织,项目根目录 .npmrc 必须包含以下配置:
yaml
save-prefix=~
enable-pre-post-scripts=true
注册插件
@ifanrx/uni-mp 依赖 pinia 和 vue-query,请务必安装好依赖。
js
import {createSSRApp} from 'vue'
import {VueQueryPlugin, vueQueryPluginOptions} from '@ifanrx/uni-mp'
import {createPinia} from 'pinia'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
const pinia = createPinia()
app.use(pinia)
app.use(VueQueryPlugin, vueQueryPluginOptions)
return {
app,
}
}
注册全局组件
使用 uni-app 的 easycom 将 @ifanrx/uni-mp 和 uni-ui 组件全局注册,在 manifest.json 中添加如下配置:
json
"easycom": {
"autoscan": true,
"custom": {
// 注意:缺一不可
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
"^mp-(.*)": "@ifanrx/uni-mp/components/mp-$1/mp-$1.vue",
}
},
使用方式
vue
<script setup>
// 所有模块均可从 @ifanrx/uni-mp 中具名导入
import {message, router, device} from '@ifanrx/uni-mp'
</script>
<template>
<!-- 全局注册过的组件无需引入,直接在页面中使用 uni-ui 和 uni-mp 的组件 -->
<mp-nav-bar title="首页" />
</template>
统一 Vite 开发构建流程
✅
此为基础,所有 uni-app 项目必须采用统一的开发构建流程
许多项目的开发构建流程以及 Vite 配置都大同小异,因此我们引入了 defineViteConfig
方法。
这个方法旨在提供一个标准化和简化的方式来处理 Vite 配置。
defineViteConfig
的优势
- 内置默认配置:
defineViteConfig
包含了一套预设的配置,这些配置适用于多数项目。 - 自定义与合并:它允许你根据项目的具体需求添加或修改配置,这些自定义配置将与默认配置合并。
- 易于维护:使用
defineViteConfig
可以减少重复代码,使项目配置更加清晰、易于维护。
标准开发构建流程
首先 defineViteConfig
依赖于统一的 npm script,需要在项目中写入:
json
{
"scripts": {
"dev": "uni -p mp-weixin -- --BAAS_DEV", // 开发模式,知晓云测试环境
"dev:prod": "uni -p mp-weixin", // 开发模式,知晓云 QA 服务器
"dev:qa": "uni -p mp-weixin -- --BAAS_QA", // 开发模式,知晓云生产环境
"build": "uni build -p mp-weixin" // 构建模式,知晓云生产环境,用于发版
}
}
DANGER
请不要使用 dev:prod
命令进行发版。若通过此命令构建的版本发布,会在小程序首页出现警告弹窗。
声明 Vite 配置
应使用 defineViteConfig
方法进行 Vite 配置,你只需要将项目特定的配置作为参数传入。该方法会自动将传入的配置与默认配置合并,生成最终的 Vite 配置。
js
import defineViteConfig from '@ifanrx/uni-mp/define-vite-config'
export default defineViteConfig(() => {
// 项目特定的配置
return {
plugins: [bar()],
resolve: {
alias: {
'@bar': 'bar',
},
},
}
})
默认配置内容
以下是 defineViteConfig
方法中已包含的默认配置:
Click me to view the code
js
/* eslint-disable import/no-extraneous-dependencies */
import fs from 'node:fs'
import path from 'node:path'
import deepmerge from 'deepmerge'
import minimist from 'minimist'
import copy from 'rollup-plugin-copy'
import uni from '@dcloudio/vite-plugin-uni'
import {input} from '@inquirer/prompts'
import {isBool, isNum, isStr} from 'licia'
import usePluginImport from '@ifanrx/vite-plugin-importer'
import {nodeResolve} from '@rollup/plugin-node-resolve'
import {viteCommonjs} from '@originjs/vite-plugin-commonjs'
import projectConfigJSONPlugin from './vite-plugin-project-config-json.js'
/**
* @typedef {object} ExtendConfigFnEnv
* @prop {boolean} DEV 是否 dev 开发模式
* @prop {boolean} BAAS_DEV 是否知晓云测试环境
* @prop {boolean} BAAS_QA 是否知晓云 QA 服务器
* @prop {string} BAAS_QA_SERVICE 知晓云 QA 服务器多环境
* @prop {object} defaultViteConfig 默认配置
* @prop {object} commandArgs
*/
/**
* @typedef {ExtendConfigFnEnv & import('vite').ConfigEnv} ConfigFnEnv
*/
/**
* @typedef {(env:ConfigFnEnv)=>ReturnType<import('vite').UserConfigFn>} ConfigFn
*/
/**
* 定义 Vite 配置,该配置已经集成了项目的通用配置项。
* 该函数允许传入一个配置函数,用于生成特定的配置对象。
* 传入的配置将与内置的通用配置合并,以形成最终的配置对象。
* @param {ConfigFn} configFn
* @return
*/
export default function defineViteConfig(configFn = () => ({})) {
/**
* @param {import('vite').ConfigEnv} options
*/
return async ({mode, ...rest}) => {
const argv = process.argv.slice(process.argv.findIndex(item => item === '--') + 1)
const args = minimist(argv)
const DEV = mode === 'development' // 是否 dev 开发模式
let {BAAS_DEV, BAAS_QA, s: qaService, ...restArgs} = args // 是否知晓云测试环境,是否知晓云 QA 服务器
qaService = isStr(qaService) || isNum(qaService) ? String(qaService) : ''
const isInvalidQAService = !qaService || isBool(qaService) || !qaService.trim()
if (BAAS_QA && isInvalidQAService) {
const answer = await Promise.race([
input({message: 'QA 服务器环境(选填)'}),
// eslint-disable-next-line no-promise-executor-return
new Promise(resolve => setTimeout(() => resolve(''), 5000)),
])
qaService = answer.trim()
}
const defaultAlias = {
'@': path.resolve(process.cwd(), 'src'),
}
if (fs.existsSync(path.resolve(process.cwd(), '../shared-constants'))) {
defaultAlias['shared-constants'] = path.resolve(process.cwd(), '../shared-constants')
}
const defaultViteConfig = {
plugins: [
uni.default(),
uniPolyfill(),
nodeResolve(),
usePluginImport({
licia: {
// eslint-disable-next-line no-template-curly-in-string
transform: 'licia/${member}',
preventFullImport: true,
},
}),
// 开发模式关闭微信开发者工具的 es6 转 es5,提高编译速度
DEV &&
projectConfigJSONPlugin(config => {
const {setting} = config
setting.es6 = false
return config
}),
// pnpm build 时,将构建产物从 dist/build 复制到 dist/dev 目录
// 不切换微信开发者工具的项目也能进行发版
!DEV && {
...copy({
targets: [{src: 'dist/build/mp-weixin/', dest: 'dist/dev/'}],
hook: 'closeBundle',
}),
enforce: 'post',
},
process.env.UNI_PLATFORM === 'h5' ? viteCommonjs() : null,
].filter(Boolean),
// 必须是 es2017~2019 之间,超出范围会产生编译问题
build: {
target: 'es2017',
},
define: {
VITE_DEV: DEV,
VITE_BAAS_DEV: BAAS_DEV,
VITE_BAAS_QA: BAAS_QA,
VITE_BAAS_QA_SERVICE: JSON.stringify(qaService),
},
resolve: {
alias: defaultAlias,
},
}
const customViteConfig = configFn({
DEV,
BAAS_DEV,
BAAS_QA,
BAAS_QA_SERVICE: JSON.stringify(qaService),
mode,
defaultViteConfig,
commandArgs: restArgs,
...rest,
})
return deepmerge(defaultViteConfig, customViteConfig, {
arrayMerge: (target, source) => target.concat(source),
})
}
}
/**
* @see https://github.com/dcloudio/uni-app/issues/4604
* @see https://github.com/vuejs/pinia/issues/2210#issuecomment-1868843572
*/
export function uniPolyfill() {
return {
name: 'vite-plugin-uni-polyfill',
transform(code, id) {
if (id.endsWith('@dcloudio/uni-mp-vue/dist/vue.runtime.esm.js')) {
// eslint-disable-next-line no-param-reassign
code += `
// polyfill for @vueuse/core
export const render = () => {}
export const TransitionGroup = {}
`
if (!code.includes('hasInjectionContext')) {
// eslint-disable-next-line no-param-reassign
code += `
// polyfill for @pinia, @tanstack/vue-query
export function hasInjectionContext() {return !!getCurrentInstance()}
`
}
}
if (
id.endsWith('@dcloudio/uni-h5-vue/dist/vue.runtime.esm.js') &&
!code.includes('hasInjectionContext')
) {
// eslint-disable-next-line no-param-reassign
code += `
// polyfill for @pinia, @tanstack/vue-query
export function hasInjectionContext() {return !!getCurrentInstance()}
`
}
return code
},
}
}
区分环境
我们的 Vite 配置中定义了三个关键的全局变量,你可以使用它们来区分不同的运行环境。
为提供代码提示,我们建议将这三个变量集成到项目的常量定义中,方便在代码中显式引用:
js
export const DEV = VITE_DEV // 是否 dev 开发模式
export const BAAS_DEV = VITE_BAAS_DEV // 是否知晓云测试环境
export const BAAS_QA = VITE_BAAS_QA // 是否知晓云 QA 服务器
自定义参数
如果需要自定义启动命令参数,比如:可以通过 --SCREENSHOTS
参数表示当前环境用于截图,就可以通过 commandArgs
获取自定义参数:
json
"scripts": {
"dev:screenshots": "uni -p mp-weixin -- --BAAS_QA --SCREENSHOTS",
}
js
import defineViteConfig from '@ifanrx/uni-mp/define-vite-config'
export default defineViteConfig(({commandArgs}) => {
const {SCREENSHOTS} = commandArgs
return {
define: {
VITE_SCREENSHOTS: SCREENSHOTS,
},
}
})
提升微信开发者工具编译速度
WARNING
已集成到 defineViteConfig
,无需重复引入
微信开发者工具默认开启「将 JS 编译至 ES5」,这会导致修改代码后重新编译时间较长。然而,关闭此选项可能会导致线上部分机型运行异常。因此,解决方案是仅在本地开发时关闭该选项。
Click me to view the code
如下所示,可以使用 Vite 插件,在编译代码后关闭该选项。
js
import projectConfigJSONPlugin from '@ifanrx/uni-mp/vite-plugin-project-config-json'
export default defineConfig(({mode}) => {
const DEV = mode === 'development'
return {
// ...
plugins: [
DEV &&
projectConfigJSONPlugin(config => {
const {setting} = config
setting.es6 = false
return config
}),
].filter(Boolean),
// ...
}
})
优化 console 响应式变量输出
✅
推荐启用此功能
此功能必须启用微信开发者工具的 「Enable custom formatters」。
启用方式:打开微信开发者工具调试器右上角小齿轮 「Settings」,在 「Preferences」中找到 「Console」分组,勾选 「Enable custom formatters」。
uni-mp 内部会自动挂载 initDevToolsCustomFormatter,可对 ref/reactive 响应式变量的 console 输出进行格式优化。
