/install wmp-to-uniapp
WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion
Convert a native WeChat Mini Program project into a complete, runnable uni-app + Vue3 + TypeScript project.
Triggers
Triggers when user mentions converting, migrating, or porting a WeChat miniprogram / 微信小程序 to uni-app, or provides a miniprogram project path and asks to transform it. Also triggers for related tasks like analyzing a miniprogram project structure for conversion,or generating uni-app code from WXML/WXSS/JS source files.
Conversion Flow
1. Analyze source project
↓
2. Initialize uni-app project skeleton (TypeScript)
↓
3. Convert config (app.json → pages.json + manifest.json)
↓
4. Convert pages (wxml/js/wxss → Vue3 SFC, \x3Cscript setup lang="ts">)
↓
5. Convert components (Component() → Vue3 \x3Cscript setup lang="ts">)
↓
6. Convert utils & API calls (wx.* → uni.*, .js → .ts)
↓
7. Finalize & verify
Before starting, ask the user:
- Source project root path
- Target output directory (default:
./output-uni-appor adjacent to source) - Whether to keep WeChat-only features (conditional compilation) or drop them
Step 1: Analyze Source Project
Read these files to understand the project:
app.json— pages list, subPackages, tabBar, window config, globalStyle, usingComponentsproject.config.json— appid, compile settings (reference only)package.json(if exists) — npm dependencies, identify which can carry over- All page directories under
pages/— each needs 1 .wxml + 1 .js + 1 .wxss (1 .json optional) - All component directories under
components/or custom paths utils/and any custom JS modules (utils, api wrappers, configs)app.js— global logic, globalData, onLaunch/onShow lifecycleapp.wxss— global styles
Output a structured catalog:
Pages (N): pages/index/index, pages/detail/detail, ...
Components (M): components/star/star, components/list-item/list-item, ...
Utils (K): utils/request.js, utils/util.js, utils/config.js, ...
SubPackages (optional): pkgA/pages/...
TabBar: yes/no, N tabs
Dependencies: [list from package.json]
Cloud: yes/no (wx.cloud usage detected)
Custom processing (WXS, plugins, workers): [list]
This catalog drives all subsequent steps.
Step 2: Initialize uni-app Project Skeleton (TypeScript)
Create an empty uni-app project with the standard structure:
\x3Coutput-dir>/
├── pages/
├── components/
├── utils/
├── types/
│ └── global.d.ts
├── static/
│ └── images/
├── App.vue
├── main.ts
├── manifest.json
├── pages.json
├── tsconfig.json
└── uni.scss
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"types": ["@dcloudio/types"],
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
},
"skipLibCheck": true
},
"include": [
"*.vue",
"**/*.ts",
"**/*.tsx",
"**/*.vue"
],
"exclude": ["node_modules", "unpackage", "dist"]
}
types/global.d.ts
/// \x3Creference types="@dcloudio/types" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent\x3C{}, {}, any>
export default component
}
// Extend App global properties
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$globalData: Record\x3Cstring, any>
}
}
export {}
App.vue
\x3Cscript lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
onLaunch() {
console.log('App Launch')
},
onShow() {
console.log('App Show')
},
onHide() {
console.log('App Hide')
}
})
\x3C/script>
\x3Cstyle>
/* Global styles from original app.wxss go here */
\x3C/style>
main.ts
import { createSSRApp } from 'vue'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
return { app }
}
uni.scss
/* uni-app global style variables */
@import '@/uni.scss';
manifest.json
Generate minimal manifest with WeChat mini program appid from project.config.json:
{
"name": "",
"appid": "",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"mp-weixin": {
"appid": "\x3Cfrom-project.config.json>",
"setting": {
"urlCheck": false
},
"usingComponents": true
}
}
Step 3: Convert Configuration Files
3.1 app.json → pages.json
Map each field using the reference table in mappings.md.
Key rules:
pagesarray: first entry is the home page. Convert pathpages/index/indextopages/index/index.subPackages: preserve structure, prefix paths correctly.window→globalStyle: flatten allwindow.*fields intoglobalStyle.*.tabBar: copy verbatim (uni-app compatible). Append.selectedIconPathentries if missing.- Per-page config: each page's
.jsonfile →pages[n].style. If page hasusingComponents, map to page-level component registration.
3.2 app.js → App.vue
App({ onLaunch, onShow, onHide, globalData, ... })→App.vue \x3Cscript lang="ts">with equivalent lifecycle methods usingdefineComponent.globalData→ typed reactive object exported from a shared moduleutils/globalData.ts, or usegetApp().globalData.- Any initial API calls in
onLaunchcarry over.
3.3 app.wxss → App.vue \x3Cstyle>
- Copy content into
App.vue\x3Cstyle>block (no scoped). - Convert WXSS-only syntax:
- Remove
@importstatements, convert to CSS@importin\x3Cstyle>. - Keep
rpxas-is (uni-app supports it).
- Remove
Step 4: Convert Pages (WXML/JS/WXSS → Vue SFC + TypeScript)
For each page pages/foo/foo.{wxml,js,wxss,json}:
4.1 Create pages/foo/foo.vue
Template structure:
\x3Ctemplate>
\x3Cview class="page-foo">
\x3C!-- converted WXML content -->
\x3C/view>
\x3C/template>
\x3Cscript setup lang="ts">
// converted JS content, fully typed
\x3C/script>
\x3Cstyle scoped>
/* converted WXSS content */
\x3C/style>
4.2 WXML → \x3Ctemplate>
Use mappings from mappings.md:
wx:if→v-if;wx:else→v-else;wx:elif→v-else-ifwx:for="{{list}}"→v-for="(item, index) in list";wx:key="id"→:key="item.id"bind:tap="fn"→@tap="fn";catch:tap="fn"→@tap.stop="fn"hidden="{{v}}"→v-show="!v"(semantic inversion)data-xxx="{{v}}"→:data-xxx="v"ordata-xxx="v"for static strings\x3Cblock>→\x3Ctemplate>or remove (Vue fragment behavior){{ }}interpolation → keep as{{ }}(same)import src/include src→ Vue component imports
4.3 JS → \x3Cscript setup lang="ts">
Convert Page({ data, onLoad, methods, ... }):
// Source (page.js) — JavaScript, untyped:
Page({
data: {
count: 0,
list: [],
userInfo: null,
loading: false
},
onLoad(options) {
this.fetchData()
},
onShow() { /* ... */ },
onPullDownRefresh() {
this.fetchData()
},
increment() {
this.setData({ count: this.data.count + 1 })
},
fetchData() {
this.setData({ loading: true })
wx.request({
url: 'https://api.example.com/list',
success: (res) => {
this.setData({ list: res.data, loading: false })
}
})
}
})
\x3C!-- Target (page.vue) — TypeScript -->
\x3Cscript setup lang="ts">
import { ref, type Ref } from 'vue'
// --- Type definitions ---
interface ListItem {
id: number
title: string
coverUrl: string
createdAt: string
}
interface UserInfo {
nickName: string
avatarUrl: string
}
// --- Reactive state (typed) ---
const count = ref\x3Cnumber>(0)
const list = ref\x3CListItem[]>([])
const userInfo = ref\x3CUserInfo | null>(null)
const loading = ref\x3Cboolean>(false)
// --- Lifecycle ---
onLoad((options?: AnyObject) => {
fetchData()
})
onShow(() => {
// ...
})
onPullDownRefresh(() => {
fetchData()
})
// --- Methods ---
const increment = (): void => {
count.value++
}
const fetchData = async (): Promise\x3Cvoid> => {
loading.value = true
try {
const res = await uni.request\x3CListItem[]>({
url: 'https://api.example.com/list'
})
list.value = res.data as ListItem[]
} catch (err) {
uni.showToast({ title: '加载失败', icon: 'none' })
console.error('fetchData error:', err)
} finally {
loading.value = false
uni.stopPullDownRefresh()
}
}
\x3C/script>
Rules:
datafields →ref\x3CT>(initialValue)with explicit generic typesthis.setData({ k: v })→ direct assignmentdata.value = v(Vue reactivity handles updates)this.data.k→data.value- Lifecycle hooks: import from
@dcloudio/uni-app—onLoad,onShow,onReady,onHide,onUnload,onPullDownRefresh,onReachBottom,onPageScroll,onShareAppMessage,onShareTimeline - Callbacks →
asyncfunctions withPromise\x3CT>return types - Define interfaces/types at the top of
\x3Cscript setup>for all data structures onShareAppMessagereturns{ title, path, imageUrl }(same format)
4.4 WXSS → \x3Cstyle scoped>
- Copy content into
\x3Cstyle scoped>block. - Change
langtoscssif original uses SCSS-style nesting. rpxstays as-is.- Convert
@import "xxx.wxss"to@import "xxx.css"or drop (styles are scoped per-component).
4.5 Page-level JSON → pages.json per-page style
If a page has foo.json with { navigationBarTitleText, usingComponents, ... }, migrate:
navigationBarTitleTextetc. →pages.json>pages[n].styleusingComponents→ page-level component registration (or rely on easycom)
Step 5: Convert Components (Component() → Vue3 SFC + TypeScript)
For each component components/foo/foo.{wxml,js,wxss,json}:
5.1 Component JS → \x3Cscript setup lang="ts">
// Source — JavaScript Component():
Component({
properties: {
title: String,
count: { type: Number, value: 0 },
list: { type: Array, value: [] }
},
data: {
internalState: false,
expanded: false
},
methods: {
onTap() {
this.setData({ internalState: true })
this.triggerEvent('change', { value: 1 })
},
toggleExpand() {
this.setData({ expanded: !this.data.expanded })
}
},
observers: {
'count'(val) {
this.handleCountChange(val)
}
},
lifetimes: {
attached() {
this.loadData()
},
detached() {
this.cleanup()
}
}
})
\x3C!-- Target — TypeScript -->
\x3Cscript setup lang="ts">
import { ref, watch, onMounted, onUnmounted, type Ref } from 'vue'
// --- Typed Props ---
interface Props {
title: string
count?: number
list?: string[]
}
const props = withDefaults(defineProps\x3CProps>(), {
count: 0,
list: () => []
})
// --- Typed Emits ---
const emit = defineEmits\x3C{
change: [value: number]
}>()
// --- Internal state ---
const internalState = ref\x3Cboolean>(false)
const expanded = ref\x3Cboolean>(false)
// --- Methods ---
const onTap = (): void => {
internalState.value = true
emit('change', 1)
}
const toggleExpand = (): void => {
expanded.value = !expanded.value
}
const handleCountChange = (val: number | undefined): void => {
console.log('count changed to', val)
}
const loadData = (): void => {
// init logic
}
const cleanup = (): void => {
// cleanup logic
}
// --- Watchers (was observers) ---
watch(() => props.count, (val: number | undefined) => {
handleCountChange(val)
})
// --- Lifecycle (was lifetimes) ---
onMounted(() => {
loadData()
})
onUnmounted(() => {
cleanup()
})
\x3C/script>
Rules (see mappings.md §5):
properties→defineProps\x3CInterface>()with interface, ordefineProps({...})for runtime validation. UsewithDefaults(defineProps\x3C>(), {...})for defaults.data→ref\x3CT>()/reactive\x3CT>()methods→ typed functions ((): void => {...})this.triggerEvent(name, detail)→emit(name, value)with typeddefineEmits\x3C{ event: [payloadType] }>()observers→watch()/watchEffect()behaviors→ typed composables (use*.ts)lifetimes.created→ code in\x3Cscript setup lang="ts">top-levellifetimes.attached→onMounted()lifetimes.detached→onUnmounted()this.selectComponent(id)→ template refs withref\x3CInstanceType\x3Ctypeof Comp>>()externalClasses→ props-based class passing +:deep()selectorsrelations→ typedprovide/injectwith injection keys
5.2 WXML / WXSS
Same as page conversion (Step 4), with this key difference:
- WXML slot:
\x3Cslot>→\x3Cslot>(same in Vue) - Multiple named slots:
\x3Cslot name="header">→\x3Cslot name="header">(same)
5.3 Component Registration
uni-app auto-registers components placed in components/ via easycom. Ensure component filename matches its usage name:
components/star/star.vue → \x3Cstar /> (auto-registered)
If using custom paths, register explicitly in pages.json > globalStyle.usingComponents or page-level style.
Step 6: Convert Utils & API Calls (JS → TypeScript)
6.1 File Migration: .js → .ts
Rename all utility files and add type annotations:
// utils/request.ts (was utils/request.js)
interface RequestConfig {
url: string
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
data?: Record\x3Cstring, any>
header?: Record\x3Cstring, string>
}
interface RequestResponse\x3CT = any> {
code: number
data: T
message: string
}
const BASE_URL = 'https://api.example.com'
export const request = \x3CT = any>(config: RequestConfig): Promise\x3CRequestResponse\x3CT>> => {
return new Promise((resolve, reject) => {
uni.request({
url: BASE_URL + config.url,
method: config.method || 'GET',
data: config.data,
header: {
'Content-Type': 'application/json',
...config.header
},
success: (res) => {
const data = res.data as RequestResponse\x3CT>
if (data.code === 0) {
resolve(data)
} else {
uni.showToast({ title: data.message || '请求失败', icon: 'none' })
reject(data)
}
},
fail: (err) => {
uni.showToast({ title: '网络异常', icon: 'none' })
reject(err)
}
})
})
}
// utils/config.ts (was utils/config.js)
export interface AppConfig {
baseUrl: string
version: string
debug: boolean
}
export const config: AppConfig = {
baseUrl: 'https://api.example.com',
version: '1.0.0',
debug: false
}
6.2 Global wx.* → uni.* Replacement
Use the API mapping table in mappings.md §6. Most APIs are a direct namespace swap.
Walk through every file and replace:
wx.setStorageSync→uni.setStorageSyncwx.request→uni.requestwx.navigateTo→uni.navigateTowx.showToast→uni.showToast- (all 100+ commonly used APIs)
For APIs that don't have a uni-app equivalent, keep them inside conditional compilation:
// #ifdef MP-WEIXIN
wx.cloud.init()
const db = wx.cloud.database()
// #endif
6.3 Promise + TypeScript
Convert callbacks to async/await with types:
// Before:
wx.getSystemInfo({
success(res: WechatMiniprogram.SystemInfo) {
console.log(res.screenWidth)
}
})
// After:
const info = await uni.getSystemInfo()
console.log(info.screenWidth)
// With explicit type:
const info = await uni.getSystemInfo() as UniApp.GetSystemInfoResult
console.log(info.windowWidth)
6.4 getApp() and getCurrentPages()
These work identically in uni-app. No changes needed unless accessing WeChat-specific fields.
For typed access:
const app = getApp\x3C{ globalData: { userId: string; token: string } }>()
console.log(app.globalData.userId)
6.5 WXS Scripts → TypeScript
WXS has no direct Vue equivalent. For each .wxs file:
- Simple data formatting → TypeScript utility functions with explicit types, imported into
\x3Cscript setup lang="ts">, used incomputedor template expressions - Event handlers in WXS → move to Vue methods (typed)
- Complex WXS logic → extract as a standalone
.tsmodule, import where needed
// utils/format.ts (was utils/filter.wxs)
export const formatPrice = (price: number): string => {
return `¥${price.toFixed(2)}`
}
export const formatTime = (timestamp: number, fmt: string = 'YYYY-MM-DD HH:mm:ss'): string => {
const d = new Date(timestamp)
const pad = (n: number): string => n.toString().padStart(2, '0')
return fmt
.replace('YYYY', d.getFullYear().toString())
.replace('MM', pad(d.getMonth() + 1))
.replace('DD', pad(d.getDate()))
.replace('HH', pad(d.getHours()))
.replace('mm', pad(d.getMinutes()))
.replace('ss', pad(d.getSeconds()))
}
6.6 Dependencies
Check package.json dependencies:
- 3rd-party miniprogram components: find uni-app equivalents or keep inside
#ifdef MP-WEIXIN - Utility libs (lodash, dayjs, etc.): carry over directly — install with
@types/*packages for TypeScript support - WeChat-specific SDKs: conditional compilation
Step 7: Finalize & Verify
7.1 Write the Complete Project
Write all converted files to the output directory. For every text file write, use the scripts/write_file.py script from the qclaw-text-file skill to ensure correct encoding and line endings.
7.2 Verification Checklist
Run through this checklist:
-
pages.jsonhas correct first page and all pages listed - All page
.vuefiles exist with\x3Cscript setup lang="ts"> - All component
.vuefiles exist with typeddefineProps\x3C>()/defineEmits\x3C>() - No
setData()calls remain (replaced with direct.valueassignment) - No untyped
ref()calls — all useref\x3CT>(...)with explicit generics - No
wx.xxxcalls remain unhandled (mapped touni.xxxor conditional compilation) - All
bind:→@,catch:→@.stop,wx:if→v-if -
hiddenattributes inverted forv-show - Page lifecycles use correct imports from
@dcloudio/uni-app - WXS logic extracted to typed
.tsmodules - WeChat-specific features wrapped in
#ifdef MP-WEIXIN - Static assets (images, fonts) copied to
static/ -
manifest.jsonhas WeChat appid set -
App.vueuses\x3Cscript lang="ts">withdefineComponent -
tsconfig.jsonpresent with correct paths and strict mode -
types/global.d.tspresent with.vuemodule declaration - All
.jsutils renamed to.tswith interfaces and type annotations
7.3 Report Summary
Output a summary table:
Total pages converted: N (\x3Cscript setup lang="ts">)
Total components converted: M (defineProps\x3C>() / defineEmits\x3C>())
Total utils converted: K (.js → .ts with types)
Custom interfaces/types defined: X
Wx.* → uni.* replacements: Y
Conditional-compilation blocks added: Z
Lines of WXS converted to TS utils: W
Pending manual review items: [list]
7.4 Manual Review Items
Flag these for manual review:
wx.cloudcalls (needs uniCloud migration or conditional compile)- Custom
getApp().globalDatapatterns — add type assertions - Third-party miniprogram UI libraries (see if uni-app alternatives exist)
- Complex animation logic (
wx.createAnimation) — types may need manual adjustment requirePluginusages — no TypeScript types available- Canvas API uses (API signatures may differ slightly)
- Complex selectors or DOM manipulation that may not translate cleanly
- Any inferred
anytypes that need explicit annotation
Post-Conversion: Run the Project
After conversion, tell the user to:
cd \x3Coutput-dir>
npm install
npm install -D @dcloudio/types @types/node
# Run in HBuilderX or with CLI:
npx @dcloudio/uvm
npm run dev:mp-weixin
Import dist/dev/mp-weixin into WeChat DevTools to test.
Resources
references/mappings.md
Complete API, component, lifecycle, config, and syntax mapping tables with TypeScript examples. Always consult this file during conversion for exact field-to-field mappings. Load it at the start of Step 3 and reference throughout Steps 3-6.
Key sections:
- §1: Config mapping (app.json → pages.json/manifest.json)
- §2: File structure mapping
- §3: WXML → Vue template syntax
- §4: Data binding & events
- §5: Lifecycle mapping (pages & components)
- §6: API mapping (wx.* → uni.*)
- §7: WXSS → CSS/SCSS
- §8: uni-app added APIs
- §9: Conditional compilation syntax (with TS)
- §10: Common problem patterns
- §11: TypeScript-specific patterns & composables
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install wmp-to-uniapp - After installation, invoke the skill by name or use
/wmp-to-uniapp - Provide required inputs per the skill's parameter spec and get structured output
What is WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion?
Convert native WeChat Mini Program projects into uni-app + Vue3 + TypeScript cross-platform projects. It is an AI Agent Skill for Claude Code / OpenClaw, with 22 downloads so far.
How do I install WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion?
Run "/install wmp-to-uniapp" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion free?
Yes, WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion support?
WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion?
It is built and maintained by OpenLark (@openlark); the current version is v1.0.0.