← Back to Skills Marketplace
openlark

WeChat Mini Program → uni-app + Vue3 + TypeScript Conversion

by OpenLark · GitHub ↗ · v1.0.0 · MIT-0
cross-platform ✓ Security Clean
22
Downloads
0
Stars
0
Active Installs
1
Versions
Install in OpenClaw
/install wmp-to-uniapp
Description
Convert native WeChat Mini Program projects into uni-app + Vue3 + TypeScript cross-platform projects.
README (SKILL.md)

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-app or 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:

  1. app.json — pages list, subPackages, tabBar, window config, globalStyle, usingComponents
  2. project.config.json — appid, compile settings (reference only)
  3. package.json (if exists) — npm dependencies, identify which can carry over
  4. All page directories under pages/ — each needs 1 .wxml + 1 .js + 1 .wxss (1 .json optional)
  5. All component directories under components/ or custom paths
  6. utils/ and any custom JS modules (utils, api wrappers, configs)
  7. app.js — global logic, globalData, onLaunch/onShow lifecycle
  8. app.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:

  • pages array: first entry is the home page. Convert path pages/index/index to pages/index/index.
  • subPackages: preserve structure, prefix paths correctly.
  • windowglobalStyle: flatten all window.* fields into globalStyle.*.
  • tabBar: copy verbatim (uni-app compatible). Append .selectedIconPath entries if missing.
  • Per-page config: each page's .json file → pages[n].style. If page has usingComponents, 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 using defineComponent.
  • globalData → typed reactive object exported from a shared module utils/globalData.ts, or use getApp().globalData.
  • Any initial API calls in onLaunch carry over.

3.3 app.wxss → App.vue \x3Cstyle>

  • Copy content into App.vue \x3Cstyle> block (no scoped).
  • Convert WXSS-only syntax:
    • Remove @import statements, convert to CSS @import in \x3Cstyle>.
    • Keep rpx as-is (uni-app supports it).

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:ifv-if; wx:elsev-else; wx:elifv-else-if
  • wx: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" or data-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:

  • data fields → ref\x3CT>(initialValue) with explicit generic types
  • this.setData({ k: v }) → direct assignment data.value = v (Vue reactivity handles updates)
  • this.data.kdata.value
  • Lifecycle hooks: import from @dcloudio/uni-apponLoad, onShow, onReady, onHide, onUnload, onPullDownRefresh, onReachBottom, onPageScroll, onShareAppMessage, onShareTimeline
  • Callbacks → async functions with Promise\x3CT> return types
  • Define interfaces/types at the top of \x3Cscript setup> for all data structures
  • onShareAppMessage returns { title, path, imageUrl } (same format)

4.4 WXSS → \x3Cstyle scoped>

  • Copy content into \x3Cstyle scoped> block.
  • Change lang to scss if original uses SCSS-style nesting.
  • rpx stays 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:

  • navigationBarTitleText etc. → pages.json > pages[n].style
  • usingComponents → 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):

  • propertiesdefineProps\x3CInterface>() with interface, or defineProps({...}) for runtime validation. Use withDefaults(defineProps\x3C>(), {...}) for defaults.
  • dataref\x3CT>() / reactive\x3CT>()
  • methods → typed functions ((): void => {...})
  • this.triggerEvent(name, detail)emit(name, value) with typed defineEmits\x3C{ event: [payloadType] }>()
  • observerswatch() / watchEffect()
  • behaviors → typed composables (use*.ts)
  • lifetimes.created → code in \x3Cscript setup lang="ts"> top-level
  • lifetimes.attachedonMounted()
  • lifetimes.detachedonUnmounted()
  • this.selectComponent(id) → template refs with ref\x3CInstanceType\x3Ctypeof Comp>>()
  • externalClasses → props-based class passing + :deep() selectors
  • relations → typed provide/inject with 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.setStorageSyncuni.setStorageSync
  • wx.requestuni.request
  • wx.navigateTouni.navigateTo
  • wx.showToastuni.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 in computed or template expressions
  • Event handlers in WXS → move to Vue methods (typed)
  • Complex WXS logic → extract as a standalone .ts module, 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.json has correct first page and all pages listed
  • All page .vue files exist with \x3Cscript setup lang="ts">
  • All component .vue files exist with typed defineProps\x3C>() / defineEmits\x3C>()
  • No setData() calls remain (replaced with direct .value assignment)
  • No untyped ref() calls — all use ref\x3CT>(...) with explicit generics
  • No wx.xxx calls remain unhandled (mapped to uni.xxx or conditional compilation)
  • All bind:@, catch:@.stop, wx:ifv-if
  • hidden attributes inverted for v-show
  • Page lifecycles use correct imports from @dcloudio/uni-app
  • WXS logic extracted to typed .ts modules
  • WeChat-specific features wrapped in #ifdef MP-WEIXIN
  • Static assets (images, fonts) copied to static/
  • manifest.json has WeChat appid set
  • App.vue uses \x3Cscript lang="ts"> with defineComponent
  • tsconfig.json present with correct paths and strict mode
  • types/global.d.ts present with .vue module declaration
  • All .js utils renamed to .ts with 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.cloud calls (needs uniCloud migration or conditional compile)
  • Custom getApp().globalData patterns — add type assertions
  • Third-party miniprogram UI libraries (see if uni-app alternatives exist)
  • Complex animation logic (wx.createAnimation) — types may need manual adjustment
  • requirePlugin usages — no TypeScript types available
  • Canvas API uses (API signatures may differ slightly)
  • Complex selectors or DOM manipulation that may not translate cleanly
  • Any inferred any types 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
Usage Guidance
Install this only if you want an agent to inspect a WeChat Mini Program source tree and generate many converted files. Provide an explicit fresh output folder, avoid pointing it at an existing project directory, and review generated code and dependencies before running the converted app.
Capability Assessment
Purpose & Capability
The requested capabilities match the stated purpose: inspect Mini Program source files, convert configuration/pages/components/utils/styles, and generate a new uni-app project.
Instruction Scope
The trigger language is somewhat broad around analysis, migration, and code generation, but it remains tied to WeChat Mini Program to uni-app conversion and asks for source and target paths before starting.
Install Mechanism
The artifact contains markdown instructions and a mapping reference only; no bundled executable scripts, install hooks, dependencies, or hidden runtime code were present.
Credentials
Reading a source project tree and writing many output files is proportionate for a converter, but users should use a dedicated empty output directory to avoid accidental overwrites or workspace pollution.
Persistence & Privilege
No persistence, background workers, privilege escalation, credential harvesting, session/profile access, or automatic network execution was found; post-conversion npm commands are user-run instructions.
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install wmp-to-uniapp
  3. After installation, invoke the skill by name or use /wmp-to-uniapp
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v1.0.0
wmp-to-uniapp v1.0.0 - Initial release: convert native WeChat Mini Program projects to uni-app + Vue3 + TypeScript cross-platform projects. - Catalogs and analyzes source projects, including configuration, pages, components, utils, and styles. - Sets up a new uni-app project skeleton with TypeScript and essential configuration files. - Automatically converts core configs (app.json, app.js, app.wxss) to their uni-app equivalents. - Transforms pages and components from WXML/JS/WXSS to Vue 3 SFCs with `<script setup lang="ts">`. - Migrates API calls and project utilities from `wx.*` to `uni.*`, and `.js` files to `.ts`.
Metadata
Slug wmp-to-uniapp
Version 1.0.0
License MIT-0
All-time Installs 0
Active Installs 0
Total Versions 1
Frequently Asked Questions

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.

💬 Comments