Skip to content

Commit bba085e

Browse files
fix(babel-preset-taro): list组件转换避免导入命名冲突 (#18750)
1 parent 032fbed commit bba085e

2 files changed

Lines changed: 104 additions & 82 deletions

File tree

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,9 @@ artifacts
8484

8585
# Node Addons
8686
*.node
87-
chrome
87+
chrome
88+
89+
# ai
90+
.claude
91+
.trae
92+
.kiro

packages/babel-preset-taro/transform-taro-components.js

Lines changed: 98 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,23 @@ const LIST_BUILDER_PROPS = ['padding', 'type', 'list', 'childCount', 'childHeigh
3333

3434
module.exports = declare((api) => {
3535
api.assertVersion(7)
36-
const componentImports = new Map()
36+
function hasTargetTaroComponent(state, target) {
37+
return state.taroComponentImports.has(target)
38+
}
3739

38-
function hasTargetTaroComponent(target) {
39-
return componentImports.has(target) && componentImports.get(target).source === TARO_COMPONENTS
40+
function getComponentLocalName(state, name, scope) {
41+
if (state.taroComponentImports.has(name)) {
42+
return state.taroComponentImports.get(name).localName
43+
}
44+
if (state.generatedImports.has(name)) {
45+
return state.generatedImports.get(name).localName
46+
}
47+
const programScope = scope.getProgramParent()
48+
// 始终生成唯一 ID,避免与用户代码或外部包导入的组件本地名冲突
49+
const newId = programScope.generateUidIdentifier(name)
50+
const record = { localName: newId.name, source: TARO_COMPONENTS }
51+
state.generatedImports.set(name, record)
52+
return record.localName
4053
}
4154

4255
function pickAttrs(attrs, props) {
@@ -51,78 +64,76 @@ module.exports = declare((api) => {
5164
name: 'plugin:transform-taro-components',
5265
visitor: {
5366
Program: {
54-
exit(path) {
55-
if ([COMPONENT_LIST, COMPONENT_LIST_ITEM].some((component) => componentImports.has(component))) {
56-
const taroComponentsImportDeclIndex = path.node.body.findIndex((node) => {
57-
return (
58-
api.types.isImportDeclaration(node) &&
59-
api.types.isStringLiteral(node.source) &&
60-
node.source.value === TARO_COMPONENTS
61-
)
67+
enter(_path, state) {
68+
// 每个文件单独维护状态,避免跨文件污染
69+
state.taroComponentImports = new Map()
70+
state.generatedImports = new Map()
71+
},
72+
exit(path, state) {
73+
const taroComponentImports = state.taroComponentImports
74+
if ([COMPONENT_LIST, COMPONENT_LIST_ITEM].some((component) => taroComponentImports.has(component))) {
75+
const collectedSpecifiers = []
76+
const remainingBody = []
77+
78+
path.node.body.forEach((node) => {
79+
if (api.types.isImportDeclaration(node) && api.types.isStringLiteral(node.source)) {
80+
if (node.source.value === TARO_COMPONENTS) {
81+
collectedSpecifiers.push(...node.specifiers.filter((specifier) => api.types.isImportSpecifier(specifier)))
82+
return
83+
}
84+
}
85+
remainingBody.push(node)
6286
})
63-
if (taroComponentsImportDeclIndex === -1) {
64-
return
65-
}
66-
const taroComponentsImportDecl = path.node.body[taroComponentsImportDeclIndex]
67-
path.node.body.splice(taroComponentsImportDeclIndex, 1)
87+
6888
// 重新生成 @tarojs/components 导入声明并插入到路径的开头,排除掉 List、ListItem,添加 ScrollView、ListBuilder、View 到 @tarojs/components 导入声明中
69-
if (taroComponentsImportDecl) {
70-
const { specifiers } = taroComponentsImportDecl
71-
// 排除掉 List、ListItem
72-
const newSpecifiers = specifiers.filter((specifier) => {
73-
return !(
74-
api.types.isImportSpecifier(specifier) &&
75-
(specifier.imported?.name === COMPONENT_LIST || specifier.imported?.name === COMPONENT_LIST_ITEM)
76-
)
77-
})
78-
79-
if (componentImports.has(COMPONENT_LIST)) {
80-
const scrollViewLocalName = componentImports.has(COMPONENT_SCROLL_VIEW)
81-
? componentImports.get(COMPONENT_SCROLL_VIEW).localName
82-
: COMPONENT_SCROLL_VIEW
83-
84-
const listBuilderLocalName = componentImports.has(COMPONENT_LIST_BUILDER)
85-
? componentImports.get(COMPONENT_LIST_BUILDER).localName
86-
: COMPONENT_LIST_BUILDER
87-
88-
if (!newSpecifiers.some((specifier) => specifier.imported?.name === scrollViewLocalName)) {
89-
newSpecifiers.push(
90-
api.types.importSpecifier(
91-
api.types.identifier(scrollViewLocalName),
92-
api.types.identifier(scrollViewLocalName)
93-
)
94-
)
95-
}
89+
const baseSpecifiers = collectedSpecifiers.filter((specifier) => {
90+
return !(
91+
api.types.isImportSpecifier(specifier) &&
92+
(specifier.imported?.name === COMPONENT_LIST || specifier.imported?.name === COMPONENT_LIST_ITEM)
93+
)
94+
})
9695

97-
if (!newSpecifiers.some((specifier) => specifier.imported?.name === listBuilderLocalName)) {
98-
newSpecifiers.push(
99-
api.types.importSpecifier(
100-
api.types.identifier(listBuilderLocalName),
101-
api.types.identifier(listBuilderLocalName)
102-
)
103-
)
104-
}
105-
}
106-
if (componentImports.has(COMPONENT_LIST_ITEM)) {
107-
const viewLocalName = componentImports.has(COMPONENT_VIEW)
108-
? componentImports.get(COMPONENT_VIEW).localName
109-
: COMPONENT_VIEW
110-
111-
if (!newSpecifiers.some((specifier) => specifier.imported?.name === viewLocalName)) {
112-
newSpecifiers.push(
113-
api.types.importSpecifier(api.types.identifier(viewLocalName), api.types.identifier(viewLocalName))
114-
)
115-
}
96+
const specifierMap = new Map()
97+
baseSpecifiers.forEach((specifier) => {
98+
specifierMap.set(specifier.local.name, specifier)
99+
})
100+
101+
// 保证重建的 @tarojs/components 导入不重复本地名,且补齐转换所需组件
102+
const ensureSpecifier = (localName, importedName) => {
103+
if (!specifierMap.has(localName)) {
104+
specifierMap.set(
105+
localName,
106+
api.types.importSpecifier(api.types.identifier(localName), api.types.identifier(importedName))
107+
)
116108
}
109+
}
110+
111+
if (taroComponentImports.has(COMPONENT_LIST)) {
112+
const scrollViewLocalName = getComponentLocalName(state, COMPONENT_SCROLL_VIEW, path.scope)
113+
const listBuilderLocalName = getComponentLocalName(state, COMPONENT_LIST_BUILDER, path.scope)
114+
115+
ensureSpecifier(scrollViewLocalName, COMPONENT_SCROLL_VIEW)
116+
ensureSpecifier(listBuilderLocalName, COMPONENT_LIST_BUILDER)
117+
}
117118

118-
path.node.body.unshift(
119-
api.types.importDeclaration(newSpecifiers, api.types.stringLiteral(TARO_COMPONENTS))
119+
if (taroComponentImports.has(COMPONENT_LIST_ITEM)) {
120+
const viewLocalName = getComponentLocalName(state, COMPONENT_VIEW, path.scope)
121+
122+
ensureSpecifier(viewLocalName, COMPONENT_VIEW)
123+
}
124+
125+
const nextSpecifiers = Array.from(specifierMap.values())
126+
if (nextSpecifiers.length > 0) {
127+
remainingBody.unshift(
128+
api.types.importDeclaration(nextSpecifiers, api.types.stringLiteral(TARO_COMPONENTS))
120129
)
121130
}
131+
132+
path.node.body = remainingBody
122133
}
123134
},
124135
},
125-
ImportDeclaration(path) {
136+
ImportDeclaration(path, state) {
126137
const { node } = path
127138
const { source, specifiers } = node
128139
if (api.types.isStringLiteral(source)) {
@@ -136,44 +147,49 @@ module.exports = declare((api) => {
136147
const local = specifier.local
137148

138149
// 收集组件导入信息
139-
componentImports.set(imported.name, {
140-
source: packageName,
141-
importedName: imported.name,
142-
localName: local.name,
143-
})
150+
if (packageName === TARO_COMPONENTS && !state.taroComponentImports.has(imported.name)) {
151+
state.taroComponentImports.set(imported.name, {
152+
source: packageName,
153+
importedName: imported.name,
154+
localName: local.name,
155+
})
156+
}
144157
}
145158
})
146159
}
147160
},
148-
JSXElement(path) {
161+
JSXElement(path, state) {
149162
const openingElement = path.node.openingElement
150163
if (openingElement.name && api.types.isJSXIdentifier(openingElement.name)) {
151164
const props = openingElement.attributes
152165
const children = path.node.children
153166
if (
154-
hasTargetTaroComponent(COMPONENT_LIST) &&
155-
openingElement.name.name === componentImports.get(COMPONENT_LIST).localName
167+
hasTargetTaroComponent(state, COMPONENT_LIST) &&
168+
openingElement.name.name === state.taroComponentImports.get(COMPONENT_LIST).localName
156169
) {
170+
const scrollViewName = getComponentLocalName(state, COMPONENT_SCROLL_VIEW, path.scope)
171+
const listBuilderName = getComponentLocalName(state, COMPONENT_LIST_BUILDER, path.scope)
172+
157173
// 创建 ScrollView 开始标签
158174
const scrollViewProps = pickAttrs(props, SCROLL_VIEW_PROPS)
159175
scrollViewProps.push(
160176
api.types.jsxAttribute(api.types.jsxIdentifier('type'), api.types.stringLiteral('custom'))
161177
)
162178
const scrollViewOpening = api.types.jsxOpeningElement(
163-
api.types.jsxIdentifier(COMPONENT_SCROLL_VIEW),
179+
api.types.jsxIdentifier(scrollViewName),
164180
scrollViewProps,
165181
false
166182
)
167183
// 创建 ScrollView 闭合标签
168-
const scrollViewClosing = api.types.jsxClosingElement(api.types.jsxIdentifier(COMPONENT_SCROLL_VIEW))
184+
const scrollViewClosing = api.types.jsxClosingElement(api.types.jsxIdentifier(scrollViewName))
169185
// 创建 ListBuilder 开始标签
170186
const listBuilderOpening = api.types.jsxOpeningElement(
171-
api.types.jsxIdentifier(COMPONENT_LIST_BUILDER),
187+
api.types.jsxIdentifier(listBuilderName),
172188
pickAttrs(props, LIST_BUILDER_PROPS),
173189
false
174190
)
175191
// 创建 ListBuilder 闭合标签
176-
const listBuilderClosing = api.types.jsxClosingElement(api.types.jsxIdentifier(COMPONENT_LIST_BUILDER))
192+
const listBuilderClosing = api.types.jsxClosingElement(api.types.jsxIdentifier(listBuilderName))
177193

178194
// 创建 ListBuilder 元素,包含原 List 的子元素
179195
const listBuilderElement = api.types.jsxElement(listBuilderOpening, listBuilderClosing, children, false)
@@ -190,12 +206,13 @@ module.exports = declare((api) => {
190206
}
191207

192208
if (
193-
hasTargetTaroComponent(COMPONENT_LIST_ITEM) &&
194-
openingElement.name.name === componentImports.get(COMPONENT_LIST_ITEM).localName
209+
hasTargetTaroComponent(state, COMPONENT_LIST_ITEM) &&
210+
openingElement.name.name === state.taroComponentImports.get(COMPONENT_LIST_ITEM).localName
195211
) {
196-
const viewOpening = api.types.jsxOpeningElement(api.types.jsxIdentifier(COMPONENT_VIEW), props, false)
212+
const viewName = getComponentLocalName(state, COMPONENT_VIEW, path.scope)
213+
const viewOpening = api.types.jsxOpeningElement(api.types.jsxIdentifier(viewName), props, false)
197214

198-
const viewClosing = api.types.jsxClosingElement(api.types.jsxIdentifier(COMPONENT_VIEW))
215+
const viewClosing = api.types.jsxClosingElement(api.types.jsxIdentifier(viewName))
199216

200217
const viewElement = api.types.jsxElement(viewOpening, viewClosing, path.node.children, false)
201218

0 commit comments

Comments
 (0)