A rewrite in name only
You have a web page that works. It renders, it fetches, it stores a token, it listens for scroll. To ship it inside a mini-program runtime, you do not throw it away — you remap it. The logic mostly survives; the surface it runs on changes. The trap is assuming the browser is still underneath. It is not: there is no document, no window, no localStorage global, and no <div> you can poke at. Here is the map that keeps a migration predictable.
Lifecycle: events become callbacks
On the web you lean on DOMContentLoaded, load, and visibilitychange. A mini-program page is a Page() object whose lifecycle is a set of named callbacks:
Page({
onLoad(query) { /* once, on first create — read route params here */ },
onShow() { /* every time the page appears, incl. return from child */ },
onReady() { /* first render done, safe to query layout */ },
onHide() { /* backgrounded or navigated away */ },
onUnload() { /* destroyed — clear timers here */ },
})
The single biggest behavioral difference: onShow fires on return from a child page, so list pages that must reflect a mutation made downstream re-fetch there — not only in onLoad. App-level events (onLaunch, onShow, onHide) live on App(). There is no beforeunload prompt; if you need a guard, intercept the navigation call.
View: HTML and CSS become WXML and WXSS
HTML becomes WXML — a template with {{ }} bindings and no DOM you can traverse. You never document.querySelector and mutate; you call this.setData({ key: value }) and the render layer diffs. That single rule eliminates a whole category of layout bugs but also kills any code that assumed it could reach into the node tree.
WXSS is CSS with two changes that bite: a new unit rpx where 750rpx equals the screen width (so 375px design → 750rpx, auto-scaled per device), and a reduced selector set — no adjacent or attribute selectors, and far fewer pseudo-classes (last-child is gone). Keep styles flat and class-based.
Network: fetch becomes wx.request
This is where most migrations stall. wx.request is not fetch:
| Concern | Web (fetch) | Mini-program (wx.request) |
|---|---|---|
| Transport | HTTP/HTTPS, any host | HTTPS/WSS only, host must be on the allowlist |
| Domain | none | Server domain whitelist configured in console; localhost/IP blocked |
| Concurrency | browser-limited | Max 10 concurrent for request/upload/download |
| Timeout | per call | Default and max 60s; configurable in app.json |
| Referrer | set by browser | Fixed platform format, cannot be customized |
| Background | continues | If not finished in 5s after background, fail interrupted |
The allowlist is the classic wall: a request that works in DevTools with domain checking off fails in the trial and production builds until the domain is registered and ICP-filed. Wrap wx.request in a Promise layer for headers, token injection, and 401 handling — exactly as you would for fetch, but plan the queue because of the 10-connection cap.
Storage and state
localStorage becomes wx.setStorageSync / wx.getStorageSync, capped at 10MB. sessionStorage has no direct twin — use page-route params or global getApp().globalData for the short-lived bits. Do not assume persistence across a cold start is instant; read async where the UI can tolerate it.
When Web APIs have no twin
Some browser capabilities simply are not there, and the migration plan must name them:
- Geolocation —
navigator.geolocation→wx.getLocation(with scope authorization). - Media / live — WebRTC is unavailable; use native
<live-pusher>/<live-player>components. - Maps, camera, video — native components
<map>,<camera>,<video>replace the HTML elements and sit above the WebView layer. - Cookies / same-origin — there is no cookie jar shared with a backend the way the browser has; auth travels as tokens in request headers you inject yourself.
- File system — no arbitrary FS; use
wx.getFileSystemManagerfor the sandbox you are given.
Where Cross Mini App fits
Cross Mini App gives you a runtime that already speaks this surface — wx.* parity, rpx layout, the 10-connection model, and the allowlist semantics — so a ported H5 becomes a catalog entry rather than a re-platforming project. You keep your business logic, swap the browser-shaped edges for runtime-shaped ones, and ship inside a host app that your users already have. The migration is a map, not a cliff.
Sources
- WeChat — Page(Object): lifecycle callbacks onLoad/onShow/onReady/onHide/onUnload
- WeChat — Network: wx.request allowlist, 10-concurrent cap, 60s timeout, HTTPS only
- Tencent Cloud — WXSS, rpx unit, and reduced CSS selector set
- WeChat — Framework: WXML data binding, no DOM/BOM, setData render model
- WeChat — Storage API: setStorageSync/getStorageSync, 10MB cap