react-to-taro
npx skills add https://github.com/dafang/taro-weapp-plugin --skill react-to-taro
Agent 安装分布
Skill 文档
React to Taro WeChat Mini Program Compiler
You 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.
Scripts (è¾ å©èæ¬)
æ¤ Skill æä¾ä¸ä¸ªè¾ å©èæ¬ç¨äºåæåéªè¯è½¬æ¢å·¥ä½:
1. analyze.js – 代ç åæå¨
æ«æ React æºä»£ç ï¼çæè½¬æ¢æ¥åï¼æ è®°éè¦å¤ççä½ç½®ã
node scripts/analyze.js <file-or-directory>
# è¾åº: taro-migration-report.json
æ£æµå 容:
- JSX å ç´ (div, span, img, input ç)
- äºä»¶å¤çå¨ (onChange, onKeyDown ç)
- è·¯ç±ä»£ç (react-router-dom)
- Web API (axios, localStorage, DOM)
2. generate-transforms.js – è½¬æ¢æä»¤çæå¨
读ååææ¥åï¼çæå ·ä½çè½¬æ¢æä»¤ä¾ Agent æ§è¡ã
node scripts/generate-transforms.js taro-migration-report.json
# è¾åº: taro-transforms.json
3. validate.js – 代ç éªè¯å¨
æ£æ¥è½¬æ¢åç Taro ä»£ç æ¯å¦ç¬¦åè§èã
node scripts/validate.js <file-or-directory>
# éªè¯éè¿è¿å 0ï¼å¤±è´¥è¿å 1
éªè¯è§å:
- æ Web åçå ç´
- æ react-router-dom
- æ DOM API / localStorage
- æ e.target.value (åºä¸º e.detail.value)
- æ undefined state
æ¨è工使µ
# 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
Input Context
You will receive React Web source code for transformation. Identify the file type:
- component: React functional/class component
- page: React page with routing
- app_entry: App entry point with routes
- utility: Helper functions/hooks
Operational Protocol
Your output must be production-ready code only. Do not provide markdown explanations unless specifically asked for “analysis”.
RULESET A: IMPORT TRANSFORMATION
Destroy List (Remove These)
// 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'
Inject List (Add These)
// 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'
RULESET B: JSX ELEMENT MAPPING
Container Elements â View
| React | Taro |
|---|---|
<div>, <section>, <article>, <main> |
<View> |
<nav>, <aside>, <header>, <footer> |
<View> |
<ul>, <ol>, <li> |
<View> |
Text Elements â Text
| 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>
Media Elements â Image
| React | Taro | Default |
|---|---|---|
<img src alt> |
<Image src mode> |
mode="widthFix" |
Mode Selection:
- Width-constrained:
mode="widthFix" - Fixed height/square:
mode="aspectFill"
In loops: Add lazyLoad prop
Form Elements
Input
// BEFORE
<input type="text" value={v} onChange={e => set(e.target.value)} />
// AFTER
<Input type="text" value={v} onInput={e => set(e.detail.value)} />
Password
// BEFORE
<input type="password" />
// AFTER
<Input type="text" password />
Keyboard Submit
// BEFORE
onKeyDown={e => e.key === 'Enter' && submit()}
// AFTER
onConfirm={() => submit()}
RULESET C: EVENT TRANSFORMATION
Event Mapping
| 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 |
Critical Pattern
// React: e.target.value
onChange={e => setValue(e.target.value)}
// Taro: e.detail.value
onInput={e => setValue(e.detail.value)}
Event Propagation
// Use e.stopPropagation() - NOT catchTap
onClick={e => { e.stopPropagation(); action() }}
Custom Events Must Start with on
// INVALID
<Component handleClick={fn} callback={fn} />
// VALID
<Component onClick={fn} onCallback={fn} />
RULESET D: NAVIGATION & ROUTING
Hook Replacement
// 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
Navigation Actions
| 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' }) |
Path Convention
React: /products/:id
Taro: /pages/products/index?id=xxx
Link Transformation
// 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>
RULESET E: API SHIM LAYER
HTTP Requests
// 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
Storage
// BEFORE
localStorage.setItem('key', JSON.stringify(data))
const data = JSON.parse(localStorage.getItem('key'))
// AFTER
Taro.setStorageSync('key', data)
const data = Taro.getStorageSync('key')
Feedback
// BEFORE
alert('Message')
confirm('Sure?')
// AFTER
Taro.showToast({ title: 'Message', icon: 'none' })
const { confirm } = await Taro.showModal({ title: 'Confirm', content: 'Sure?' })
DOM Query
// BEFORE
document.getElementById('el').getBoundingClientRect()
// AFTER
Taro.createSelectorQuery().select('#el').boundingClientRect().exec()
RULESET F: STYLE & LAYOUT
Tailwind CSS with weapp-tailwindcss
宿¹ææ¡£: https://tw.icebreaker.top/docs/quick-start/frameworks/taro
å®è£ ä¾èµ
npm install weapp-tailwindcss tailwindcss autoprefixer postcss -D
Webpack5 é ç½® (config/index.ts)
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 }]
}
}
})
}
}
}
})
Vite é ç½® (æ¿ä»£æ¹æ¡)
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[]
}
}
注æäºé¡¹
- å ³é微信å¼åè å·¥å ·ç”代ç èªå¨çéè½½”åè½ï¼å¦åæ ·å¼å¯è½ä¸çæ
- ä¸ NutUI æ @tarojs/plugin-html ä¸èµ·ä½¿ç¨æ¶éæ¥ç宿¹æ³¨æäºé¡¹
- 建议å
³é prebundle åè½ (
prebundle: { enable: false })
ä½¿ç¨æ¹å¼
ä¿æ className å符串ä¸åï¼weapp-tailwindcss ä¼èªå¨è½¬æ¢:
<View className="flex items-center p-4 bg-white">
Viewport Fixes
// 100vh doesn't work correctly
// BEFORE
<div className="h-screen">
// AFTER
<View className="min-h-screen">
Safe Area (iPhone X+)
// Fixed bottom elements need safe area padding
<View style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}>
RULESET G: PERFORMANCE OPTIMIZATION
CustomWrapper for Lists
// Wrap list items to isolate updates
{items.map(item => (
<CustomWrapper key={item.id}>
<ItemCard item={item} />
</CustomWrapper>
))}
Image Lazy Loading
// In ScrollView or loops
<Image src={url} lazyLoad mode="widthFix" />
ScrollView for Scrollable Lists
// BEFORE
<div className="overflow-y-auto h-96">
// AFTER
<ScrollView scrollY className="h-96" onScrollToLower={loadMore}>
App Config
// Enable lazy loading for large apps
export default defineAppConfig({
lazyCodeLoading: 'requiredComponents'
})
RULESET H: PLATFORM CONSTRAINTS
JSX Limitations
// INVALID: Only .map() allowed
{items.filter(x => x.active).map(...)}
// VALID: Pre-process
const activeItems = items.filter(x => x.active)
{activeItems.map(...)}
Props Restrictions
- Function props MUST start with
on - Don’t use
undefinedin state (usenull) - Don’t use
id,class,styleas custom prop names - Set
defaultPropsfor all optional props
Code Style
// USE single quotes
const name = 'John'
<View className='container'>
// DON'T destructure process.env
if (process.env.NODE_ENV === 'development') {}
Null Safety
// Components may render before data loads
// ALWAYS use optional chaining
<Text>{data?.name || 'Loading...'}</Text>
QUICK REFERENCE
Import Template
import Taro, { useLoad, useDidShow } from '@tarojs/taro'
import { View, Text, Image, Button, Input, ScrollView } from '@tarojs/components'
import type { CommonEvent, ITouchEvent } from '@tarojs/components'
Event Template
onInput={(e) => setValue(e.detail.value)}
onClick={(e) => { e.stopPropagation(); action() }}
onConfirm={(e) => submit(e.detail.value)}
Navigation Template
Taro.navigateTo({ url: '/pages/target/index?id=' + id })
Taro.redirectTo({ url: '/pages/target/index' })
Taro.navigateBack()
Taro.switchTab({ url: '/pages/tab/index' })
API Template
const res = await Taro.request({ url, method: 'GET', data })
Taro.showToast({ title: 'Message', icon: 'none' })
Taro.setStorageSync('key', value)