diff --git a/packages/webpack-plugin/lib/index.js b/packages/webpack-plugin/lib/index.js
index cf596e3b94..524b8c17c2 100644
--- a/packages/webpack-plugin/lib/index.js
+++ b/packages/webpack-plugin/lib/index.js
@@ -742,6 +742,7 @@ class MpxWebpackPlugin {
// 若配置disableRequireAsync=true, 则全平台构建不支持异步分包
supportRequireAsync: !this.options.disableRequireAsync && (this.options.mode === 'wx' || this.options.mode === 'ali' || this.options.mode === 'tt' || isWeb(this.options.mode)),
partialCompileRules: this.options.partialCompileRules,
+ useExtendComponents: this.options.useExtendComponents,
collectDynamicEntryInfo: ({ resource, packageName, filename, entryType, hasAsync }) => {
const curInfo = mpx.dynamicEntryInfo[packageName] = mpx.dynamicEntryInfo[packageName] || {
hasPage: false,
diff --git a/packages/webpack-plugin/lib/json-compiler/index.js b/packages/webpack-plugin/lib/json-compiler/index.js
index 755d7863d5..9dfda1a2c4 100644
--- a/packages/webpack-plugin/lib/json-compiler/index.js
+++ b/packages/webpack-plugin/lib/json-compiler/index.js
@@ -12,7 +12,7 @@ const createHelpers = require('../helpers')
const createJSONHelper = require('./helper')
const RecordIndependentDependency = require('../dependencies/RecordIndependentDependency')
const RecordRuntimeInfoDependency = require('../dependencies/RecordRuntimeInfoDependency')
-const { MPX_DISABLE_EXTRACTOR_CACHE, RESOLVE_IGNORED_ERR, JSON_JS_EXT } = require('../utils/const')
+const { MPX_DISABLE_EXTRACTOR_CACHE, RESOLVE_IGNORED_ERR, JSON_JS_EXT, EXTEND_COMPONENTS_LIST } = require('../utils/const')
const resolve = require('../utils/resolve')
const resolveTabBarPath = require('../utils/resolve-tab-bar-path')
const normalize = require('../utils/normalize')
@@ -186,6 +186,21 @@ module.exports = function (content) {
json.usingComponents = json.usingComponents || {}
}
+ if (mode === 'wx' || mode === 'ali') {
+ const { useExtendComponents = {} } = mpx
+ if (isApp && useExtendComponents[mode]) {
+ const extendComponents = {}
+ useExtendComponents[mode].forEach((name) => {
+ if (EXTEND_COMPONENTS_LIST[mode]?.includes(name)) {
+ extendComponents[name] = normalize.lib(`runtime/components/${mode}/mpx-${name}.mpx`)
+ } else {
+ emitWarning(`extend component ${name} is not supported in ${mode} environment!`)
+ }
+ })
+ json.usingComponents = Object.assign({}, extendComponents, json.usingComponents)
+ }
+ }
+
// 快应用补全json配置,必填项
if (mode === 'qa' && isApp) {
const defaultConf = {
diff --git a/packages/webpack-plugin/lib/react/processJSON.js b/packages/webpack-plugin/lib/react/processJSON.js
index bf1f3766f8..bcdbee2321 100644
--- a/packages/webpack-plugin/lib/react/processJSON.js
+++ b/packages/webpack-plugin/lib/react/processJSON.js
@@ -10,7 +10,7 @@ const getJSONContent = require('../utils/get-json-content')
const resolve = require('../utils/resolve')
const createJSONHelper = require('../json-compiler/helper')
const getRulesRunner = require('../platform/index')
-const { RESOLVE_IGNORED_ERR } = require('../utils/const')
+const { RESOLVE_IGNORED_ERR, EXTEND_COMPONENTS_LIST } = require('../utils/const')
const RecordResourceMapDependency = require('../dependencies/RecordResourceMapDependency')
const RecordPageConfigsMapDependency = require('../dependencies/RecordPageConfigsMapDependency')
@@ -31,7 +31,8 @@ module.exports = function (jsonContent, {
mode,
srcMode,
env,
- projectRoot
+ projectRoot,
+ useExtendComponents = {}
} = mpx
const context = loaderContext.context
@@ -101,6 +102,18 @@ module.exports = function (jsonContent, {
if (ctorType !== 'app') {
rulesRunnerOptions.mainKey = ctorType
+ } else {
+ if (useExtendComponents[mode]) {
+ const extendComponents = {}
+ useExtendComponents[mode].forEach((name) => {
+ if (EXTEND_COMPONENTS_LIST[mode]?.includes(name)) {
+ extendComponents[name] = require.resolve(`../runtime/components/react/dist/mpx-${name}.jsx`)
+ } else {
+ emitWarning(`extend component ${name} is not supported in ${mode} environment!`)
+ }
+ })
+ jsonObj.usingComponents = Object.assign({}, extendComponents, jsonObj.usingComponents)
+ }
}
const rulesRunner = getRulesRunner(rulesRunnerOptions)
diff --git a/packages/webpack-plugin/lib/react/processScript.js b/packages/webpack-plugin/lib/react/processScript.js
index 708b869c3a..63ffe2ae76 100644
--- a/packages/webpack-plugin/lib/react/processScript.js
+++ b/packages/webpack-plugin/lib/react/processScript.js
@@ -63,6 +63,7 @@ import { getComponent } from ${stringifyRequest(loaderContext, optionProcessorPa
output += buildGlobalParams({ moduleId, scriptSrcMode, loaderContext, isProduction, ctorType, jsonConfig, componentsMap, outputPath, genericsInfo, componentGenerics })
output += getRequireScript({ ctorType, script, loaderContext })
+
output += `export default global.__mpxOptionsMap[${JSON.stringify(moduleId)}]\n`
}
diff --git a/packages/webpack-plugin/lib/react/script-helper.js b/packages/webpack-plugin/lib/react/script-helper.js
index ed89e991a3..a8668a5475 100644
--- a/packages/webpack-plugin/lib/react/script-helper.js
+++ b/packages/webpack-plugin/lib/react/script-helper.js
@@ -3,7 +3,6 @@ const createHelpers = require('../helpers')
const parseRequest = require('../utils/parse-request')
const shallowStringify = require('../utils/shallow-stringify')
const normalize = require('../utils/normalize')
-
function stringifyRequest (loaderContext, request) {
return loaderUtils.stringifyRequest(loaderContext, request)
}
diff --git a/packages/webpack-plugin/lib/runtime/components/ali/mpx-recycle-view.mpx b/packages/webpack-plugin/lib/runtime/components/ali/mpx-recycle-view.mpx
new file mode 100644
index 0000000000..889a8a8ab8
--- /dev/null
+++ b/packages/webpack-plugin/lib/runtime/components/ali/mpx-recycle-view.mpx
@@ -0,0 +1,519 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/webpack-plugin/lib/runtime/components/ali/mpx-sticky-header.mpx b/packages/webpack-plugin/lib/runtime/components/ali/mpx-sticky-header.mpx
new file mode 100644
index 0000000000..340558669e
--- /dev/null
+++ b/packages/webpack-plugin/lib/runtime/components/ali/mpx-sticky-header.mpx
@@ -0,0 +1,212 @@
+
+
+
+
+
+
diff --git a/packages/webpack-plugin/lib/runtime/components/ali/mpx-sticky-section.mpx b/packages/webpack-plugin/lib/runtime/components/ali/mpx-sticky-section.mpx
new file mode 100644
index 0000000000..e6afee3a03
--- /dev/null
+++ b/packages/webpack-plugin/lib/runtime/components/ali/mpx-sticky-section.mpx
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-recycle-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-recycle-view.tsx
new file mode 100644
index 0000000000..0666112a0d
--- /dev/null
+++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-recycle-view.tsx
@@ -0,0 +1,443 @@
+import React, { forwardRef, useRef, useState, useEffect, useMemo, createElement, useImperativeHandle } from 'react'
+import { SectionList, FlatList, RefreshControl, NativeSyntheticEvent, NativeScrollEvent } from 'react-native'
+import useInnerProps, { getCustomEvent } from './getInnerListeners'
+import { extendObject, useLayout, useTransformStyle } from './utils'
+interface ListItem {
+ isSectionHeader?: boolean;
+ _originalItemIndex?: number;
+ [key: string]: any;
+}
+
+interface Section {
+ headerData: ListItem | null;
+ data: ListItem[];
+ hasSectionHeader?: boolean;
+ _originalItemIndex?: number;
+}
+
+interface ItemHeightType {
+ value?: number;
+ getter?: (item: any, index: number) => number;
+}
+
+interface RecycleViewProps {
+ enhanced?: boolean;
+ bounces?: boolean;
+ scrollEventThrottle?: number;
+ height?: number | string;
+ width?: number | string;
+ listData?: ListItem[];
+ generichash?: string;
+ style?: Record;
+ itemHeight?: ItemHeightType;
+ sectionHeaderHeight?: ItemHeightType;
+ listHeaderData?: any;
+ listHeaderHeight?: ItemHeightType;
+ 'genericrecycle-item'?: string;
+ 'genericsection-header'?: string;
+ 'genericlist-header'?: string;
+ 'enable-var'?: boolean;
+ 'external-var-context'?: any;
+ 'parent-font-size'?: number;
+ 'parent-width'?: number;
+ 'parent-height'?: number;
+ 'enable-sticky'?: boolean;
+ 'enable-back-to-top'?: boolean;
+ 'lower-threshold'?: number;
+ 'refresher-enabled'?: boolean;
+ 'show-scrollbar'?: boolean;
+ 'refresher-triggered'?: boolean;
+ bindrefresherrefresh?: (event: any) => void;
+ bindscrolltolower?: (event: any) => void;
+ bindscroll?: (event: any) => void;
+ [key: string]: any;
+}
+
+interface ScrollPositionParams {
+ index: number;
+ animated?: boolean;
+ viewOffset?: number;
+ viewPosition?: number;
+}
+
+const getGeneric = (generichash: string, generickey: string) => {
+ if (!generichash || !generickey) return null
+ const GenericComponent = global.__mpxGenericsMap[generichash](generickey)
+ if (!GenericComponent) return null
+
+ return forwardRef((props: any, ref: any) => {
+ return createElement(GenericComponent, extendObject({}, {
+ ref: ref
+ }, props))
+ })
+}
+
+const getListHeaderComponent = (generichash: string, generickey: string, data: any) => {
+ if (!generichash || !generickey) return undefined
+ return () => {
+ const ListHeaderComponent = getGeneric(generichash, generickey)
+ return ListHeaderComponent ? createElement(ListHeaderComponent, { listHeaderData: data }) : null
+ }
+}
+
+const getSectionHeaderRenderer = (generichash: string, generickey: string) => {
+ if (!generichash || !generickey) return undefined
+ return (sectionData: { section: Section }) => {
+ if (!sectionData.section.hasSectionHeader) return null
+ const SectionHeaderComponent = getGeneric(generichash, generickey)
+ return SectionHeaderComponent ? createElement(SectionHeaderComponent, { itemData: sectionData.section.headerData }) : null
+ }
+}
+
+const getItemRenderer = (generichash: string, generickey: string) => {
+ if (!generichash || !generickey) return undefined
+ return ({ item }: { item: any }) => {
+ const ItemComponent = getGeneric(generichash, generickey)
+ return ItemComponent ? createElement(ItemComponent, { itemData: item }) : null
+ }
+}
+
+const RecycleView = forwardRef((props = {}, ref) => {
+ const {
+ enhanced = false,
+ bounces = true,
+ scrollEventThrottle = 0,
+ height,
+ width,
+ listData,
+ generichash,
+ style = {},
+ itemHeight = {},
+ sectionHeaderHeight = {},
+ listHeaderHeight = {},
+ listHeaderData = null,
+ 'genericrecycle-item': genericrecycleItem,
+ 'genericsection-header': genericsectionHeader,
+ 'genericlist-header': genericListHeader,
+ 'enable-var': enableVar,
+ 'external-var-context': externalVarContext,
+ 'parent-font-size': parentFontSize,
+ 'parent-width': parentWidth,
+ 'parent-height': parentHeight,
+ 'enable-sticky': enableSticky = false,
+ 'enable-back-to-top': enableBackToTop = false,
+ 'lower-threshold': lowerThreshold = 50,
+ 'refresher-enabled': refresherEnabled,
+ 'show-scrollbar': showScrollbar = true,
+ 'refresher-triggered': refresherTriggered
+ } = props
+
+ const [refreshing, setRefreshing] = useState(!!refresherTriggered)
+
+ const scrollViewRef = useRef(null)
+
+ const indexMap = useRef<{ [key: string]: string | number }>({})
+
+ const reverseIndexMap = useRef<{ [key: string]: number }>({})
+
+ const {
+ hasSelfPercent,
+ setWidth,
+ setHeight
+ } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
+
+ const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef })
+
+ useEffect(() => {
+ if (refreshing !== refresherTriggered) {
+ setRefreshing(!!refresherTriggered)
+ }
+ }, [refresherTriggered])
+
+ const onRefresh = () => {
+ const { bindrefresherrefresh } = props
+ bindrefresherrefresh &&
+ bindrefresherrefresh(
+ getCustomEvent('refresherrefresh', {}, { layoutRef }, props)
+ )
+ }
+
+ const onEndReached = () => {
+ const { bindscrolltolower } = props
+ bindscrolltolower &&
+ bindscrolltolower(
+ getCustomEvent('scrolltolower', {}, { layoutRef }, props)
+ )
+ }
+
+ const onScroll = (event: NativeSyntheticEvent) => {
+ const { bindscroll } = props
+ bindscroll &&
+ bindscroll(
+ getCustomEvent('scroll', event.nativeEvent, { layoutRef }, props)
+ )
+ }
+
+ // 通过sectionIndex和rowIndex获取原始索引
+ const getOriginalIndex = (sectionIndex: number, rowIndex: number | 'header'): number => {
+ const key = `${sectionIndex}_${rowIndex}`
+ return reverseIndexMap.current[key] ?? -1 // 如果找不到,返回-1
+ }
+
+ const scrollToIndex = ({ index, animated, viewOffset = 0, viewPosition = 0 }: ScrollPositionParams) => {
+ if (scrollViewRef.current) {
+ if (enableSticky) {
+ // 通过索引映射表快速定位位置
+ const position = indexMap.current[index]
+ const [sectionIndex, itemIndex] = (position as string).split('_')
+ scrollViewRef.current.scrollToLocation?.({
+ itemIndex: itemIndex === 'header' ? 0 : Number(itemIndex) + 1,
+ sectionIndex: Number(sectionIndex) || 0,
+ animated,
+ viewOffset,
+ viewPosition
+ })
+ } else {
+ scrollViewRef.current.scrollToIndex?.({ index, animated, viewOffset, viewPosition })
+ }
+ }
+ }
+
+ const getItemHeight = ({ sectionIndex, rowIndex }: { sectionIndex: number, rowIndex: number }) => {
+ if (!itemHeight) {
+ return 0
+ }
+ if ((itemHeight as ItemHeightType).getter) {
+ if (enableSticky) {
+ const item = convertedListData[sectionIndex].data[rowIndex]
+ // 使用getOriginalIndex获取原始索引
+ const originalIndex = getOriginalIndex(sectionIndex, rowIndex)
+ return (itemHeight as ItemHeightType).getter?.(item, originalIndex) || 0
+ } else {
+ const item = convertedListData[rowIndex]
+ return (itemHeight as ItemHeightType).getter?.(item, rowIndex) || 0
+ }
+ } else {
+ return (itemHeight as ItemHeightType).value || 0
+ }
+ }
+
+ const getSectionHeaderHeight = ({ sectionIndex }: { sectionIndex: number }) => {
+ const item = convertedListData[sectionIndex]
+ const { hasSectionHeader } = item
+ // 使用getOriginalIndex获取原始索引
+ const originalIndex = getOriginalIndex(sectionIndex, 'header')
+ if (!hasSectionHeader) return 0
+ if ((sectionHeaderHeight as ItemHeightType).getter) {
+ return (sectionHeaderHeight as ItemHeightType).getter?.(item, originalIndex) || 0
+ } else {
+ return (sectionHeaderHeight as ItemHeightType).value || 0
+ }
+ }
+
+ const convertedListData = useMemo(() => {
+ if (enableSticky) {
+ const sections: Section[] = []
+ let currentSection: Section | null = null
+ // 清空之前的索引映射
+ indexMap.current = {}
+ // 清空反向索引映射
+ reverseIndexMap.current = {}
+ listData.forEach((item: ListItem, index: number) => {
+ if (item.isSectionHeader) {
+ // 如果已经存在一个 section,先把它添加到 sections 中
+ if (currentSection) {
+ sections.push(currentSection)
+ }
+ // 创建新的 section
+ currentSection = {
+ headerData: item,
+ data: [],
+ hasSectionHeader: true,
+ _originalItemIndex: index
+ }
+ // 为 section header 添加索引映射
+ const sectionIndex = sections.length
+ indexMap.current[index] = `${sectionIndex}_header`
+ // 添加反向索引映射
+ reverseIndexMap.current[`${sectionIndex}_header`] = index
+ } else {
+ // 如果没有当前 section,创建一个默认的
+ if (!currentSection) {
+ // 创建默认section (无header的section)
+ currentSection = {
+ headerData: null,
+ data: [],
+ hasSectionHeader: false,
+ _originalItemIndex: -1
+ }
+ }
+ // 将 item 添加到当前 section 的 data 中
+ const itemIndex = currentSection.data.length
+ currentSection.data.push(extendObject({}, item, {
+ _originalItemIndex: index
+ }))
+ let sectionIndex
+ // 为 item 添加索引映射 - 存储格式为: "sectionIndex_itemIndex"
+ if (!currentSection.hasSectionHeader && sections.length === 0) {
+ // 在默认section中(第一个且无header)
+ sectionIndex = 0
+ indexMap.current[index] = `${sectionIndex}_${itemIndex}`
+ } else {
+ // 在普通section中
+ sectionIndex = sections.length
+ indexMap.current[index] = `${sectionIndex}_${itemIndex}`
+ }
+ // 添加反向索引映射
+ reverseIndexMap.current[`${sectionIndex}_${itemIndex}`] = index
+ }
+ })
+ // 添加最后一个 section
+ if (currentSection) {
+ sections.push(currentSection)
+ }
+ return sections
+ } else {
+ return listData
+ }
+ }, [listData])
+
+ const { getItemLayout } = useMemo(() => {
+ const layouts: Array<{ length: number, offset: number, index: number }> = []
+ let offset = 0
+
+ if (generichash && genericListHeader) {
+ // 计算列表头部的高度
+ offset += listHeaderHeight.getter?.() || listHeaderHeight.value || 0
+ }
+
+ if (enableSticky) {
+ // 遍历所有 sections
+ convertedListData.forEach((section: Section, sectionIndex: number) => {
+ // 添加 section header 的位置信息
+ const headerHeight = getSectionHeaderHeight({ sectionIndex })
+ layouts.push({
+ length: headerHeight,
+ offset,
+ index: layouts.length
+ })
+ offset += headerHeight
+
+ // 添加该 section 中所有 items 的位置信息
+ section.data.forEach((item: ListItem, itemIndex: number) => {
+ const contenteight = getItemHeight({ sectionIndex, rowIndex: itemIndex })
+ layouts.push({
+ length: contenteight,
+ offset,
+ index: layouts.length
+ })
+ offset += contenteight
+ })
+
+ // 添加该 section 尾部位置信息
+ // 因为即使 sectionList 没传 renderSectionFooter,getItemLayout 中的 index 的计算也会包含尾部节点
+ layouts.push({
+ length: 0,
+ offset,
+ index: layouts.length
+ })
+ })
+ } else {
+ convertedListData.forEach((item: ListItem, index: number) => {
+ const itemHeightValue = getItemHeight({ sectionIndex: 0, rowIndex: index })
+ layouts.push({
+ length: itemHeightValue,
+ offset,
+ index: layouts.length
+ })
+ offset += itemHeightValue
+ })
+ }
+ return {
+ itemLayouts: layouts,
+ getItemLayout: (data: any, index: number) => layouts[index]
+ }
+ }, [convertedListData])
+
+ const scrollAdditionalProps = extendObject(
+ {
+ alwaysBounceVertical: false,
+ alwaysBounceHorizontal: false,
+ scrollEventThrottle: scrollEventThrottle,
+ scrollsToTop: enableBackToTop,
+ showsHorizontalScrollIndicator: showScrollbar,
+ onEndReachedThreshold: lowerThreshold,
+ ref: scrollViewRef,
+ bounces: false,
+ stickySectionHeadersEnabled: enableSticky,
+ onScroll: onScroll,
+ onEndReached: onEndReached
+ },
+ layoutProps
+ )
+
+ if (enhanced) {
+ Object.assign(scrollAdditionalProps, {
+ bounces
+ })
+ }
+ if (refresherEnabled) {
+ Object.assign(scrollAdditionalProps, {
+ refreshing: refreshing
+ })
+ }
+
+ useImperativeHandle(ref, () => {
+ return {
+ ...props,
+ scrollToIndex
+ }
+ })
+
+ const innerProps = useInnerProps(extendObject({}, props, scrollAdditionalProps), [
+ 'id',
+ 'show-scrollbar',
+ 'lower-threshold',
+ 'refresher-triggered',
+ 'refresher-enabled',
+ 'bindrefresherrefresh'
+ ], { layoutRef })
+
+ return enableSticky
+ ? createElement(
+ SectionList,
+ extendObject(
+ {
+ style: [{ height, width }, style, layoutStyle],
+ sections: convertedListData,
+ renderItem: getItemRenderer(generichash, genericrecycleItem),
+ getItemLayout: getItemLayout,
+ ListHeaderComponent: getListHeaderComponent(generichash, genericListHeader, listHeaderData),
+ renderSectionHeader: getSectionHeaderRenderer(generichash, genericsectionHeader),
+ refreshControl: refresherEnabled
+ ? React.createElement(RefreshControl, {
+ onRefresh: onRefresh,
+ refreshing: refreshing
+ })
+ : undefined
+ },
+ innerProps
+ )
+ )
+ : createElement(
+ FlatList,
+ extendObject(
+ {
+ style: [{ height, width }, style, layoutStyle],
+ data: convertedListData,
+ renderItem: getItemRenderer(generichash, genericrecycleItem),
+ getItemLayout: getItemLayout,
+ ListHeaderComponent: getListHeaderComponent(generichash, genericListHeader, listHeaderData),
+ refreshControl: refresherEnabled
+ ? React.createElement(RefreshControl, {
+ onRefresh: onRefresh,
+ refreshing: refreshing
+ })
+ : undefined
+ },
+ innerProps
+ )
+ )
+})
+
+export default RecycleView
diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx
index d8940a1cef..8099705b7b 100644
--- a/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx
+++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-scroll-view.tsx
@@ -239,6 +239,7 @@ const _ScrollView = forwardRef, S
// layout 完成前先隐藏,避免安卓闪烁问题
const refresherLayoutStyle = useMemo(() => { return !hasRefresherLayoutRef.current ? HIDDEN_STYLE : {} }, [hasRefresherLayoutRef.current])
+
const lastOffset = useRef(0)
if (scrollX && scrollY) {
diff --git a/packages/webpack-plugin/lib/runtime/components/react/mpx-sticky-section.tsx b/packages/webpack-plugin/lib/runtime/components/react/mpx-sticky-section.tsx
index 63175e0099..b95aefb177 100644
--- a/packages/webpack-plugin/lib/runtime/components/react/mpx-sticky-section.tsx
+++ b/packages/webpack-plugin/lib/runtime/components/react/mpx-sticky-section.tsx
@@ -1,9 +1,9 @@
-
import { useRef, forwardRef, createElement, ReactNode, useCallback, useMemo } from 'react'
import { View, ViewStyle } from 'react-native'
import useNodesRef, { HandlerRef } from './useNodesRef'
import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils'
import { StickyContext } from './context'
+
import useInnerProps from './getInnerListeners'
interface StickySectionProps {
diff --git a/packages/webpack-plugin/lib/runtime/components/wx/mpx-recycle-view.mpx b/packages/webpack-plugin/lib/runtime/components/wx/mpx-recycle-view.mpx
new file mode 100644
index 0000000000..1de3bb8623
--- /dev/null
+++ b/packages/webpack-plugin/lib/runtime/components/wx/mpx-recycle-view.mpx
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/webpack-plugin/lib/template-compiler/compiler.js b/packages/webpack-plugin/lib/template-compiler/compiler.js
index 4efdc605d3..d56afbc702 100644
--- a/packages/webpack-plugin/lib/template-compiler/compiler.js
+++ b/packages/webpack-plugin/lib/template-compiler/compiler.js
@@ -1,7 +1,7 @@
const JSON5 = require('json5')
const he = require('he')
const config = require('../config')
-const { MPX_ROOT_VIEW, MPX_APP_MODULE_ID, PARENT_MODULE_ID } = require('../utils/const')
+const { MPX_ROOT_VIEW, MPX_APP_MODULE_ID, PARENT_MODULE_ID, EXTEND_COMPONENTS_LIST } = require('../utils/const')
const normalize = require('../utils/normalize')
const { normalizeCondition } = require('../utils/match-condition')
const isValidIdentifierStr = require('../utils/is-valid-identifier-str')
@@ -724,13 +724,21 @@ function parse (template, options) {
processElement(element, root, options, meta)
tagNames.add(element.tag)
+
+ const genericAttrs = []
// 统计通过抽象节点方式使用的组件
element.attrsList.forEach((attr) => {
if (genericRE.test(attr.name)) {
tagNames.add(attr.value)
+ if (mode === 'wx' || mode === 'ali') {
+ genericAttrs.push({
+ name: attr.name.replace('generic:', 'mpx-generic-'),
+ value: attr.value
+ })
+ }
}
})
-
+ element.attrsList = element.attrsList.concat(genericAttrs)
if (!unary) {
currentParent = element
stack.push(element)
@@ -2260,7 +2268,11 @@ function isRealNode (el) {
}
function isComponentNode (el) {
- return usingComponents.indexOf(el.tag) !== -1 || el.tag === 'component' || componentGenerics[el.tag]
+ return usingComponents.indexOf(el.tag) !== -1 || el.tag === 'component' || componentGenerics[el.tag] || isExtendComponentNode(el)
+}
+
+function isExtendComponentNode (el) {
+ return EXTEND_COMPONENTS_LIST[mode]?.includes(el.tag)
}
function getComponentInfo (el) {
@@ -2268,7 +2280,7 @@ function getComponentInfo (el) {
}
function isReactComponent (el) {
- return !isComponentNode(el) && isRealNode(el) && !el.isBuiltIn
+ return !isComponentNode(el) && isRealNode(el) && !el.isBuiltIn && !isExtendComponentNode(el)
}
function processExternalClasses (el, options) {
diff --git a/packages/webpack-plugin/lib/utils/const.js b/packages/webpack-plugin/lib/utils/const.js
index 88e22096b7..9a1eeabed6 100644
--- a/packages/webpack-plugin/lib/utils/const.js
+++ b/packages/webpack-plugin/lib/utils/const.js
@@ -6,5 +6,13 @@ module.exports = {
JSON_JS_EXT: '.json.js',
MPX_ROOT_VIEW: 'mpx-root-view', // 根节点类名
MPX_APP_MODULE_ID: 'mpx-app-scope', // app文件moduleId
- PARENT_MODULE_ID: '__pid'
+ PARENT_MODULE_ID: '__pid',
+ EXTEND_COMPONENTS_LIST: {
+ wx: ['recycle-view'],
+ ali: ['recycle-view', 'sticky-header', 'sticky-section'],
+ web: ['recycle-view'],
+ ios: ['recycle-view'],
+ android: ['recycle-view'],
+ harmony: ['recycle-view']
+ } // 扩展组件列表
}
diff --git a/packages/webpack-plugin/lib/web/processJSON.js b/packages/webpack-plugin/lib/web/processJSON.js
index a2a110db1b..4a503b20a8 100644
--- a/packages/webpack-plugin/lib/web/processJSON.js
+++ b/packages/webpack-plugin/lib/web/processJSON.js
@@ -30,7 +30,8 @@ module.exports = function (jsonContent, {
mode,
srcMode,
env,
- projectRoot
+ projectRoot,
+ useExtendComponents = {}
} = mpx
const context = loaderContext.context
@@ -100,6 +101,14 @@ module.exports = function (jsonContent, {
if (ctorType !== 'app') {
rulesRunnerOptions.mainKey = ctorType
+ } else {
+ if (useExtendComponents[mode]) {
+ useExtendComponents[mode].forEach((name) => {
+ emitWarning(
+ `extend component ${name} is not supported in web environment!`
+ )
+ })
+ }
}
const rulesRunner = getRulesRunner(rulesRunnerOptions)