用好条件编译让前端项目打包更高效
我的写法,亲测靠谱
做移动端开发这几年,条件编译算是我天天打交道的东西了。尤其在 uni-app、Taro 这种跨端框架里,不同平台行为差异太大,不用条件编译根本活不下去。但我刚开始也是一头雾水,各种 #ifdef 堆得像面条一样,后来项目一升级,直接崩了。
现在我有一套自己的习惯写法,核心就一句话:**逻辑集中,标记清晰,避免嵌套**。
比如我现在处理一个按钮点击,在 H5 和小程序表现不一样,H5 要跳转外链,小程序要打开原生页面。我是这么写的:
// utils/platform.js
const isH5 = process.env.VUE_APP_PLATFORM === 'h5'
const isMP = process.env.VUE_APP_PLATFORM === 'mp-weixin'
export { isH5, isMP }
// components/MyButton.vue
import { isH5, isMP } from '@/utils/platform'
export default {
methods: {
handleTap() {
if (isH5) {
window.location.href = 'https://jztheme.com/page'
return
}
if (isMP) {
wx.navigateToMiniProgram({
appId: 'wx123456',
path: '/pages/index'
})
return
}
// 默认 fallback
console.log('当前平台不支持该操作')
}
}
}
看起来很简单对吧?但关键是这个 platform.js 我只写一次,全项目引用。这样改起来方便,查问题也快。你要是到处写 #ifdef H5,后期重构的时候能把自己绕晕。
这几种错误写法,别再踩坑了
我见过太多人把条件编译写成“代码迷宫”,这里列几个典型反面案例。
- 嵌套地狱:一个功能里三层 if,每层都带
#ifdef,最后连自己都不知道哪段代码在哪个平台生效。 - 魔幻字符串满天飞:
process.env.UNI_PLATFORM === 'h5'写了十几遍,拼错一次全完蛋。 - 混用预处理器语法和运行时判断:一边用
#ifdef,一边又在v-if里判断环境变量,结果打包后逻辑错乱。
最离谱的一次,我在一个项目里看到这种写法:
// ❌ 千万别学!
#ifdef H5
axios.get('/api/data')
#else
#ifdef MP-WEIXIN
wx.request({ url: '/api/data' })
#else
fetch('/api/data')
#endif
#endif
这种写法看着“省事”,实际上完全不可维护。你换个平台就得重写一遍,而且 IDE 根本没法语法高亮,写到后面全是红色波浪线警告。
还有人喜欢在 CSS 里狂用条件编译:
/* ❌ 别这么干 */
#ifdef H5
.button {
touch-action: manipulation;
}
#endif
#ifdef MP-WEIXIN
.button {
-webkit-touch-callout: none;
}
#endif
问题是 CSS 不支持运行时动态切换,这种写法会导致所有平台都带上冗余样式。更靠谱的做法是用 class 控制:
.h5 .button {
touch-action: manipulation;
}
.mp-weixin .button {
-webkit-touch-callout: none;
}
实际项目中的坑
去年我接手一个老项目,里面用了大量的 uni.getSystemInfoSync() 加条件判断来区分平台。结果上线后 iOS 用户反馈按钮没反应。折腾了半天才发现,某些低端安卓机返回的 platform 是 android 小写,而代码里写的是大写 Android,直接进了 else 分支。
从那以后我坚决不用运行时信息做平台判断,全用构建时变量。毕竟 process.env 是编译阶段确定的,稳定得多。
还有一个容易忽略的点:**测试覆盖**。你写了 H5 的逻辑,本地跑没问题,但打包成小程序可能就挂了。建议在 CI 里加多平台构建步骤,哪怕不运行,至少确保能编译通过。
另外提一句,很多人喜欢在 main.js 里根据平台注册不同的全局组件或插件。这本身没问题,但要注意顺序和依赖关系。比如你在 H5 引了某个 DOM 操作库,在小程序就会报错。正确姿势是:
// main.js
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
// 条件引入,而不是条件注册
#ifdef H5
import '@/plugins/h5-plugin'
#endif
#ifdef MP-WEIXIN
import '@/plugins/mp-plugin'
#endif
app.$mount()
别让条件编译污染业务逻辑
这是我最近才悟出来的道理。以前我总想着“哪里需要就哪里加 #ifdef”,结果业务代码里到处都是平台判断。后来我把这些差异抽象成接口:
// api/openExternal.js
export function openExternal(url) {
// 统一入口,内部实现分平台
if (isH5) {
window.open(url, '_blank')
return
}
if (isMP) {
// 小程序特殊处理
wx.navigateTo({ url: /webview?url=${encodeURIComponent(url)} })
return
}
console.warn('当前平台不支持打开外部链接', url)
}
业务层只需要调 openExternal('https://jztheme.com'),完全不用关心平台差异。这才是解耦的正确方式。
你可能会说:“啊这不是多写了一层吗?” 是多了几行代码,但换来的是可维护性和可测试性。等你项目上 20 个地方都要打开外链时,你就知道值不值了。
关于编译宏的命名规范
uni-app 官方给了 UNI_PLATFORM,Taro 有 TARO_ENV,但我不建议直接用。原因很简单:万一哪天框架改名了呢?我一般会封装一层:
// env.js
const ENV_MAP = {
'h5': 'h5',
'mp-weixin': 'weapp',
'mp-alipay': 'alipay',
'rn': 'rn'
}
export const PLATFORM = ENV_MAP[process.env.UNI_PLATFORM || process.env.TARO_ENV] || 'unknown'
export const IS_H5 = PLATFORM === 'h5'
export const IS_WEAPP = PLATFORM === 'weapp'
这样将来换框架或者环境变量变名字,我只要改这一处就行。别小看这点封装,关键时刻能救你命。
以上是我总结的最佳实践
条件编译不是银弹,用不好反而会让项目变得更脆弱。我的经验就是:提前规划、统一管理、少在业务层裸奔 #ifdef。虽然改完之后还是有那么一两个边界情况处理得不够优雅,但至少不会半夜被报警叫醒。
以上是我踩坑后的总结,希望对你有帮助。有更好的方案欢迎评论区交流,我也一直在找更干净的解法。

暂无评论