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)