@sprlab/wccompiler 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +998 -998
- package/adapters/angular-compiled/angular.d.ts +197 -197
- package/adapters/angular-compiled/angular.mjs +488 -488
- package/adapters/angular.js +54 -54
- package/adapters/angular.ts +630 -630
- package/adapters/react.js +114 -114
- package/adapters/vue.js +103 -103
- package/bin/wcc.js +412 -412
- package/bin/wcc.test.js +126 -126
- package/integrations/angular.js +73 -73
- package/integrations/react.js +859 -859
- package/integrations/vue.js +253 -253
- package/lib/codegen.js +2074 -2074
- package/lib/compiler-browser.js +545 -545
- package/lib/compiler.js +483 -479
- package/lib/config.js +71 -71
- package/lib/css-scoper.js +180 -180
- package/lib/dev-server.js +193 -193
- package/lib/import-resolver.js +160 -160
- package/lib/parser-extractors.js +1240 -1169
- package/lib/parser.js +273 -269
- package/lib/reactive-runtime.js +143 -143
- package/lib/sfc-parser.js +333 -333
- package/lib/template-normalizer.js +114 -114
- package/lib/tree-walker.js +1013 -1013
- package/lib/types.js +262 -262
- package/lib/wcc-runtime.js +68 -68
- package/package.json +85 -85
- package/types/wcc.d.ts +28 -28
- package/types/wcc.test.js +46 -46
package/integrations/vue.js
CHANGED
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vue Vite plugin for WCC custom elements.
|
|
3
|
-
* Configures isCustomElement and provides enhanced DX for v-model modifiers and scoped slots.
|
|
4
|
-
*
|
|
5
|
-
* @module @sprlab/wccompiler/integrations/vue
|
|
6
|
-
*
|
|
7
|
-
* IMPORTANT: This plugin is OPTIONAL for basic usage.
|
|
8
|
-
* WCC components work in Vue with zero WCC-specific config:
|
|
9
|
-
* - Props: <wcc-counter :count="val"></wcc-counter>
|
|
10
|
-
* - Events: <wcc-counter @count-changed="handler($event.detail)"></wcc-counter>
|
|
11
|
-
* - v-model: <wcc-counter v-model:count="val"></wcc-counter> (Vue 3.4+ CE support)
|
|
12
|
-
* - Named slots: <div slot="header">...</div>
|
|
13
|
-
*
|
|
14
|
-
* The only Vue-specific config needed (same as Lit, Shoelace, FAST):
|
|
15
|
-
* vue({ template: { compilerOptions: { isCustomElement: tag => tag.includes('-') } } })
|
|
16
|
-
*
|
|
17
|
-
* This plugin adds:
|
|
18
|
-
* 1. isCustomElement config (so you don't need to write it manually)
|
|
19
|
-
* 2. v-model modifier support (.trim, .number, .lazy)
|
|
20
|
-
* 3. Scoped slot syntax: <template #item="{ name }">{{name}}</template>
|
|
21
|
-
*
|
|
22
|
-
* @example vite.config.js (with plugin — full DX)
|
|
23
|
-
* ```js
|
|
24
|
-
* import { wccVuePlugin } from '@sprlab/wccompiler/integrations/vue'
|
|
25
|
-
* export default { plugins: [wccVuePlugin()] }
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* @example vite.config.js (without plugin — still works for basic usage)
|
|
29
|
-
* ```js
|
|
30
|
-
* import vue from '@vitejs/plugin-vue'
|
|
31
|
-
* export default {
|
|
32
|
-
* plugins: [vue({
|
|
33
|
-
* template: { compilerOptions: { isCustomElement: tag => tag.includes('-') } }
|
|
34
|
-
* })]
|
|
35
|
-
* }
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import vue from '@vitejs/plugin-vue'
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @typedef {Object} WccVuePluginOptions
|
|
43
|
-
* @property {string} [prefix='wcc-'] - Tag prefix for custom element detection
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Vite plugin that pre-transforms v-model:propName on custom elements
|
|
48
|
-
* before Vue's compiler processes the template.
|
|
49
|
-
*
|
|
50
|
-
* This is necessary because Vue's compiler filters out `onUpdate:*` event listeners
|
|
51
|
-
* for custom elements (isModelListener check in patchProp). By rewriting to
|
|
52
|
-
* `@propName-changed`, we use an event name that Vue registers normally.
|
|
53
|
-
*
|
|
54
|
-
* @param {WccVuePluginOptions} [options]
|
|
55
|
-
* @returns {import('vite').Plugin[]}
|
|
56
|
-
*/
|
|
57
|
-
export function wccVuePlugin(options = {}) {
|
|
58
|
-
const prefix = typeof options.prefix === 'string' ? options.prefix : 'wcc-'
|
|
59
|
-
|
|
60
|
-
const preTransformPlugin = {
|
|
61
|
-
name: 'vite-plugin-wcc-vmodel',
|
|
62
|
-
enforce: 'pre',
|
|
63
|
-
transform(code, id) {
|
|
64
|
-
if (!id.endsWith('.vue')) return null
|
|
65
|
-
|
|
66
|
-
let result = code
|
|
67
|
-
|
|
68
|
-
// NOTE: The compiled WCC component emits only `wcc:model` as its single
|
|
69
|
-
// canonical model change event. Framework-specific event formats are handled
|
|
70
|
-
// by each framework's adapter/plugin.
|
|
71
|
-
//
|
|
72
|
-
// This plugin is needed for:
|
|
73
|
-
// 1. v-model:propName (Vue can't unwrap CustomEvent.detail natively)
|
|
74
|
-
// 2. v-model modifiers (.trim, .number)
|
|
75
|
-
// 3. Scoped slot syntax transformation ({{prop}} → {%prop%})
|
|
76
|
-
|
|
77
|
-
// Transform v-model:propName="expr" on custom elements (tags with hyphens)
|
|
78
|
-
// Also handles modifiers: v-model:propName.trim.number="expr"
|
|
79
|
-
// → :propName="expr" + merged @wcc:model handler
|
|
80
|
-
// with modifiers applied to the extracted value:
|
|
81
|
-
// .trim → value.trim() (for string values)
|
|
82
|
-
// .number → Number(value)
|
|
83
|
-
// .lazy → no-op for custom elements
|
|
84
|
-
//
|
|
85
|
-
// Multiple v-model:prop on the same element are merged into a single
|
|
86
|
-
// @wcc:model handler with semicolons (avoids Vue "Duplicate attribute" error).
|
|
87
|
-
let prev = ''
|
|
88
|
-
while (prev !== result) {
|
|
89
|
-
prev = result
|
|
90
|
-
result = result.replace(
|
|
91
|
-
/(<[\w]+-[\w-]*(?:\s[^>]*?)?)\bv-model:(\w+)((?:\.\w+)*)="([^"]+)"/,
|
|
92
|
-
(match, prefix, prop, modifiersStr, expr) => {
|
|
93
|
-
const modifiers = modifiersStr ? modifiersStr.slice(1).split('.') : []
|
|
94
|
-
let value = '$event.detail.value'
|
|
95
|
-
// Apply modifiers in order
|
|
96
|
-
for (const mod of modifiers) {
|
|
97
|
-
if (mod === 'trim') {
|
|
98
|
-
value = `(typeof ${value} === 'string' ? (${value}).trim() : ${value})`
|
|
99
|
-
} else if (mod === 'number') {
|
|
100
|
-
value = `Number(${value})`
|
|
101
|
-
}
|
|
102
|
-
// .lazy is a no-op for custom elements (they already use change events)
|
|
103
|
-
}
|
|
104
|
-
const handler = `$event.detail.prop === '${prop}' && (${expr} = ${value})`
|
|
105
|
-
// Check if there's already a @wcc:model on this element — append to it
|
|
106
|
-
if (prefix.includes('@wcc:model="')) {
|
|
107
|
-
const merged = prefix.replace(
|
|
108
|
-
/@wcc:model="([^"]*)"/,
|
|
109
|
-
(_, existing) => `@wcc:model="${existing}; ${handler}"`
|
|
110
|
-
)
|
|
111
|
-
return `${merged}:${prop}="${expr}"`
|
|
112
|
-
}
|
|
113
|
-
return `${prefix}:${prop}="${expr}" @wcc:model="${handler}"`
|
|
114
|
-
}
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Transform v-model="expr" (without argument) on custom elements
|
|
119
|
-
// Also handles modifiers: v-model.trim.lazy="expr"
|
|
120
|
-
// → :model-value="expr" + merged @wcc:model handler
|
|
121
|
-
prev = ''
|
|
122
|
-
while (prev !== result) {
|
|
123
|
-
prev = result
|
|
124
|
-
result = result.replace(
|
|
125
|
-
/(<[\w]+-[\w-]*(?:\s[^>]*?)?)\bv-model((?:\.\w+)*)="([^"]+)"/,
|
|
126
|
-
(match, prefix, modifiersStr, expr) => {
|
|
127
|
-
const modifiers = modifiersStr ? modifiersStr.slice(1).split('.') : []
|
|
128
|
-
let value = '$event.detail.value'
|
|
129
|
-
for (const mod of modifiers) {
|
|
130
|
-
if (mod === 'trim') {
|
|
131
|
-
value = `(typeof ${value} === 'string' ? (${value}).trim() : ${value})`
|
|
132
|
-
} else if (mod === 'number') {
|
|
133
|
-
value = `Number(${value})`
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const handler = `$event.detail.prop === 'modelValue' && (${expr} = ${value})`
|
|
137
|
-
// Check if there's already a @wcc:model on this element — append to it
|
|
138
|
-
if (prefix.includes('@wcc:model="')) {
|
|
139
|
-
const merged = prefix.replace(
|
|
140
|
-
/@wcc:model="([^"]*)"/,
|
|
141
|
-
(_, existing) => `@wcc:model="${existing}; ${handler}"`
|
|
142
|
-
)
|
|
143
|
-
return `${merged}:model-value="${expr}"`
|
|
144
|
-
}
|
|
145
|
-
return `${prefix}:model-value="${expr}" @wcc:model="${handler}"`
|
|
146
|
-
}
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Post-process: merge any duplicate @wcc:model attributes on the same element
|
|
151
|
-
// This handles the case where v-model (no arg) was before v-model:prop in source order
|
|
152
|
-
result = result.replace(
|
|
153
|
-
/<([\w]+-[\w-]*)((?:\s[^>]*?)?)@wcc:model="([^"]*)"((?:\s[^>]*?)?)@wcc:model="([^"]*)"([^>]*?)>/g,
|
|
154
|
-
(match, tag, before, handler1, middle, handler2, after) => {
|
|
155
|
-
return `<${tag}${before}@wcc:model="${handler1}; ${handler2}"${middle}${after}>`
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
// ── Slot transforms ──
|
|
160
|
-
// Transform <template #name>content</template> inside custom elements
|
|
161
|
-
// → <div slot="name">content</div>
|
|
162
|
-
// This prevents Vue from intercepting the slot syntax and erroring.
|
|
163
|
-
// The WCC component's runtime slot parser detects slot="name" on regular elements.
|
|
164
|
-
//
|
|
165
|
-
// IMPORTANT: Only transform templates inside custom elements (tags with hyphens).
|
|
166
|
-
// This ensures we don't interfere with Vue's own slot/template handling on native elements.
|
|
167
|
-
|
|
168
|
-
// Helper: transform scoped slot content — escape {{prop}} → {%prop%} for declared props only
|
|
169
|
-
function transformScopedContent(content, propsExpr) {
|
|
170
|
-
const props = propsExpr.split(',').map(p => p.trim()).filter(Boolean)
|
|
171
|
-
let transformed = content
|
|
172
|
-
for (const prop of props) {
|
|
173
|
-
// Replace {{propName}} and {{ propName }} with {%propName%} / {% propName %}
|
|
174
|
-
transformed = transformed.replace(
|
|
175
|
-
new RegExp('\\{\\{(\\s*)' + prop + '(\\s*)\\}\\}', 'g'),
|
|
176
|
-
(m, ws1, ws2) => `{%${ws1}${prop}${ws2}%}`
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
return { transformed, props }
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Handle scoped slots: <template #name="{ prop1, prop2 }">...</template>
|
|
183
|
-
// → <div slot="name" slot-props="prop1, prop2" hidden>content with {%prop%}</div>
|
|
184
|
-
// The 'hidden' attribute prevents {%prop%} tokens from flashing before the WCC runtime processes them.
|
|
185
|
-
// Only inside custom elements (tag names with hyphens)
|
|
186
|
-
prev = ''
|
|
187
|
-
while (prev !== result) {
|
|
188
|
-
prev = result
|
|
189
|
-
result = result.replace(
|
|
190
|
-
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+#(\w+)="\{\s*([^}]*)\s*\}">([\s\S]*?)<\/template>/,
|
|
191
|
-
(match, openTag, before, slotName, propsExpr, content) => {
|
|
192
|
-
const { transformed, props } = transformScopedContent(content, propsExpr)
|
|
193
|
-
return `${openTag}${before}<div slot="${slotName}" slot-props="${props.join(', ')}" hidden>${transformed}</div>`
|
|
194
|
-
}
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Handle scoped slots: <template v-slot:name="{ prop1, prop2 }">...</template>
|
|
199
|
-
// → <div slot="name" slot-props="prop1, prop2" hidden>content with {%prop%}</div>
|
|
200
|
-
// Only inside custom elements (tag names with hyphens)
|
|
201
|
-
prev = ''
|
|
202
|
-
while (prev !== result) {
|
|
203
|
-
prev = result
|
|
204
|
-
result = result.replace(
|
|
205
|
-
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+v-slot:(\w+)="\{\s*([^}]*)\s*\}">([\s\S]*?)<\/template>/,
|
|
206
|
-
(match, openTag, before, slotName, propsExpr, content) => {
|
|
207
|
-
const { transformed, props } = transformScopedContent(content, propsExpr)
|
|
208
|
-
return `${openTag}${before}<div slot="${slotName}" slot-props="${props.join(', ')}" hidden>${transformed}</div>`
|
|
209
|
-
}
|
|
210
|
-
)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Handle non-scoped <template #name>...</template> (shorthand)
|
|
214
|
-
// Only inside custom elements (tag names with hyphens)
|
|
215
|
-
prev = ''
|
|
216
|
-
while (prev !== result) {
|
|
217
|
-
prev = result
|
|
218
|
-
result = result.replace(
|
|
219
|
-
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+#(\w+)>([\s\S]*?)<\/template>/,
|
|
220
|
-
(match, openTag, before, slotName, content) => {
|
|
221
|
-
return `${openTag}${before}<div slot="${slotName}">${content}</div>`
|
|
222
|
-
}
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Handle non-scoped <template v-slot:name>...</template> (verbose)
|
|
227
|
-
// Only inside custom elements (tag names with hyphens)
|
|
228
|
-
prev = ''
|
|
229
|
-
while (prev !== result) {
|
|
230
|
-
prev = result
|
|
231
|
-
result = result.replace(
|
|
232
|
-
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+v-slot:(\w+)>([\s\S]*?)<\/template>/,
|
|
233
|
-
(match, openTag, before, slotName, content) => {
|
|
234
|
-
return `${openTag}${before}<div slot="${slotName}">${content}</div>`
|
|
235
|
-
}
|
|
236
|
-
)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (result !== code) return result
|
|
240
|
-
return null
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const vuePlugin = vue({
|
|
245
|
-
template: {
|
|
246
|
-
compilerOptions: {
|
|
247
|
-
isCustomElement: (tag) => tag.startsWith(prefix)
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
return [preTransformPlugin, vuePlugin]
|
|
253
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Vue Vite plugin for WCC custom elements.
|
|
3
|
+
* Configures isCustomElement and provides enhanced DX for v-model modifiers and scoped slots.
|
|
4
|
+
*
|
|
5
|
+
* @module @sprlab/wccompiler/integrations/vue
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: This plugin is OPTIONAL for basic usage.
|
|
8
|
+
* WCC components work in Vue with zero WCC-specific config:
|
|
9
|
+
* - Props: <wcc-counter :count="val"></wcc-counter>
|
|
10
|
+
* - Events: <wcc-counter @count-changed="handler($event.detail)"></wcc-counter>
|
|
11
|
+
* - v-model: <wcc-counter v-model:count="val"></wcc-counter> (Vue 3.4+ CE support)
|
|
12
|
+
* - Named slots: <div slot="header">...</div>
|
|
13
|
+
*
|
|
14
|
+
* The only Vue-specific config needed (same as Lit, Shoelace, FAST):
|
|
15
|
+
* vue({ template: { compilerOptions: { isCustomElement: tag => tag.includes('-') } } })
|
|
16
|
+
*
|
|
17
|
+
* This plugin adds:
|
|
18
|
+
* 1. isCustomElement config (so you don't need to write it manually)
|
|
19
|
+
* 2. v-model modifier support (.trim, .number, .lazy)
|
|
20
|
+
* 3. Scoped slot syntax: <template #item="{ name }">{{name}}</template>
|
|
21
|
+
*
|
|
22
|
+
* @example vite.config.js (with plugin — full DX)
|
|
23
|
+
* ```js
|
|
24
|
+
* import { wccVuePlugin } from '@sprlab/wccompiler/integrations/vue'
|
|
25
|
+
* export default { plugins: [wccVuePlugin()] }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @example vite.config.js (without plugin — still works for basic usage)
|
|
29
|
+
* ```js
|
|
30
|
+
* import vue from '@vitejs/plugin-vue'
|
|
31
|
+
* export default {
|
|
32
|
+
* plugins: [vue({
|
|
33
|
+
* template: { compilerOptions: { isCustomElement: tag => tag.includes('-') } }
|
|
34
|
+
* })]
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import vue from '@vitejs/plugin-vue'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @typedef {Object} WccVuePluginOptions
|
|
43
|
+
* @property {string} [prefix='wcc-'] - Tag prefix for custom element detection
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Vite plugin that pre-transforms v-model:propName on custom elements
|
|
48
|
+
* before Vue's compiler processes the template.
|
|
49
|
+
*
|
|
50
|
+
* This is necessary because Vue's compiler filters out `onUpdate:*` event listeners
|
|
51
|
+
* for custom elements (isModelListener check in patchProp). By rewriting to
|
|
52
|
+
* `@propName-changed`, we use an event name that Vue registers normally.
|
|
53
|
+
*
|
|
54
|
+
* @param {WccVuePluginOptions} [options]
|
|
55
|
+
* @returns {import('vite').Plugin[]}
|
|
56
|
+
*/
|
|
57
|
+
export function wccVuePlugin(options = {}) {
|
|
58
|
+
const prefix = typeof options.prefix === 'string' ? options.prefix : 'wcc-'
|
|
59
|
+
|
|
60
|
+
const preTransformPlugin = {
|
|
61
|
+
name: 'vite-plugin-wcc-vmodel',
|
|
62
|
+
enforce: 'pre',
|
|
63
|
+
transform(code, id) {
|
|
64
|
+
if (!id.endsWith('.vue')) return null
|
|
65
|
+
|
|
66
|
+
let result = code
|
|
67
|
+
|
|
68
|
+
// NOTE: The compiled WCC component emits only `wcc:model` as its single
|
|
69
|
+
// canonical model change event. Framework-specific event formats are handled
|
|
70
|
+
// by each framework's adapter/plugin.
|
|
71
|
+
//
|
|
72
|
+
// This plugin is needed for:
|
|
73
|
+
// 1. v-model:propName (Vue can't unwrap CustomEvent.detail natively)
|
|
74
|
+
// 2. v-model modifiers (.trim, .number)
|
|
75
|
+
// 3. Scoped slot syntax transformation ({{prop}} → {%prop%})
|
|
76
|
+
|
|
77
|
+
// Transform v-model:propName="expr" on custom elements (tags with hyphens)
|
|
78
|
+
// Also handles modifiers: v-model:propName.trim.number="expr"
|
|
79
|
+
// → :propName="expr" + merged @wcc:model handler
|
|
80
|
+
// with modifiers applied to the extracted value:
|
|
81
|
+
// .trim → value.trim() (for string values)
|
|
82
|
+
// .number → Number(value)
|
|
83
|
+
// .lazy → no-op for custom elements
|
|
84
|
+
//
|
|
85
|
+
// Multiple v-model:prop on the same element are merged into a single
|
|
86
|
+
// @wcc:model handler with semicolons (avoids Vue "Duplicate attribute" error).
|
|
87
|
+
let prev = ''
|
|
88
|
+
while (prev !== result) {
|
|
89
|
+
prev = result
|
|
90
|
+
result = result.replace(
|
|
91
|
+
/(<[\w]+-[\w-]*(?:\s[^>]*?)?)\bv-model:(\w+)((?:\.\w+)*)="([^"]+)"/,
|
|
92
|
+
(match, prefix, prop, modifiersStr, expr) => {
|
|
93
|
+
const modifiers = modifiersStr ? modifiersStr.slice(1).split('.') : []
|
|
94
|
+
let value = '$event.detail.value'
|
|
95
|
+
// Apply modifiers in order
|
|
96
|
+
for (const mod of modifiers) {
|
|
97
|
+
if (mod === 'trim') {
|
|
98
|
+
value = `(typeof ${value} === 'string' ? (${value}).trim() : ${value})`
|
|
99
|
+
} else if (mod === 'number') {
|
|
100
|
+
value = `Number(${value})`
|
|
101
|
+
}
|
|
102
|
+
// .lazy is a no-op for custom elements (they already use change events)
|
|
103
|
+
}
|
|
104
|
+
const handler = `$event.detail.prop === '${prop}' && (${expr} = ${value})`
|
|
105
|
+
// Check if there's already a @wcc:model on this element — append to it
|
|
106
|
+
if (prefix.includes('@wcc:model="')) {
|
|
107
|
+
const merged = prefix.replace(
|
|
108
|
+
/@wcc:model="([^"]*)"/,
|
|
109
|
+
(_, existing) => `@wcc:model="${existing}; ${handler}"`
|
|
110
|
+
)
|
|
111
|
+
return `${merged}:${prop}="${expr}"`
|
|
112
|
+
}
|
|
113
|
+
return `${prefix}:${prop}="${expr}" @wcc:model="${handler}"`
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Transform v-model="expr" (without argument) on custom elements
|
|
119
|
+
// Also handles modifiers: v-model.trim.lazy="expr"
|
|
120
|
+
// → :model-value="expr" + merged @wcc:model handler
|
|
121
|
+
prev = ''
|
|
122
|
+
while (prev !== result) {
|
|
123
|
+
prev = result
|
|
124
|
+
result = result.replace(
|
|
125
|
+
/(<[\w]+-[\w-]*(?:\s[^>]*?)?)\bv-model((?:\.\w+)*)="([^"]+)"/,
|
|
126
|
+
(match, prefix, modifiersStr, expr) => {
|
|
127
|
+
const modifiers = modifiersStr ? modifiersStr.slice(1).split('.') : []
|
|
128
|
+
let value = '$event.detail.value'
|
|
129
|
+
for (const mod of modifiers) {
|
|
130
|
+
if (mod === 'trim') {
|
|
131
|
+
value = `(typeof ${value} === 'string' ? (${value}).trim() : ${value})`
|
|
132
|
+
} else if (mod === 'number') {
|
|
133
|
+
value = `Number(${value})`
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const handler = `$event.detail.prop === 'modelValue' && (${expr} = ${value})`
|
|
137
|
+
// Check if there's already a @wcc:model on this element — append to it
|
|
138
|
+
if (prefix.includes('@wcc:model="')) {
|
|
139
|
+
const merged = prefix.replace(
|
|
140
|
+
/@wcc:model="([^"]*)"/,
|
|
141
|
+
(_, existing) => `@wcc:model="${existing}; ${handler}"`
|
|
142
|
+
)
|
|
143
|
+
return `${merged}:model-value="${expr}"`
|
|
144
|
+
}
|
|
145
|
+
return `${prefix}:model-value="${expr}" @wcc:model="${handler}"`
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Post-process: merge any duplicate @wcc:model attributes on the same element
|
|
151
|
+
// This handles the case where v-model (no arg) was before v-model:prop in source order
|
|
152
|
+
result = result.replace(
|
|
153
|
+
/<([\w]+-[\w-]*)((?:\s[^>]*?)?)@wcc:model="([^"]*)"((?:\s[^>]*?)?)@wcc:model="([^"]*)"([^>]*?)>/g,
|
|
154
|
+
(match, tag, before, handler1, middle, handler2, after) => {
|
|
155
|
+
return `<${tag}${before}@wcc:model="${handler1}; ${handler2}"${middle}${after}>`
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
// ── Slot transforms ──
|
|
160
|
+
// Transform <template #name>content</template> inside custom elements
|
|
161
|
+
// → <div slot="name">content</div>
|
|
162
|
+
// This prevents Vue from intercepting the slot syntax and erroring.
|
|
163
|
+
// The WCC component's runtime slot parser detects slot="name" on regular elements.
|
|
164
|
+
//
|
|
165
|
+
// IMPORTANT: Only transform templates inside custom elements (tags with hyphens).
|
|
166
|
+
// This ensures we don't interfere with Vue's own slot/template handling on native elements.
|
|
167
|
+
|
|
168
|
+
// Helper: transform scoped slot content — escape {{prop}} → {%prop%} for declared props only
|
|
169
|
+
function transformScopedContent(content, propsExpr) {
|
|
170
|
+
const props = propsExpr.split(',').map(p => p.trim()).filter(Boolean)
|
|
171
|
+
let transformed = content
|
|
172
|
+
for (const prop of props) {
|
|
173
|
+
// Replace {{propName}} and {{ propName }} with {%propName%} / {% propName %}
|
|
174
|
+
transformed = transformed.replace(
|
|
175
|
+
new RegExp('\\{\\{(\\s*)' + prop + '(\\s*)\\}\\}', 'g'),
|
|
176
|
+
(m, ws1, ws2) => `{%${ws1}${prop}${ws2}%}`
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
return { transformed, props }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Handle scoped slots: <template #name="{ prop1, prop2 }">...</template>
|
|
183
|
+
// → <div slot="name" slot-props="prop1, prop2" hidden>content with {%prop%}</div>
|
|
184
|
+
// The 'hidden' attribute prevents {%prop%} tokens from flashing before the WCC runtime processes them.
|
|
185
|
+
// Only inside custom elements (tag names with hyphens)
|
|
186
|
+
prev = ''
|
|
187
|
+
while (prev !== result) {
|
|
188
|
+
prev = result
|
|
189
|
+
result = result.replace(
|
|
190
|
+
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+#(\w+)="\{\s*([^}]*)\s*\}">([\s\S]*?)<\/template>/,
|
|
191
|
+
(match, openTag, before, slotName, propsExpr, content) => {
|
|
192
|
+
const { transformed, props } = transformScopedContent(content, propsExpr)
|
|
193
|
+
return `${openTag}${before}<div slot="${slotName}" slot-props="${props.join(', ')}" hidden>${transformed}</div>`
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Handle scoped slots: <template v-slot:name="{ prop1, prop2 }">...</template>
|
|
199
|
+
// → <div slot="name" slot-props="prop1, prop2" hidden>content with {%prop%}</div>
|
|
200
|
+
// Only inside custom elements (tag names with hyphens)
|
|
201
|
+
prev = ''
|
|
202
|
+
while (prev !== result) {
|
|
203
|
+
prev = result
|
|
204
|
+
result = result.replace(
|
|
205
|
+
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+v-slot:(\w+)="\{\s*([^}]*)\s*\}">([\s\S]*?)<\/template>/,
|
|
206
|
+
(match, openTag, before, slotName, propsExpr, content) => {
|
|
207
|
+
const { transformed, props } = transformScopedContent(content, propsExpr)
|
|
208
|
+
return `${openTag}${before}<div slot="${slotName}" slot-props="${props.join(', ')}" hidden>${transformed}</div>`
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Handle non-scoped <template #name>...</template> (shorthand)
|
|
214
|
+
// Only inside custom elements (tag names with hyphens)
|
|
215
|
+
prev = ''
|
|
216
|
+
while (prev !== result) {
|
|
217
|
+
prev = result
|
|
218
|
+
result = result.replace(
|
|
219
|
+
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+#(\w+)>([\s\S]*?)<\/template>/,
|
|
220
|
+
(match, openTag, before, slotName, content) => {
|
|
221
|
+
return `${openTag}${before}<div slot="${slotName}">${content}</div>`
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Handle non-scoped <template v-slot:name>...</template> (verbose)
|
|
227
|
+
// Only inside custom elements (tag names with hyphens)
|
|
228
|
+
prev = ''
|
|
229
|
+
while (prev !== result) {
|
|
230
|
+
prev = result
|
|
231
|
+
result = result.replace(
|
|
232
|
+
/(<[\w]+-[\w-]*[^>]*>)([\s\S]*?)<template\s+v-slot:(\w+)>([\s\S]*?)<\/template>/,
|
|
233
|
+
(match, openTag, before, slotName, content) => {
|
|
234
|
+
return `${openTag}${before}<div slot="${slotName}">${content}</div>`
|
|
235
|
+
}
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (result !== code) return result
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const vuePlugin = vue({
|
|
245
|
+
template: {
|
|
246
|
+
compilerOptions: {
|
|
247
|
+
isCustomElement: (tag) => tag.startsWith(prefix)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
return [preTransformPlugin, vuePlugin]
|
|
253
|
+
}
|