diff --git a/packages/ui/build/plugins/component-v-bind-fix.spec.ts b/packages/ui/build/plugins/component-v-bind-fix.spec.ts
index 47bce8b391..0f3c108123 100644
--- a/packages/ui/build/plugins/component-v-bind-fix.spec.ts
+++ b/packages/ui/build/plugins/component-v-bind-fix.spec.ts
@@ -1,5 +1,10 @@
import { describe, test, expect } from 'vitest'
-import { transformVueComponent } from './component-v-bind-fix'
+import { transformVueComponent as o } from './component-v-bind-fix'
+
+const transformVueComponent = (code: string) => {
+ if (!code) { return undefined }
+ return o(code)?.code
+}
describe('component-v-bind-fix', () => {
describe('transformVueComponent', () => {
@@ -229,4 +234,116 @@ const background = 'yellow'
expect(transformVueComponent(componentCode())).toBe(expectedComponentCode())
})
+
+ test('transform multiple nodes', () => {
+ const componentCode = (attrs = '', nestedAttrs = '') => `
+
+
+
+ Label
+
+
+
+
+
+
+ `
+
+ const expectedComponentCode = () => `
+
+
+
+ Label
+
+
+
+
+
+
+ `
+
+ expect(transformVueComponent(componentCode())).toBe(expectedComponentCode())
+ })
+
+ test('transform multiple v-bind the same variable', () => {
+ const componentCode = (attrs = '', nestedAttrs = '') => `
+
+
+
+
+
+
+
+ `
+
+ const expectedComponentCode = () => `
+
+
+
+
+
+
+
+ `
+
+ expect(transformVueComponent(componentCode())).toBe(expectedComponentCode())
+ })
})
diff --git a/packages/ui/build/plugins/component-v-bind-fix.ts b/packages/ui/build/plugins/component-v-bind-fix.ts
index 5814ef2f4f..71698e66cf 100644
--- a/packages/ui/build/plugins/component-v-bind-fix.ts
+++ b/packages/ui/build/plugins/component-v-bind-fix.ts
@@ -1,6 +1,7 @@
import { Plugin } from 'vite'
import kebabCase from 'lodash/kebabCase'
-import { parse } from 'vue/compiler-sfc'
+import { type SFCParseResult, parse } from 'vue/compiler-sfc'
+import MagicString from 'magic-string'
/**
* Parse css and extract all variable names used in `v-bind`
@@ -16,8 +17,24 @@ import { parse } from 'vue/compiler-sfc'
*
* Returns`['colorComputed', 'getBg()']`
*/
-const parseCssVBindCode = (style: string) => {
- return parse(style).descriptor.cssVars
+const getVBinds = (sfc: SFCParseResult) => {
+ return sfc.descriptor.cssVars
+}
+
+/** Returns start and end indexes of v-bind used in style */
+const getStyleVBindLocs = (source: string, vBind: string) => {
+ // Regex for v-bind(color), v-bind('color'), v-bind("color")
+ const regex = new RegExp(`v-bind\\(['|"]?${vBind}['|"]?\\)`, 'gm')
+ const indexes = [] as { start: number, end: number }[]
+ let match
+
+ // The same variable can be used multiple times vBind in css
+ // replace all of them until there are no more matches
+ while ((match = regex.exec(source)) !== null) {
+ indexes.push({ start: match.index, end: match.index + match[0].length })
+ }
+
+ return indexes
}
/**
@@ -30,12 +47,23 @@ const parseCssVBindCode = (style: string) => {
*
*
* ```
- * Returns ``
+ * Returns loc for ``
*/
-const getRootNodeOpenTagCode = (code: string) => {
- const template = code.match(/]*>([\s\S]*)<\/template>/)?.[1]
- const rootNode = template?.match(/<[^>]*>/)?.[0]
- return rootNode
+const getRootNodesOpenTags = (sfc: SFCParseResult) => {
+ const ast = sfc.descriptor.template?.ast
+ const rootNodes = ast?.children.filter(node => node.type === 1 /* ELEMENT */)
+
+ return rootNodes?.map(({ loc }) => {
+ const openTag = loc.source.match(/<[^>]*>/)?.[0]
+ if (!openTag) { return undefined }
+
+ return {
+ ...loc,
+ end: { ...loc.start, offset: loc.start.offset + openTag.length },
+
+ source: openTag,
+ }
+ })
}
const renderCssVariablesAsStringCode = (vBinds: string[]) => {
@@ -63,7 +91,7 @@ const renderObjectGuardCode = (existingContent: string, binds: string[]) => {
return `typeof ${existingContent} === 'object' ? (Array.isArray(${existingContent}) ? ${arrayStyle} : ${objectStyle}) : ${stringStyle}`
}
-const addStyleToRootNode = (rootNode: string, vBinds: string[]) => {
+const addStyleAttrToTag = (rootNode: string, vBinds: string[]) => {
const [vBindCode, vBindContent] = rootNode?.match(/:style="([^"]*)"/) || []
const [attrCode, attrContent] = rootNode?.match(/[^:]style="([^"]*)"/) || []
const cssVariablesString = renderCssVariablesAsStringCode(vBinds)
@@ -84,33 +112,37 @@ const addStyleToRootNode = (rootNode: string, vBinds: string[]) => {
return rootNode.replace(/(\/?>)$/, ` :style="\`${cssVariablesString}\`"$1`)
}
-/** Replace each v-bind() with var(--va-index-name) */
-const replaceVueVBindWithCssVariables = (code: string, vBinds: string[]) => {
- vBinds.forEach((vBind, index) => {
- try {
- code = code.replace(new RegExp(`v-bind\\(['|"]?${vBind}['|"]?\\)`, 'gm'), `var(--va-${index}-${kebabCase(vBind)})`)
- } catch (e) {
- console.log(vBind)
- throw e
- }
- })
-
- return code
-}
-
export const transformVueComponent = (code: string) => {
- const style = code.match(/