Skip to content

Commit b9f374c

Browse files
committed
merge feat-transion-25.05.10
2 parents c983123 + 7fd09a4 commit b9f374c

File tree

17 files changed

+607
-35
lines changed

17 files changed

+607
-35
lines changed

analyze_git_stats.py

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import subprocess
2+
import sys
3+
import argparse
4+
from collections import defaultdict
5+
from datetime import datetime
6+
7+
def analyze_git_stats():
8+
# Parse command line arguments
9+
parser = argparse.ArgumentParser(description='Analyze git contribution stats.')
10+
parser.add_argument('--since', type=str, default="1 year ago",
11+
help='Start time period to analyze (e.g. "1 year ago", "2023-01-01")')
12+
parser.add_argument('--until', type=str, default=None,
13+
help='End time period to analyze (e.g. "now", "1 month ago", "2024-01-01"). Defaults to now.')
14+
args = parser.parse_args()
15+
16+
# Configuration
17+
since_date = args.since
18+
until_date = args.until
19+
20+
# Author Alias Mapping
21+
# Format: 'Alias Name': 'Canonical Name'
22+
AUTHOR_MAPPINGS = {
23+
'wangshunnn': 'Soon Wang',
24+
'mackwang112': 'mackwang',
25+
'wangxiaokou': 'wangcuijuan',
26+
'dongxingxingdong': 'WX-DongXing',
27+
'yandadaFreedom': 'lareinayanyu'
28+
# Add more mappings here as needed
29+
}
30+
31+
# Using 'git log' to get the data
32+
# --numstat: shows added/deleted lines
33+
# --no-merges: optional, generally we want to count actual code contributions, not merge commits
34+
# --pretty=format:"AUTHOR:%aN": helps us identify who made the commit
35+
cmd = [
36+
"git", "log",
37+
f"--since={since_date}",
38+
"--numstat",
39+
"--pretty=format:AUTHOR:%aN",
40+
"--no-merges"
41+
]
42+
43+
if until_date:
44+
cmd.append(f"--until={until_date}")
45+
46+
try:
47+
# Run the command with utf-8 encoding errors ignored to prevent crashing on binary filenames or weird author names
48+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, errors='replace')
49+
stdout, stderr = process.communicate()
50+
51+
if process.returncode != 0:
52+
print(f"Error executing git command: {stderr}")
53+
return
54+
55+
except FileNotFoundError:
56+
print("Error: 'git' command not found. Please ensure git is installed.")
57+
return
58+
59+
# Data structure:
60+
# stats = {
61+
# author: {
62+
# 'total': {'added': 0, 'deleted': 0, 'commits': 0},
63+
# 'lt_1000': {'added': 0, 'deleted': 0, 'commits': 0},
64+
# 'lt_5000': {'added': 0, 'deleted': 0, 'commits': 0}
65+
# }
66+
# }
67+
stats = defaultdict(lambda: {
68+
'total': {'added': 0, 'deleted': 0, 'commits': 0},
69+
'lt_1000': {'added': 0, 'deleted': 0, 'commits': 0},
70+
'lt_5000': {'added': 0, 'deleted': 0, 'commits': 0}
71+
})
72+
73+
lines = stdout.split('\n')
74+
75+
pending_author = None
76+
pending_commit_stats = {'added': 0, 'deleted': 0}
77+
78+
def finalize_commit(author, commit_stats):
79+
if not author:
80+
return
81+
82+
added = commit_stats['added']
83+
deleted = commit_stats['deleted']
84+
total_change = added + deleted
85+
86+
# Add to Total
87+
stats[author]['total']['added'] += added
88+
stats[author]['total']['deleted'] += deleted
89+
stats[author]['total']['commits'] += 1
90+
91+
# Add to < 5000
92+
if total_change < 5000:
93+
stats[author]['lt_5000']['added'] += added
94+
stats[author]['lt_5000']['deleted'] += deleted
95+
stats[author]['lt_5000']['commits'] += 1
96+
97+
# Add to < 1000
98+
if total_change < 1000:
99+
stats[author]['lt_1000']['added'] += added
100+
stats[author]['lt_1000']['deleted'] += deleted
101+
stats[author]['lt_1000']['commits'] += 1
102+
103+
for line in lines:
104+
line = line.strip()
105+
if not line:
106+
continue
107+
108+
if line.startswith("AUTHOR:"):
109+
# Finish previous commit
110+
finalize_commit(pending_author, pending_commit_stats)
111+
112+
# Start new commit
113+
raw_author = line.split("AUTHOR:", 1)[1].strip()
114+
pending_author = AUTHOR_MAPPINGS.get(raw_author, raw_author)
115+
pending_commit_stats = {'added': 0, 'deleted': 0}
116+
else:
117+
# It's a numstat line: "added deleted filepath"
118+
parts = line.split(maxsplit=2)
119+
if len(parts) == 3:
120+
added, deleted, _ = parts
121+
122+
# Handle binary files or other non-numeric entries
123+
if added == '-' or deleted == '-':
124+
continue
125+
126+
try:
127+
pending_commit_stats['added'] += int(added)
128+
pending_commit_stats['deleted'] += int(deleted)
129+
except ValueError:
130+
continue
131+
132+
# Finalize the last commit
133+
finalize_commit(pending_author, pending_commit_stats)
134+
135+
# Output formatting
136+
def print_table(title, key_type):
137+
print(f"\n{title}")
138+
print(f"{'Author':<30} | {'Added':<10} | {'Deleted':<10} | {'Total Lines':<12} | {'Commits':<8}")
139+
print("-" * 80)
140+
141+
# Convert to list for sorting
142+
results = []
143+
for author, data in stats.items():
144+
category_data = data[key_type]
145+
total_changed = category_data['added'] + category_data['deleted']
146+
# Only include if they have commits in this category
147+
if category_data['commits'] > 0:
148+
results.append({
149+
'author': author,
150+
'added': category_data['added'],
151+
'deleted': category_data['deleted'],
152+
'total': total_changed,
153+
'commits': category_data['commits']
154+
})
155+
156+
# Sort by total lines changed (descending)
157+
results.sort(key=lambda x: x['total'], reverse=True)
158+
159+
total_added_all = 0
160+
total_deleted_all = 0
161+
total_commits_all = 0
162+
163+
for r in results:
164+
print(f"{r['author']:<30} | {r['added']:<10} | {r['deleted']:<10} | {r['total']:<12} | {r['commits']:<8}")
165+
total_added_all += r['added']
166+
total_deleted_all += r['deleted']
167+
total_commits_all += r['commits']
168+
169+
print("-" * 80)
170+
print(f"{'TOTAL':<30} | {total_added_all:<10} | {total_deleted_all:<10} | {total_added_all+total_deleted_all:<12} | {total_commits_all:<8}")
171+
172+
print_table("=== ALL COMMITS ===", 'total')
173+
print_table("=== COMMITS < 5000 LINES ===", 'lt_5000')
174+
print_table("=== COMMITS < 1000 LINES ===", 'lt_1000')
175+
176+
if __name__ == "__main__":
177+
analyze_git_stats()

docs-vitepress/guide/rn/style.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
RN 样式属性和 Web/小程序中 CSS 样式属性是相交关系:
1717

1818
- **RN 独有属性**`tintColor``writingDirection` 等,CSS 不支持
19-
- **CSS 独有属性**`clip-path``animation``transition` 等,RN 不支持
19+
- **CSS 独有属性**`clip-path``animation` 等,RN 不支持
2020

2121
因此,在跨平台开发时:
2222
1. **优先使用交集属性**:尽量使用两边都支持的样式属性

packages/api-proxy/src/platform/api/route/index.ios.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,10 @@ function navigateBack (options = {}) {
104104
}
105105
if (delta >= routeLength && global.__mpx?.config.rnConfig.onAppBack?.(delta - routeLength + 1)) {
106106
nextTick(() => {
107-
navigationHelper.lastSuccessCallback()
108-
navigationHelper.lastSuccessCallback = null
107+
if (navigationHelper.lastSuccessCallback) {
108+
navigationHelper.lastSuccessCallback()
109+
navigationHelper.lastSuccessCallback = null
110+
}
109111
})
110112
} else {
111113
navigation.pop(delta)

packages/core/src/convertor/wxToWeb.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
diffAndCloneA,
77
error,
88
hasOwn,
9-
isDev
9+
isDev,
10+
getDefaultValueByType
1011
} from '@mpxjs/utils'
1112
import { implemented } from '../core/implement'
1213

@@ -56,6 +57,12 @@ export default {
5657
return diffAndCloneA(prop.value).clone
5758
}
5859
: prop.value
60+
} else {
61+
// 没有显式设置value时,根据type自动添加默认值,与微信小程序原生行为保持一致
62+
const defaultValue = getDefaultValueByType(prop.type, 'web')
63+
if (defaultValue !== undefined) {
64+
newProp.default = defaultValue
65+
}
5966
}
6067
props[key] = newProp
6168
} else {

packages/core/src/helper/MpxScroll/index.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ export default class MpxScroll {
5757
const isIntersecting = change.isIntersecting
5858
this.isIntersecting = isIntersecting
5959
if (!isIntersecting) {
60-
// 非 inter section 状态下及时清除 transtorm,以免影响正常滚动时元素的 fixed 定位
61-
this.el.style.cssText = ''
60+
// 非 inter section 状态下及时清除 transform,以免影响正常滚动时元素的 fixed 定位
61+
this.el.style.transform = ''
6262
this.pullDownEventRegister && this.pullDownEventRegister.destroy()
6363
} else {
6464
this.pullDownEventRegister = new EventRegister(this.el, [
@@ -103,7 +103,12 @@ export default class MpxScroll {
103103

104104
transformPage (distance) {
105105
this.translateY = distance
106-
this.el.style.cssText = `transform: translateY(${distance}px)`
106+
if (distance === 0) {
107+
// 距离为 0 时移除 transform,避免影响页面 fixed 定位
108+
this.el.style.transform = ''
109+
} else {
110+
this.el.style.transform = `translateY(${distance}px)`
111+
}
107112
}
108113

109114
onTouchEnd (e) {
@@ -219,8 +224,15 @@ export default class MpxScroll {
219224
}
220225

221226
onReachBottom (onReachBottomDistance, callback) {
222-
const { bottom } = this.el.getBoundingClientRect()
223-
const mark = bottom - window.innerHeight <= onReachBottomDistance
227+
const scrollTop = getScrollTop()
228+
const scrollHeight = document.documentElement.scrollHeight
229+
const clientHeight = window.innerHeight
230+
231+
// 使用 scrollHeight 判断实际内容高度是否超过视口,只有可滚动时才计算触底
232+
const scrollable = scrollHeight > clientHeight
233+
// 距离底部的距离 = 内容总高度 - (当前滚动位置 + 视口高度)
234+
const distanceToBottom = scrollHeight - (scrollTop + clientHeight)
235+
const mark = scrollable && (distanceToBottom <= onReachBottomDistance)
224236

225237
if (!this.bottomReached && mark) {
226238
this.bottomReached = true

packages/core/src/platform/patch/getDefaultOptions.ios.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as ReactNative from 'react-native'
33
import { ReactiveEffect } from '../../observer/effect'
44
import { watch } from '../../observer/watch'
55
import { del, reactive, set } from '../../observer/reactive'
6-
import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, error, setFocusedNavigation } from '@mpxjs/utils'
6+
import { hasOwn, isFunction, noop, isObject, isArray, getByPath, collectDataset, hump2dash, dash2hump, callWithErrorHandling, wrapMethodsWithErrorHandling, error, setFocusedNavigation, getDefaultValueByType } from '@mpxjs/utils'
77
import MpxProxy from '../../core/proxy'
88
import { BEFOREUPDATE, ONLOAD, UPDATED, ONSHOW, ONHIDE, ONRESIZE, REACTHOOKSEXEC } from '../../core/innerLifecycle'
99
import mergeOptions from '../../core/mergeOptions'
@@ -172,8 +172,8 @@ const instanceProto = {
172172
type: field
173173
}
174174
}
175-
// 处理props默认值
176-
propsData[key] = field.value
175+
// 处理props默认值,没有显式设置value时根据type获取默认值,与微信小程序原生行为保持一致
176+
propsData[key] = hasOwn(field, 'value') ? field.value : getDefaultValueByType(field.type)
177177
}
178178
}
179179
})
@@ -315,7 +315,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
315315
if (type === 'page') {
316316
const props = propsRef.current
317317
const decodedQuery = {}
318-
const rawQuery = props.route.params
318+
const rawQuery = props.route.params || {}
319319
if (isObject(rawQuery)) {
320320
for (const key in rawQuery) {
321321
decodedQuery[key] = decodeURIComponent(rawQuery[key])

packages/utils/src/base.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,24 @@ function hasChanged (value, oldValue) {
135135
return !Object.is(value, oldValue)
136136
}
137137

138+
// 根据类型获取小程序默认值,与微信小程序原生行为保持一致
139+
function getDefaultValueByType (type, mode) {
140+
switch (type) {
141+
case String:
142+
return ''
143+
case Number:
144+
return 0
145+
case Boolean:
146+
return false
147+
case Object:
148+
return null
149+
case Array:
150+
return mode === 'web' ? () => [] : []
151+
default:
152+
return undefined
153+
}
154+
}
155+
138156
export {
139157
hasProto,
140158
noop,
@@ -156,5 +174,6 @@ export {
156174
def,
157175
hasChanged,
158176
forEach,
159-
cached
177+
cached,
178+
getDefaultValueByType
160179
}

packages/webpack-plugin/lib/platform/style/wx/index.js

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module.exports = function getSpec ({ warn, error }) {
3939
}
4040
// React 属性支持的枚举值
4141
const SUPPORTED_PROP_VAL_ARR = {
42-
'box-sizing': ['border-box'],
42+
'box-sizing': ['border-box', 'content-box'],
4343
'backface-visibility': ['visible', 'hidden'],
4444
overflow: ['visible', 'hidden', 'scroll'],
4545
'border-style': ['solid', 'dotted', 'dashed'],
@@ -89,20 +89,48 @@ module.exports = function getSpec ({ warn, error }) {
8989
if (rule[1].test(prop)) return rule[0]
9090
}
9191
}
92-
// const getDefaultValueFromVar = (str) => {
93-
// const totalVarExp = /^var\((.+)\)$/
94-
// if (!totalVarExp.test(str)) return str
95-
// const newVal = parseValues((str.match(totalVarExp)?.[1] || ''), ',')
96-
// if (newVal.length <= 1) return ''
97-
// if (!totalVarExp.test(newVal[1])) return newVal[1]
98-
// return getDefaultValueFromVar(newVal[1])
99-
// }
100-
// 属性值校验
92+
93+
// 从 CSS 变量中提取 fallback 值进行验证
94+
// 返回值:fallback 值 | null(没有 fallback)| undefined(循环引用)
95+
const getDefaultValueFromVar = (str, visited = new Set()) => {
96+
const totalVarExp = /^var\((.+)\)$/
97+
if (!totalVarExp.test(str)) return str
98+
99+
// 防止循环引用 - 返回 undefined 表示检测到循环
100+
if (visited.has(str)) return undefined
101+
visited.add(str)
102+
103+
const newVal = parseValues((str.match(totalVarExp)?.[1] || ''), ',')
104+
if (newVal.length <= 1) return null // 没有 fallback
105+
const fallback = newVal[1].trim()
106+
// 如果 fallback 也是 var(),递归提取
107+
if (totalVarExp.test(fallback)) return getDefaultValueFromVar(fallback, visited)
108+
return fallback
109+
}
110+
101111
const verifyValues = ({ prop, value, selector }, isError = true) => {
102112
prop = prop.trim()
103113
value = value.trim()
104114
const tips = isError ? error : warn
105-
if (cssVariableExp.test(value) || calcExp.test(value) || envExp.test(value)) return true
115+
116+
// 对于包含 CSS 变量的值,提取 fallback 值进行验证
117+
if (cssVariableExp.test(value)) {
118+
const fallback = getDefaultValueFromVar(value)
119+
// undefined 表示检测到循环引用
120+
if (fallback === undefined) {
121+
tips(`CSS variable circular reference in fallback chain detected in ${selector} for property ${prop}, value: ${value}`)
122+
return false
123+
}
124+
// null 表示没有 fallback,CSS 变量本身是合法的(运行时会解析)
125+
if (fallback === null) {
126+
return true
127+
}
128+
// 有 fallback 值,将 fallback 作为新的 value 继续后续验证流程
129+
value = fallback
130+
}
131+
132+
// calc() 和 env() 跳过验证
133+
if (calcExp.test(value) || envExp.test(value)) return true
106134
const namedColor = ['transparent', 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'grey', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
107135
const valueExp = {
108136
number: /^((-?(\d+(\.\d+)?|\.\d+))(rpx|px|%|vw|vh)?|hairlineWidth)$/,

0 commit comments

Comments
 (0)