跪拜 Guibai
← Back to the summary

UniApp Mini Programs Are a Compatibility Minefield, and Here's the Map


theme: minimalism

Too many TOCs in the article, making it inconvenient to read? On PC, you can check out my Two TamperMonkey Scripts to Solve Three Pain Points of the Juejin Reading Experience, which can solve some Juejin reading experience issues.

My "Getting Started" series of articles:

  1. Getting Started with Mac, This One Article Is Enough
  2. Getting Started with iTerm + OMZ, This One Article Is Enough
  3. Getting Started with Firefox Developer Edition and Mobile Edition, This One Article Is Enough
  4. Getting Started with WebStorm, This One Article Is Enough
  5. Getting Started with VSCode, This One Article Is Enough
  6. Getting Started with Vim, This One Article Is Enough
  7. Getting Started with Git, This One Article Is Enough
  8. Getting Started with Docusaurus, This One Article Is Enough
  9. Getting Started with Nginx, This One Article Is Enough
  10. Getting Started with UniApp to Write Mini Programs, Some Feelings ← This Article

🎼 Preface

It's been a long time since I published an article. There are many things I want to write, but I always find various excuses to push them to tomorrow's tomorrow's tomorrow. Every day, I receive some likes and favorites. Thank you very much for your recognition of my previous articles.

Since my last article, a long time has passed, during which some things happened that made me quite depressed. In short, at the beginning of January 2024, I returned to Hangzhou from Shanghai.

After returning to Hangzhou, I joined a small startup company working on AI. My phrasing was "to wade into this muddy water of history," while the boss's phrasing was "to personally enter the fray," which instantly made me feel my cultural level was a bit lacking.

The first thing I did after joining the company was to write a mini program. I had already planned to write this article when I accepted this task, and it should have been written long ago, but ultimately it was delayed again and again, becoming stale and smelly.

TL;DR

You might find this article somewhat different from my others; yes, it has relatively fewer images and is a bit dry.

This is not a "how to write mini programs with uni-app" or "how to develop mini programs" themed article, so it won't introduce various getting-started knowledge in great detail (though it will touch on some). It's more about how I think about technology selection, best practices in mini program development, and complaints about various problems encountered.

Main Content

Suitable Readers

What You Will Learn

  1. The getting-started posture for UniApp mini program development (choose one of two)
  2. Some pitfalls of UniApp and WeChat Mini Programs
  3. Some major pitfalls of UniApp and secrets to avoid them
  4. Some things considered best practices

Terminology

Explanation of terms used in the main text:

Term Explanation
MP Mini Program (real device or simulator)
H5 Running in H5 mode

Edit History

Date Version Description
2025/06/30 V1

🕹️ Technology Selection

Before writing this mini program, my background was this:

So why did I take it on? Two reasons: there was no one else, and such a challenge excited me far more than it worried me.

Choosing the Development Framework uni-app

You might know that besides UniApp, other well-known mini program frameworks include Taro and so on (I... don't know). So why choose UniApp?

Actually, I was just "bamboozled" by a colleague. He told me this:

As mentioned above, mini programs were a completely new field for me. I didn't rush into business development but spent two weeks familiarizing myself with the relevant tech stack and ultimately deciding on the project architecture. To put it plainly, it was just reading the official docs' "Quick Start" and tinkering locally.

Not Choosing HX as the Development Tool

The official docs' "Quick Start" has two articles, HX or CLI, which is a question.

Students who have read my articles should be able to understand a few of my characteristics:

  1. Self-proclaimed "Software Researcher," likes playing with software, especially development tools, particularly enjoys researching configurations
  2. A die-hard WebStorm fan, thinking I'm quite proficient with it
  3. Know a bit about VSCode, thinking many heavy users don't know it as well as I do
  4. A keyboard shortcut expert
  5. Fond of the command line
  6. Code cleanliness obsession

All of the above would influence my final choice.

To make the right choice, and also as part of the familiarization process, I tried both HX and CLI methods.

First, as a "Software Researcher," I really enjoy playing with various software. But HX's rough, retro icon first gave a sense of "dimness," and the moment the interface opened, I lost even more enthusiasm. The color scheme is simply indescribable; the official bragging says it's "eye-protecting."

From the interface, it feels like it might be based on Monaco, but deliberately tries not to look too much like VSCode.

To understand the tech stack, I followed the documentation through several operations and also wrote some demo code, but it didn't feel smooth to use, especially the keyboard shortcuts, even though they can be customized.

Even worse, it arbitrarily modified system file associations.

The most unacceptable thing is its uni_modules mode. Once introduced, it means two consequences:

  1. You will have a bunch of code you didn't write that must be committed, meaning your code commit volume doesn't reflect your actual work, and you need to specifically extend the ignore list for tools like Lint
  2. You will be held hostage by HX, because only HX can add or update uni_modules

The absolute most unacceptable thing is that HX hides many details. What dependencies are used, what versions they are, you're completely in the dark. Want to import an npm package? Can't. Want to configure Husky? Can't. Want to write an npm script? Can't. All because there's no package.json.

After comparing HX and CLI projects, I found that an HX project is essentially just the src directory of a CLI project. This means a CLI project can actually be developed with HX, but an HX project can only be developed with HX. Inclusiveness is important.

Summarizing the drawbacks of HX:

  1. Asking an experienced developer who already prefers a certain IDE or editor to switch to a new development tool just for one development framework, unless this development tool has truly dazzling highlights, but HX has almost no highlights at the IDE or editor level
  2. HX projects lack inclusiveness, basically forcing people to use HX, making future upgrades or migrations problematic

Another thing that disgusted me was the official promotional wording for HX. In their Quick Start - via vue-cli command line, I quote a few passages (because they might change them).

To enhance ease of use and lower the barrier

Many developers are unfamiliar with node and have a psychological resistance to the command line. Don't assume that all developers know node. HBuilder has millions of developers, and less than half of them master node.

"Many"? "Psychological resistance"?

I've always believed that besides facilitating development, the most important role of an IDE is that it is one of the developer's best "teachers." And the command line is one of the essential skills for developers. As a teacher, it should "guide patiently."

Moreover, using the command line, no one said you need to "master" Node. I'm not talented, but my command line skills are okay, though my Node knowledge is just passable.

If you are used to other IDEs and don't mind developing uni-app inefficiently, then you can also use other IDEs.

A very low EQ sentence... It's like saying "I'm the best in the world, your loss if you don't use it," very passive-aggressive.

Why Choose the CLI Method

If not choosing HX, the only option is the CLI method. Benefits:

  1. You get a complete, self-runnable project
  2. Can use the command line to boost efficiency
  3. Can use npm
  4. Can use Husky with various Lint tools
  5. Can use your preferred IDE or editor, and can also use HX

"Drawbacks" to Bear with CLI

Need to Configure easycom

First is easycom.

As long as the component is installed in the project's components directory or uni_modules directory, and conforms to the components/component-name/component-name.(vue|uvue) directory structure...

Honestly, I really dislike this kind of implicit dependency, but it does provide some convenience. What you need to do in CLI development mode is add the following configuration in src/pages.json:

"easycom": {
  "autoscan": true,
  "custom": {
    "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
  }
}

But this only informs the compiler, not your development tool. You will see the IDE complaining:

To let the IDE know this thing exists and be able to navigate to it, you also need to install plugins:

The effect after using the plugin in WebStorm (here I deliberately wrote a wrong attribute, and the IDE can detect it and give a warning):

Cannot Use uni_modules

As mentioned in the previous chapter's excerpt, uni_modules is also a form of easycom, so it's not that you absolutely cannot use it.

Of course, you can also open the project with HX and use HX's capabilities to install and upgrade.

However, I strongly advise against doing this.

Based on my experience with this project, I did indeed draw experience from uni_modules projects, but I didn't use a single one. This kind of third-party dependency that requires hard-copying code is really hard to swallow.

TypeScript

No TS, no coding. Must use TS, so I used the TS template they provided.

Vue3

I haven't really been exposed to Vue much, so I just wanted to use the latest version. UniApp supports Vue2 and Vue3, but as a Vue newbie plus upgrade fanatic, I must fully use Vue3 syntax. Templates and uni_modules code, currently (as of 2025/06), mostly seem to still be Vue2.

TSX

I wanted to use it. But the mini program side doesn't support it yet, so I gave up.

🔋 Development Struggles

Results of the Struggles

Since Storybook couldn't be set up in a UniApp project, I specifically created a test page pages/_xx to tinker with UniApp and common components at any time.

Test Early

I have to say, UniApp (including the mini program itself) is really terrible in terms of compatibility.

My development habit is to debug generally from browser to mini program development tool, then to a real device, and finally upload to the experimental version.

If you're like me, you like to test in the browser, then test in the mini program development tool, and then test on a real mobile device.

MP Wrapping

In UniApp, you have a global uni object with many methods hanging off it. However, I don't recommend using uni.xx directly in your business code; instead, create a wrapper layer. Here are a few considerations:

Problem: Not Concise Enough

For example, uni.login, in most cases we only need the returned code, but the original return looks like this:

{  
  errMsg: 'login:ok', // ← WeChat
  success: true, // ← Alipay
  code: '0e3ejr000AXOwR1g43400j7gwL2ejr05'
}

Problem: Imprecise or Incorrect Type Definitions

Still using the uni.login example, the official data return type is as follows:

interface LoginRes {
  errMsg: string;
  code: string; // ← The only useful part we care about
  // 💥 None of the following are present
  authResult: string;
  anonymousCode?: string;
  authCode?: string;
  authErrorScope?: any;
  authSucessScope?: string [];
  appleInfo?: AppleLoginAppleInfo;
}

Problem: Method Polymorphism

For example, for storage operations, uni provides both synchronous (getStorageSync) and asynchronous (getStorage) methods. This leads to inconsistency in business code; by wrapping, we can restrict it to one type.

Additionally, most Promise interfaces provide success, fail callbacks, and passing callbacks means they are no longer Promises, which is very annoying for a Promise fanatic like me.

Problem: Platform Differences

uni's interfaces do not handle compatibility differences between platforms.

Example of call failure inconsistency: uni.getStorage: when key does not exist, WeChat will reject({ errMsg: 'getStorage:fail data not found' }), while Alipay will resolve({ data: null }).

But the synchronous interface for storage is still aligned, which is one reason why our wrapper uses the synchronous interface.

Problem: Need to Be Business-Oriented

For example, storage has reserved prefixes for keys; we can uniformly set a prefix to avoid unnecessary problems.

How to Wrap

We need:

  1. Wrap errors with MpError to ensure all errors are Error objects, not the plain Object given by uni
  2. Utility method wrapUni, forcing all APIs that can be Promise-ified to return Promise, standardizing the error object MpError, and catching non-existent methods and throwing wrapped MpError errors
  3. All method names start with mp, appended with the actual API name (sometimes slightly modified)
  4. Wrap on demand; you can wrap whichever you use while writing business logic
  5. The principle of wrapping is: if a method has both static and Promise forms, discard one; clarify the true purpose, simplify the output

For example, here is the wrapper for uni.login:

/**  
 * 💥H5 → Promise reject 「login:fail method 'uni.login' not supported」  
 */  
export default function mpLoginCode(): Promise<string> {  
  return wrapUni<string>(() => uni.login().then(result => result.code));  
}

By renaming it to mpLoginCode, we clarified its true purpose, and changing the return to Promise<string> simplified its result.

Below is an overview of the interfaces I wrapped in src/pkg/mp while writing business logic:

Beware of Circular Dependencies

Since I reported logs for related errors during the mp wrapping process, this very easily leads to circular dependencies. I learned my lesson later when writing a Taro project, no longer having modules like sls, storage, fetcher, etc., depend back on mp. Instead, I wrote the wrapper directly inside the relevant modules.

Internationalization

First, please step out of your cognitive misunderstanding. The purpose of internationalization is not necessarily to serve overseas users. Adding internationalization capabilities to a project has the following benefits:

  1. Ensures consistency of copy
  2. Ensures consistency of data display (formatted display of numbers, dates, etc., also falls under internationalization)
  3. Prepares in advance for overseas business

So, this was also one of the foundational infrastructures I considered from the very beginning of writing the mini program.

There are two documents about internationalization:

True to UniApp's nature, internationalization also has many pitfalls:

  1. $t cannot use interpolation in mini programs
    • Passing values type reports an error..., but if not passed
    • Under H5, it will remove the interpolation from the copy
    • Under MP, whether passed or not, it will retain the original value in the copy
  2. Because there is no Intl on real devices, templates cannot use $n, nor can export const { global: { d } } = middlewareI18n
  3. Because there is no Intl on real devices, templates cannot use $d, nor can export const { global: { d } } = middlewareI18n

Below is my wrapped pkg/middleware-i18n:

Install this middleware in main.ts:

import {
  createSSRApp
} from 'vue';

+import middlewareI18n from '@/pkg/middleware-i18n';

import App from './App.vue';  

export function createApp() {
  const app = createSSRApp(App);
  
+  app.use(middlewareI18n);
  
  return {  
    app
  };
}

Pinia

Do we need a global state management tool?

Based on my experience writing React, I can write an entire application without relying on any global state management. But with Vue, I wasn't confident enough, not to mention the added major pitfall of mini programs.

In the end, I found that writing pages might not actually need global state management, but information shared between pages definitely exists, so global state is still necessary.

The next question is, which one to choose as the state management tool.

I ultimately chose Pinia over Vuex, for no particular reason. Mainly, it seems Vuex itself recommends users choose Pinia, and more importantly, I experimentally succeeded with Pinia.

Pinia's usage is relatively simple but very unintuitive. We need to:

  1. Define a middleware
  2. In main's createApp, app.use(middlewarePinia), and must include the Pinia object in the return
  3. Use Pinia's defineStore to define the needed Stores
  4. Initialize the defined Stores in App.vue (if needed)

Then you can happily use the defined Stores everywhere in the application for reading and writing global state. In my application, I defined three Stores:

  1. defineStore('login', ...) for maintaining user login information
  2. defineStore('config', ...) for maintaining application configuration, such as third-party links, feature flags, etc.
  3. defineStore('system-info', ...) for maintaining some information obtained from uni.getSystemInfoSync, because some system information can change, like theme

API Requests

API returns generally wrap the actual data in a layer, something like this:

interface ApiResponse<T = null> {
  code: string;
  message: string | null;
  data: T;
}

The frontend generally only cares about two layers:

  1. data when code is "success"
  2. message when code is "failure"

I've seen overly diligent people add a conditional check after every API call, but I'm lazy, so I need a global request wrapper that directly gets the data on success and throws an error on failure.

At the same time, I've seen too many backend API issues, from naming to type weirdness, plus various inconsistencies. So, I wrapped the basic logic for API requests in fetcher, then based on fetcher, I wrapped all data interfaces in batches centered around objects, burying the API's method, URL, and the possibly extremely foul parameters and results into the data layer. This way, the requests exposed to the frontend business layer are clean, hygienic, pure Promise methods with clear types.

This is a programming approach I've consistently adhered to, and I'm unwilling to hand this over to AI, because the process of sorting out data interfaces and converting backend interfaces into frontend API methods is essentially the process of understanding the business. Moreover, this UI-independent joint debugging mode can discover backend design issues early in the joint debugging phase.

Later, when writing a Taro mini program, I really couldn't stand copying code everywhere, and to reuse the PC-side data wrapping logic as much as possible, I purified the @kcuf/fetcher wrapped during the PC-side period and wrote @kcuf/fetcher-core, making it usable independent of the environment, so the API request methods on each end could be kept on the same baseline.

SSE

Students who have written AI streaming interactions should know about SSE.

Mini program API requests have an enableChunked option, something like this (I no longer remember why I used wx.request instead of uni):

// #ifndef H5
const requestTask = wx.request({
  url: apiUrl,
  enableChunked: true,
  header: headers,
  success: resolvePromise,
  fail: (err): void => reject(createFetcherErrorNetwork(request, err.errMsg))
});

requestTask.onHeadersReceived(() => onOpen?.());
requestTask.onChunkReceived((e: IChunkReceivedArg) => {
  processArrayBuffer(e.data);
});
// #endif

But I needed to be compatible with H5, and the browser's built-in EventSource does not support header. Although there is EventSourcePolyfill, I tried it and found a bug with circular startup (unclear why), and the Polyfill is written quite bloated. So, I wrote my own @kcuf/fetch-sse.

Parsing SSE Chunks requires TextDecoder, which is not available as a global object in mini programs. You can refer to FastestSmallestTextEncoderDecoder.

Conditional Compilation

UniApp provides a conditional compilation mechanism. The last time I heard about "conditional compilation" was I don't know when, probably in ancient times.

The benefit of conditional compilation is that specific environments won't have code snippets that are never used.

But I actually don't really like conditional compilation, especially when it involves return statements, as it can look like a syntax error.

Logging

Although the WeChat platform adds logs like PV, UV, and even new users by default, more detailed logs should still be handled by us. I adopted the Alibaba Cloud SLS Web Tracking method to provide globally unified log support for the following scenarios:

  1. Mini program and page lifecycles
  2. API request successes and failures

Also, just keeping an eye on key user actions like clicks.

.env File, Is It Necessary?

You can write the API's Base URL or other environment-related configuration information in .env. But this means you need to bear one consequence: the build results for different environments cannot be unified.

Or maybe this isn't a big problem. But I prefer to write an env package (this is also the solution I used later when writing Taro), encapsulating everything environment-related inside this package, which also prevents the disastrous phenomenon of import.meta.env.VITE_XX flying everywhere.

🪁 Launch Struggles

You thought development was torturous enough? You thought too little. Launching will torture you all over again.

Package Size Limit

Due to my lack of experience, all pages were placed under src/pages, and I also didn't know what "main package" or "sub-package" meant. When uploading on the WeChat Developer Tool, I was told "Your package exceeds 2MB, cannot upload."

I saw the difference wasn't much, so I checked "Compress scripts on upload" in the project's local settings, barely passing. But the development tool stubbornly refuses to remember that option and reminds you every time. When the business code grows and exceeds the limit significantly, you have to consider sub-packaging.

Infuriatingly, whether UniApp, Taro, or WeChat itself, none of them treat sub-packaging as important documentation visible to developers. If you are in a hurry to launch and don't know what sub-packaging is (like I was back then), it will be very frustrating.

Qualification Materials

If your mini program is something brand new, then before launching, you need to prepare for about a week, because the WeChat platform (I don't know about others) will require you to upload some qualification materials. I don't remember the specific process, but overall it was quite annoying.

Before launch, they might also require uploading screenshots. They have AI that matches the content in the screenshots with your mini program's meta-information. If there are issues, it will be rejected (this process happens every time you release an official version).

🤯 Small Collection of Problem Records

I specifically recorded the problems encountered during the development process and their corresponding solutions; you might encounter them too.

SSR

Want to use SSR in H5? Better think it through clearly. Because many APIs haven't been protected accordingly, for example, uni.getLocale doesn't work (the official didn't provide a fallback, nor did they note it in the documentation).

This is why the start command uses uni instead of uni --ssr.

Internationalization - manifest.json

Unusable, reports an error in the WeChat Mini Program Developer Tool: "project.config.json: libVersion field needs to be string, string".

Internationalization Legacy Issues

Not legacy: false legacy: false
H5 $t
H5 useI18n 💥1
Mini Program $t 💥2 😱3
Mini Program useI18n

Among them, 2 and 3 easily lead to "problematic code going live," posing a higher risk. Therefore:

  1. Do not use legacy: false
  2. Do not use useI8n
  3. Only use $t

No @keydown.meta.enter

As stated.

TS Issues

Component Source Definition Actual
chooseImage @dcloudio/types chooseImage(...): void chooseImage(...): Promise
uni-search-bar @uni-helper/uni-ui-types onInput({ value: string | number; }): void onInput(value: string): void
uni-list-item @uni-helper/uni-ui-types link: false | Xx Can support true

@dcloudio/types provides the global namespace UniApp, which works perfectly in TS files, but cannot be used in Vue (even if the script is ts), as it will cause an Eslint error.

H5 VS MP

- H5 MP Description
$t internationalization interpolation parameters MP does not perform interpolation replacement, and IDE reports error that $t does not accept two parameters
slot name with hyphens Use single words if possible; if a phrase is necessary, use underscores or camelCase
slot v-if 🪲 MP triggers the slot's onMounted regardless of whether it's ultimately determined to need rendering https://github.com/dcloudio/uni-app/issues/4755. In such cases, cannot rely on onMounted for initialization operations like data fetching; can use watch, need to set immediate: true
v-if 🪲 Under MP, must be boolean, cannot be a function object
v-show 🪲 Under MP, v-show cannot be defined on a component definition (it will always be displayed), can only be used on
Component names like capitalized Input 🪲 Under MP, the rendered node becomes input, causing strange issues (like styling)
onLoad Sync Async
uni.showModal passing undefined for confirmText or cancelText 🪲 H5 displays English, MP throws an error
uni.chooseImage cancel Under H5, canceling causes the Promise to hang forever
uni.request(//xxx) MP invalid URL
uni.createInnerAudioContext() reusing a context MP throws error on second play "TypeError: Cannot read property 'then' of undefined". Solution: destroy after use
Passing class to wrapped components Under MP, there is an extra layer, and class is on this extra layer, causing styles to potentially fail, https://uniapp.dcloud.net.cn/tutorial/vue-api.html
<uni-row> / <uni-col> passing class Use CSS https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout
<uni-swipe-action> 🪲 Under MP, an extra view wraps on the right, causing flex to be invalid; must wrap an extra flex layer yourself
<text> wrapping components 🪲 Under MP, the <text> wrapping a component has a height of 0, but only text supports selectable...
Parameters in Url 🪲 Under H5, getting from props in script gives the correct (parsed) parameters, but in the template, they are still pre-parsing
Audio onCanplay 🪲 MP re-triggers once after ending...
Audio duration 🪲 MP cannot get duration on the first onCanplay, only on the second onCanplay automatically triggered after calling .stop()...
import style files in TS/JS 🪲 Under MP, they will be tree-shaken away

Map component requires applying for a Tencent key, https://lbs.qq.com/dev/console/application/mine. Positioning in Map needs to be tested in the mini program development tool; the web simulator cannot get geographic coordinates.

🌰 onLoad

import {
  onLoad
} from '@dcloudio/uni-app';

console.info(111);
onLoad(() => console.info(222));
console.info(333);

MP output:

111
333
222

H5 output:

111
222
333

Dev Tools VS Real Device

- WeChat Dev Tools Real Device Description
Intl Real device does not support vue-i18n's d and $d (warning)
TextDecoder
RecorderManager duration duration invalid in dev tools, needs hack

Third-party Libraries

Third-party Library H5 WeChat Dev Tools Real Device Description
marked @12.0.1 Error /^((?![*_])[\s\p{P}\p{S}])/: Invalid property name in character class
micromark @4.0.0 🚫 Build error [vite]: Rollup failed to resolve import "micromark-util-symbol" (or other packages)
markdown-it @14.0.0 🚫 Build error [vite]: Rollup failed to resolve import "linkify-it"

Other Code Design Issues

About Page Padding

Some pages need padding, some don't. It seems like the following code could solve it. But!

<style lang="scss" scoped>
page {
  padding: 20px;
}
</style>

No problem in H5, but MP doesn't work. If you want MP to work, you must remove scoped, then all pages under H5 are affected...

uni-list-item

link click error has no reaction, the event is also click instead of error, which makes the problem very hard to discover:

pageApi(api) {
  let callback = {
    url: this.to,
    success: res => {
      this.$emit('click', {
        data: res
      });
    },
    fail: err => {
      this.$emit('click', { // <-- ...
        data: err
      });
    }
  };
  
  // ...
}

🙋 FAQ

❓Why not use ncu to update uni-app packages?

Their packages are all pinned versions. I tried changing to ^x.y.z, which caused a series of strange issues like uni_modules and components auto-injection failing, and installed packages would have multiple version duplicates.

The official update method is to use npx @dcloudio/uvm@lates, refer to Update Dependencies to Specified Version.

Additionally, UniApp's overall dependencies seem very unhealthy. I once merely upgraded the @dcloudio/* packages, and then, it just wouldn't run... A long-standing old bug with no one attending to it.

📌️ Links

🪭 Final Words

If your tech stack is only Vue, then using UniApp to write mini programs is undoubtedly the best choice. If your tech stack is React, then you can try Taro (I tried it, it's usable).

If you care about the sustainable development of the framework, you'll find that Taro's update frequency is not as good as UniApp's. I once thought Taro was in a dead state because the last version hadn't moved for a long time, until one day (sometime in 2026/07), it actually bumped a minor version, and then the UI library I chose, @taroify, also bumped a few minor versions. Playing dead? Very confusing.. 🤔

If you choose Taro, then you have to tolerate its outdated toolchain (Vite 4, latest is 8, and Webpack, although version 5, must be pinned to a specific version) and the issue of only supporting up to React 18 (meaning you cannot freely upgrade base libraries).

Unfortunately, whether you choose UniApp or Taro, you have to tolerate their API design issues (some may need to be traced back to WeChat's own API design issues), the slowness and bugginess of the WeChat Mini Program Developer Tools, inconsistencies with real devices, and also H5 and mini program compatibility issues, etc.

In short, mini program development is the worst development experience I have ever seen 😘.

Comments

Top 1 from juejin.cn, machine-translated. The original thread is authoritative.

向新出发叭 2 likes

The part about getting into UniApp is too real; the development experience for mini programs is indeed hard to describe. I also went straight to the CLI back then—HBuilderX isn't friendly enough for version control and CI. The points you mentioned about wrapping the uni.* APIs, doing i18n early, and keeping a test page are very practical, especially the 2MB package size limit; you really must run it through webpack-bundle-analyzer before going live. I also had a headache with analytics and event tracking after launch; writing my own backend to collect events was too much trouble. Later I used Umeng's U-App mini program statistics, where events and retention are directly visible, saving a lot of effort. This is just personal experience for reference, you can search here: https://ask.umeng.com/?utm_source=ext_comm_f_sk&search=U-App%20%E5%B0%8F%E7%A8%8B%E5%BA%8F%E7%BB%9F%E8%AE%A1&_um_campaign=6a44a2996f259537c7c17452&_um_channel=6a44a2996f259537c7c17453 . Keep it up!