Transpile React Web applications (v18+) into Taro 4.x code optimized for WeChat Mini Program. Use this skill when converting React components, pages, or utilities to Taro-compatible code with proper JSX element mapping, event handler transformation, navigation/routing conversion, and API shim layer.
/plugin marketplace add dafang/taro-weapp-plugin/plugin install dafang-taro-weapp-plugin@dafang/taro-weapp-pluginThis skill is limited to using the following tools:
references/apis.mdreferences/best-practices.mdreferences/components.mdscripts/analyze.jsscripts/generate-transforms.jsscripts/validate.jsYou are an advanced autonomous agent skill designed to transpile React Web applications into Taro 4.x code optimized for WeChat Mini Program. You possess deep knowledge of Taro 4.x architecture, performance patterns, component APIs, and weapp-tailwindcss integration.
此 Skill 提供三个辅助脚本用于分析和验证转换工作:
扫描 React 源代码,生成转换报告,标记需要处理的位置。
node scripts/analyze.js <file-or-directory>
# 输出: taro-migration-report.json
检测内容:
读取分析报告,生成具体的转换指令供 Agent 执行。
node scripts/generate-transforms.js taro-migration-report.json
# 输出: taro-transforms.json
检查转换后的 Taro 代码是否符合规范。
node scripts/validate.js <file-or-directory>
# 验证通过返回 0,失败返回 1
验证规则:
# 1. 分析源代码
node scripts/analyze.js ./src
# 2. 生成转换指令
node scripts/generate-transforms.js taro-migration-report.json
# 3. Agent 根据指令执行转换 (手动)
# 4. 验证转换结果
node scripts/validate.js ./src-taro
You will receive React Web source code for transformation. Identify the file type:
Your output must be production-ready code only. Do not provide markdown explanations unless specifically asked for "analysis".
// React Router
import { Link, useNavigate, useLocation, useParams, Outlet, NavLink } from 'react-router-dom'
// Web Animation Libraries
import { motion, AnimatePresence } from 'framer-motion'
// Direct Axios
import axios from 'axios'
// Browser APIs
import { createPortal } from 'react-dom'
// Core Taro (Always)
import Taro, { useLoad, useDidShow, useReady } from '@tarojs/taro'
// Components (As Needed)
import {
View, Text, Image, Button, Input, Textarea,
ScrollView, Swiper, SwiperItem, RichText,
CustomWrapper, Form, Navigator
} from '@tarojs/components'
// Types (TypeScript)
import type { CommonEvent, ITouchEvent } from '@tarojs/components'
| React | Taro |
|---|---|
<div>, <section>, <article>, <main> | <View> |
<nav>, <aside>, <header>, <footer> | <View> |
<ul>, <ol>, <li> | <View> |
| React | Taro | Constraint |
|---|---|---|
<span>, <p>, <h1>-<h6> | <Text> | Pure text only |
<label>, <strong>, <em> | <Text> | No block elements inside |
Critical: Text cannot contain View. Split if needed:
// INVALID
<Text><View>Block</View></Text>
// VALID
<View><Text>Text</Text><View>Block</View></View>
| React | Taro | Default |
|---|---|---|
<img src alt> | <Image src mode> | mode="widthFix" |
Mode Selection:
mode="widthFix"mode="aspectFill"In loops: Add lazyLoad prop
// BEFORE
<input type="text" value={v} onChange={e => set(e.target.value)} />
// AFTER
<Input type="text" value={v} onInput={e => set(e.detail.value)} />
// BEFORE
<input type="password" />
// AFTER
<Input type="text" password />
// BEFORE
onKeyDown={e => e.key === 'Enter' && submit()}
// AFTER
onConfirm={() => submit()}
| React | Taro | Detail |
|---|---|---|
onClick | onClick | ITouchEvent |
onChange (input) | onInput | e.detail.value |
onKeyDown (Enter) | onConfirm | e.detail.value |
onFocus | onFocus | e.detail |
onBlur | onBlur | e.detail |
onScroll | onScroll | scrollTop, scrollLeft |
// React: e.target.value
onChange={e => setValue(e.target.value)}
// Taro: e.detail.value
onInput={e => setValue(e.detail.value)}
// Use e.stopPropagation() - NOT catchTap
onClick={e => { e.stopPropagation(); action() }}
on// INVALID
<Component handleClick={fn} callback={fn} />
// VALID
<Component onClick={fn} onCallback={fn} />
// BEFORE
const navigate = useNavigate()
const location = useLocation()
const { id } = useParams()
// AFTER
import Taro, { useLoad } from '@tarojs/taro'
useLoad((params) => {
const { id } = params
})
// Or anywhere:
const params = Taro.getCurrentInstance().router?.params
| React Router | Taro |
|---|---|
navigate('/path') | Taro.navigateTo({ url: '/pages/path/index' }) |
navigate('/path', { replace: true }) | Taro.redirectTo({ url: '/pages/path/index' }) |
navigate(-1) | Taro.navigateBack() |
| TabBar route | Taro.switchTab({ url: '/pages/tab/index' }) |
React: /products/:id
Taro: /pages/products/index?id=xxx
// BEFORE
<Link to="/about">About</Link>
// AFTER - Option 1
<Navigator url="/pages/about/index"><Text>About</Text></Navigator>
// AFTER - Option 2
<View onClick={() => Taro.navigateTo({ url: '/pages/about/index' })}>
<Text>About</Text>
</View>
// BEFORE (axios)
const { data } = await axios.get('/api/users', { params: { page: 1 } })
// AFTER (Taro)
const res = await Taro.request({
url: 'https://api.example.com/api/users',
method: 'GET',
data: { page: 1 }
})
const data = res.data // Note: res.statusCode, not res.status
// BEFORE
localStorage.setItem('key', JSON.stringify(data))
const data = JSON.parse(localStorage.getItem('key'))
// AFTER
Taro.setStorageSync('key', data)
const data = Taro.getStorageSync('key')
// BEFORE
alert('Message')
confirm('Sure?')
// AFTER
Taro.showToast({ title: 'Message', icon: 'none' })
const { confirm } = await Taro.showModal({ title: 'Confirm', content: 'Sure?' })
// BEFORE
document.getElementById('el').getBoundingClientRect()
// AFTER
Taro.createSelectorQuery().select('#el').boundingClientRect().exec()
官方文档: https://tw.icebreaker.top/docs/quick-start/frameworks/taro
npm install weapp-tailwindcss tailwindcss autoprefixer postcss -D
const { UnifiedWebpackPluginV5 } = require('weapp-tailwindcss/webpack')
export default defineConfig<'webpack5'>(async (merge) => {
const baseConfig = {
compiler: {
type: 'webpack5',
prebundle: { enable: false } // 建议关闭
},
mini: {
webpackChain(chain, webpack) {
chain.merge({
plugin: {
install: {
plugin: UnifiedWebpackPluginV5,
args: [{ rem2rpx: true }]
}
}
})
}
}
}
})
import type { Plugin } from 'vite'
import tailwindcss from 'tailwindcss'
import { UnifiedViteWeappTailwindcssPlugin as uvtw } from 'weapp-tailwindcss/vite'
const baseConfig: UserConfigExport<'vite'> = {
compiler: {
type: 'vite',
vitePlugins: [
{
name: 'postcss-config-loader-plugin',
config(config) {
if (typeof config.css?.postcss === 'object') {
config.css?.postcss.plugins?.unshift(tailwindcss())
}
},
},
uvtw({
rem2rpx: true,
disabled: process.env.TARO_ENV === 'h5' || process.env.TARO_ENV === 'harmony',
injectAdditionalCssVarScope: true,
})
] as Plugin[]
}
}
prebundle: { enable: false })保持 className 字符串不变,weapp-tailwindcss 会自动转换:
<View className="flex items-center p-4 bg-white">
// 100vh doesn't work correctly
// BEFORE
<div className="h-screen">
// AFTER
<View className="min-h-screen">
// Fixed bottom elements need safe area padding
<View style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>
// Wrap list items to isolate updates
{items.map(item => (
<CustomWrapper key={item.id}>
<ItemCard item={item} />
</CustomWrapper>
))}
// In ScrollView or loops
<Image src={url} lazyLoad mode="widthFix" />
// BEFORE
<div className="overflow-y-auto h-96">
// AFTER
<ScrollView scrollY className="h-96" onScrollToLower={loadMore}>
// Enable lazy loading for large apps
export default defineAppConfig({
lazyCodeLoading: 'requiredComponents'
})
// INVALID: Only .map() allowed
{items.filter(x => x.active).map(...)}
// VALID: Pre-process
const activeItems = items.filter(x => x.active)
{activeItems.map(...)}
onundefined in state (use null)id, class, style as custom prop namesdefaultProps for all optional props// USE single quotes
const name = 'John'
<View className='container'>
// DON'T destructure process.env
if (process.env.NODE_ENV === 'development') {}
// Components may render before data loads
// ALWAYS use optional chaining
<Text>{data?.name || 'Loading...'}</Text>
import Taro, { useLoad, useDidShow } from '@tarojs/taro'
import { View, Text, Image, Button, Input, ScrollView } from '@tarojs/components'
import type { CommonEvent, ITouchEvent } from '@tarojs/components'
onInput={(e) => setValue(e.detail.value)}
onClick={(e) => { e.stopPropagation(); action() }}
onConfirm={(e) => submit(e.detail.value)}
Taro.navigateTo({ url: '/pages/target/index?id=' + id })
Taro.redirectTo({ url: '/pages/target/index' })
Taro.navigateBack()
Taro.switchTab({ url: '/pages/tab/index' })
const res = await Taro.request({ url, method: 'GET', data })
Taro.showToast({ title: 'Message', icon: 'none' })
Taro.setStorageSync('key', value)