@zeus-js/output-vue-wrapper 0.1.0-beta.2 → 0.1.0-beta.4
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-vue-wrapper v0.1.0-beta.
|
|
2
|
+
* output-vue-wrapper v0.1.0-beta.4
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -22,76 +22,192 @@ function generateVueIndex(components, options) {
|
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region packages/web-c/output-vue-wrapper/src/generateVueWrapper.ts
|
|
24
24
|
function generateVueWrapper(input) {
|
|
25
|
+
var _input$component$mode;
|
|
26
|
+
return input.mode === "event-bridge" || ((_input$component$mode = input.component.models) === null || _input$component$mode === void 0 ? void 0 : _input$component$mode.length) ? generateEventBridgeVueWrapper(input) : generateMinimalVueWrapper(input);
|
|
27
|
+
}
|
|
28
|
+
function generateMinimalVueWrapper(input) {
|
|
25
29
|
const { component, wcModuleId } = input;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
29
|
-
const syncPropsBody = generateVuePropSyncLines(propNames);
|
|
30
|
-
const watchDeps = propNames.map((name) => `props.${name}`).join(", ");
|
|
31
|
-
const watchBlock = propNames.length > 0 ? `watch(() => [${watchDeps}], syncProps);` : `// no reactive props`;
|
|
30
|
+
const slotNames = getNamedSlots(component);
|
|
31
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
32
32
|
return `
|
|
33
|
-
import {
|
|
34
|
-
cloneVNode,
|
|
35
|
-
defineComponent,
|
|
36
|
-
h,
|
|
37
|
-
onBeforeUnmount,
|
|
38
|
-
onMounted,
|
|
39
|
-
ref,
|
|
40
|
-
watch,
|
|
41
|
-
} from 'vue';
|
|
33
|
+
import { ${hasNamedSlots ? "cloneVNode, defineComponent, h" : "defineComponent, h"} } from 'vue';
|
|
42
34
|
|
|
43
35
|
import ${JSON.stringify(wcModuleId)};
|
|
44
|
-
|
|
45
|
-
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
46
|
-
const EVENT_NAMES = ${JSON.stringify(eventNames)};
|
|
47
|
-
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
48
|
-
|
|
36
|
+
${hasNamedSlots ? `\nconst NAMED_SLOTS = ${JSON.stringify(slotNames)};\n` : ""}
|
|
49
37
|
export const ${component.name} = defineComponent({
|
|
50
38
|
name: ${JSON.stringify(component.name)},
|
|
39
|
+
inheritAttrs: false,
|
|
51
40
|
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
setup(_props, { attrs, slots }) {
|
|
42
|
+
return () => {
|
|
43
|
+
${generateVueChildren(slotNames)}
|
|
44
|
+
return h(${JSON.stringify(component.tag)}, attrs, children);
|
|
45
|
+
};
|
|
54
46
|
},
|
|
47
|
+
});
|
|
48
|
+
${hasNamedSlots ? VUE_NAMED_SLOT_HELPERS : ""}
|
|
49
|
+
`.trimStart();
|
|
50
|
+
}
|
|
51
|
+
function generateEventBridgeVueWrapper(input) {
|
|
52
|
+
const { component, wcModuleId } = input;
|
|
53
|
+
const capabilities = getCapabilities(component);
|
|
54
|
+
const { eventNames, models, propNames, slotNames } = capabilities;
|
|
55
|
+
if (!propNames.length && !eventNames.length) return generateMinimalVueWrapper(input);
|
|
56
|
+
const hasProps = propNames.length > 0;
|
|
57
|
+
const hasEvents = eventNames.length > 0;
|
|
58
|
+
const emitNames = Array.from(new Set([...eventNames, ...models.map((model) => model.updateEvent)]));
|
|
59
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
60
|
+
return `
|
|
61
|
+
import {
|
|
62
|
+
${[
|
|
63
|
+
...hasNamedSlots ? ["cloneVNode"] : [],
|
|
64
|
+
"defineComponent",
|
|
65
|
+
...hasProps ? ["getCurrentInstance"] : [],
|
|
66
|
+
"h",
|
|
67
|
+
...hasEvents ? ["onBeforeUnmount"] : [],
|
|
68
|
+
...hasProps || hasEvents ? ["onMounted"] : [],
|
|
69
|
+
...hasProps ? ["onUpdated"] : [],
|
|
70
|
+
"ref"
|
|
71
|
+
].join(",\n ")},
|
|
72
|
+
} from 'vue';
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
import ${JSON.stringify(wcModuleId)};
|
|
75
|
+
${generateVueConstants(capabilities)}
|
|
76
|
+
export const ${component.name} = defineComponent({
|
|
77
|
+
name: ${JSON.stringify(component.name)},
|
|
78
|
+
inheritAttrs: false,
|
|
79
|
+
${hasProps ? `\n props: {\n ${generateVueProps(component)}\n },\n` : ""}
|
|
80
|
+
${hasEvents ? ` emits: ${models.length ? JSON.stringify(emitNames) : "EVENT_NAMES"},\n` : ""}
|
|
81
|
+
setup(${hasProps ? "props" : "_props"}, { attrs, slots${hasEvents ? ", emit" : ""} }) {
|
|
59
82
|
const elRef = ref(null);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
${generateVuePropSetup(hasProps)}
|
|
84
|
+
${generateVueEventSetup(hasEvents, models.length > 0)}
|
|
85
|
+
${generateVueMountHook(hasProps, hasEvents)}
|
|
86
|
+
${hasProps ? " onUpdated(syncProps);\n" : ""}
|
|
87
|
+
${generateVueUnmountHook(hasEvents)}
|
|
88
|
+
return () => {
|
|
89
|
+
${generateVueChildren(slotNames)}
|
|
90
|
+
const hostProps = Object.assign({}, attrs);
|
|
91
|
+
hostProps.ref = elRef;
|
|
65
92
|
|
|
66
|
-
${
|
|
93
|
+
return h(${JSON.stringify(component.tag)}, hostProps, children);
|
|
67
94
|
};
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
${hasProps ? VUE_PROP_HELPERS : ""}
|
|
98
|
+
${hasNamedSlots ? VUE_NAMED_SLOT_HELPERS : ""}
|
|
99
|
+
${models.length ? VUE_MODEL_HELPERS : ""}
|
|
100
|
+
`.trimStart();
|
|
101
|
+
}
|
|
102
|
+
function getCapabilities(component) {
|
|
103
|
+
var _component$models;
|
|
104
|
+
const models = ((_component$models = component.models) !== null && _component$models !== void 0 ? _component$models : []).map((model) => ({
|
|
105
|
+
event: model.event,
|
|
106
|
+
eventPath: model.eventPath,
|
|
107
|
+
updateEvent: `update:${model.prop}`
|
|
108
|
+
}));
|
|
109
|
+
return {
|
|
110
|
+
propNames: Object.keys(component.props),
|
|
111
|
+
eventNames: Array.from(new Set([...Object.entries(component.events).map(([key, event]) => {
|
|
112
|
+
var _event$name, _event$key;
|
|
113
|
+
return (_event$name = event.name) !== null && _event$name !== void 0 ? _event$name : toKebabCase((_event$key = event.key) !== null && _event$key !== void 0 ? _event$key : key);
|
|
114
|
+
}), ...models.map((model) => model.event)])),
|
|
115
|
+
models,
|
|
116
|
+
slotNames: getNamedSlots(component)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function getNamedSlots(component) {
|
|
120
|
+
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
121
|
+
}
|
|
122
|
+
function generateVueConstants(capabilities) {
|
|
123
|
+
const { eventNames, models, propNames, slotNames } = capabilities;
|
|
124
|
+
const lines = [];
|
|
125
|
+
if (propNames.length) {
|
|
126
|
+
lines.push(`const PROP_KEYS = ${JSON.stringify(propNames)};`);
|
|
127
|
+
lines.push(`const PROP_INPUT_KEYS = ${JSON.stringify(createVuePropInputKeys(propNames))};`);
|
|
128
|
+
lines.push("const EMPTY_PROPS = {};");
|
|
129
|
+
}
|
|
130
|
+
if (eventNames.length) lines.push(`const EVENT_NAMES = ${JSON.stringify(eventNames)};`);
|
|
131
|
+
if (models.length) lines.push(`const MODEL_BINDINGS = ${JSON.stringify(models)};`);
|
|
132
|
+
if (slotNames.length) lines.push(`const NAMED_SLOTS = ${JSON.stringify(slotNames)};`);
|
|
133
|
+
return lines.length ? `${lines.join("\n")}\n` : "";
|
|
134
|
+
}
|
|
135
|
+
function generateVuePropSetup(hasProps) {
|
|
136
|
+
if (!hasProps) return "";
|
|
137
|
+
return ` const instance = getCurrentInstance();
|
|
138
|
+
const syncedPropPresence = [];
|
|
139
|
+
const syncedPropValues = [];
|
|
68
140
|
|
|
69
|
-
|
|
70
|
-
syncProps();
|
|
71
|
-
|
|
141
|
+
const syncProps = () => {
|
|
72
142
|
const el = elRef.value;
|
|
73
143
|
if (!el) return;
|
|
74
144
|
|
|
75
|
-
|
|
76
|
-
const handler = event => emit(eventName, event);
|
|
77
|
-
el.addEventListener(eventName, handler);
|
|
78
|
-
cleanups.push(() => el.removeEventListener(eventName, handler));
|
|
79
|
-
}
|
|
80
|
-
});
|
|
145
|
+
const rawProps = instance?.vnode.props || EMPTY_PROPS;
|
|
81
146
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
cleanups.length = 0;
|
|
85
|
-
});
|
|
147
|
+
for (let index = 0; index < PROP_KEYS.length; index += 1) {
|
|
148
|
+
const name = PROP_KEYS[index];
|
|
86
149
|
|
|
87
|
-
|
|
150
|
+
if (hasRawProp(rawProps, name)) {
|
|
151
|
+
const nextValue = props[name];
|
|
152
|
+
if (
|
|
153
|
+
!syncedPropPresence[index] ||
|
|
154
|
+
!Object.is(syncedPropValues[index], nextValue)
|
|
155
|
+
) {
|
|
156
|
+
el[name] = nextValue;
|
|
157
|
+
syncedPropValues[index] = nextValue;
|
|
158
|
+
}
|
|
159
|
+
syncedPropPresence[index] = true;
|
|
160
|
+
} else if (syncedPropPresence[index]) {
|
|
161
|
+
el[name] = undefined;
|
|
162
|
+
syncedPropPresence[index] = false;
|
|
163
|
+
syncedPropValues[index] = undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
function generateVueEventSetup(hasEvents, hasModels) {
|
|
170
|
+
if (!hasEvents) return "";
|
|
171
|
+
return ` const eventHandlers = EVENT_NAMES.map(eventName => event => {
|
|
172
|
+
emit(eventName, event);
|
|
173
|
+
${hasModels ? `
|
|
174
|
+
for (const model of MODEL_BINDINGS) {
|
|
175
|
+
if (model.event !== eventName) continue;
|
|
176
|
+
emit(model.updateEvent, readEventPath(event, model.eventPath));
|
|
177
|
+
}
|
|
178
|
+
` : ""} });
|
|
179
|
+
let mountedEl = null;
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
function generateVueMountHook(hasProps, hasEvents) {
|
|
183
|
+
if (!hasProps && !hasEvents) return "";
|
|
184
|
+
return ` onMounted(() => {
|
|
185
|
+
${hasProps ? " syncProps();\n" : ""}${hasEvents ? `
|
|
186
|
+
mountedEl = elRef.value;
|
|
187
|
+
if (!mountedEl) return;
|
|
88
188
|
|
|
89
|
-
|
|
90
|
-
|
|
189
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
190
|
+
mountedEl.addEventListener(EVENT_NAMES[index], eventHandlers[index]);
|
|
191
|
+
}
|
|
192
|
+
` : ""} });
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
function generateVueUnmountHook(hasEvents) {
|
|
196
|
+
if (!hasEvents) return "";
|
|
197
|
+
return ` onBeforeUnmount(() => {
|
|
198
|
+
if (!mountedEl) return;
|
|
91
199
|
|
|
92
|
-
|
|
93
|
-
|
|
200
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
201
|
+
mountedEl.removeEventListener(EVENT_NAMES[index], eventHandlers[index]);
|
|
94
202
|
}
|
|
203
|
+
mountedEl = null;
|
|
204
|
+
});
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
function generateVueChildren(slotNames) {
|
|
208
|
+
if (!slotNames.length) return ` const children = slots.default ? slots.default() : undefined;
|
|
209
|
+
`;
|
|
210
|
+
return ` const children = slots.default ? slots.default() : [];
|
|
95
211
|
|
|
96
212
|
for (const name of NAMED_SLOTS) {
|
|
97
213
|
const slot = slots[name];
|
|
@@ -101,29 +217,7 @@ export const ${component.name} = defineComponent({
|
|
|
101
217
|
children.push(withSlot(name, vnode));
|
|
102
218
|
}
|
|
103
219
|
}
|
|
104
|
-
|
|
105
|
-
return h(
|
|
106
|
-
${JSON.stringify(component.tag)},
|
|
107
|
-
{
|
|
108
|
-
...attrs,
|
|
109
|
-
ref: elRef,
|
|
110
|
-
},
|
|
111
|
-
children,
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
function withSlot(name, vnode) {
|
|
118
|
-
if (!vnode) return vnode;
|
|
119
|
-
|
|
120
|
-
if (typeof vnode === 'string') {
|
|
121
|
-
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return cloneVNode(vnode, { slot: name });
|
|
125
|
-
}
|
|
126
|
-
`.trimStart();
|
|
220
|
+
`;
|
|
127
221
|
}
|
|
128
222
|
function generateVueProps(component) {
|
|
129
223
|
return Object.entries(component.props).map(([name, prop]) => {
|
|
@@ -132,32 +226,70 @@ function generateVueProps(component) {
|
|
|
132
226
|
}
|
|
133
227
|
function toVuePropOption(prop) {
|
|
134
228
|
var _typeMap$prop$type;
|
|
135
|
-
|
|
229
|
+
return `{ type: ${(_typeMap$prop$type = {
|
|
136
230
|
string: "String",
|
|
137
231
|
number: "Number",
|
|
138
232
|
boolean: "Boolean",
|
|
139
233
|
object: "Object",
|
|
140
234
|
array: "Array",
|
|
235
|
+
function: "Function",
|
|
141
236
|
unknown: "null"
|
|
142
|
-
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"
|
|
143
|
-
|
|
144
|
-
|
|
237
|
+
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"}, required: ${prop.required === true ? "true" : "false"} }`;
|
|
238
|
+
}
|
|
239
|
+
function createVuePropInputKeys(propNames) {
|
|
240
|
+
return Object.fromEntries(propNames.map((name) => [name, Array.from(new Set([name, toKebabCase(name)]))]));
|
|
241
|
+
}
|
|
242
|
+
function toKebabCase(value) {
|
|
243
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
244
|
+
}
|
|
245
|
+
const VUE_PROP_HELPERS = `
|
|
246
|
+
function hasRawProp(rawProps, name) {
|
|
247
|
+
const keys = PROP_INPUT_KEYS[name];
|
|
248
|
+
for (const key of keys) {
|
|
249
|
+
if (hasOwn(rawProps, key)) return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function hasOwn(source, key) {
|
|
255
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
const VUE_NAMED_SLOT_HELPERS = `
|
|
259
|
+
function withSlot(name, vnode) {
|
|
260
|
+
if (!vnode) return vnode;
|
|
261
|
+
|
|
262
|
+
if (typeof vnode === 'string') {
|
|
263
|
+
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return cloneVNode(vnode, { slot: name });
|
|
145
267
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
268
|
+
`;
|
|
269
|
+
const VUE_MODEL_HELPERS = `
|
|
270
|
+
function readEventPath(event, path) {
|
|
271
|
+
if (!path) return event.detail;
|
|
272
|
+
|
|
273
|
+
let value = event;
|
|
274
|
+
for (const segment of path.split('.')) {
|
|
275
|
+
if (value == null) return undefined;
|
|
276
|
+
value = value[segment];
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
149
279
|
}
|
|
280
|
+
`;
|
|
150
281
|
//#endregion
|
|
151
282
|
//#region packages/web-c/output-vue-wrapper/src/index.ts
|
|
152
283
|
function vueWrapper(options = {}) {
|
|
153
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index;
|
|
284
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index, _options$wrapper;
|
|
154
285
|
const normalized = {
|
|
155
286
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "vue",
|
|
156
287
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
157
288
|
fileName: options.fileName,
|
|
158
|
-
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts :
|
|
159
|
-
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts :
|
|
160
|
-
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true
|
|
289
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
290
|
+
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts : true,
|
|
291
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
292
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
161
293
|
};
|
|
162
294
|
return {
|
|
163
295
|
name: "zeus-output-vue-wrapper",
|
|
@@ -177,7 +309,8 @@ function vueWrapper(options = {}) {
|
|
|
177
309
|
fileName: ctx.outputs.join("vue", ctx.outputs.getFileName("vue", component.tag)),
|
|
178
310
|
code: generateVueWrapper({
|
|
179
311
|
component,
|
|
180
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
312
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
313
|
+
mode: normalized.wrapper
|
|
181
314
|
})
|
|
182
315
|
});
|
|
183
316
|
if (normalized.index) modules.push({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-vue-wrapper v0.1.0-beta.
|
|
2
|
+
* output-vue-wrapper v0.1.0-beta.4
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -22,76 +22,192 @@ function generateVueIndex(components, options) {
|
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region packages/web-c/output-vue-wrapper/src/generateVueWrapper.ts
|
|
24
24
|
function generateVueWrapper(input) {
|
|
25
|
+
var _input$component$mode;
|
|
26
|
+
return input.mode === "event-bridge" || ((_input$component$mode = input.component.models) === null || _input$component$mode === void 0 ? void 0 : _input$component$mode.length) ? generateEventBridgeVueWrapper(input) : generateMinimalVueWrapper(input);
|
|
27
|
+
}
|
|
28
|
+
function generateMinimalVueWrapper(input) {
|
|
25
29
|
const { component, wcModuleId } = input;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
29
|
-
const syncPropsBody = generateVuePropSyncLines(propNames);
|
|
30
|
-
const watchDeps = propNames.map((name) => `props.${name}`).join(", ");
|
|
31
|
-
const watchBlock = propNames.length > 0 ? `watch(() => [${watchDeps}], syncProps);` : `// no reactive props`;
|
|
30
|
+
const slotNames = getNamedSlots(component);
|
|
31
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
32
32
|
return `
|
|
33
|
-
import {
|
|
34
|
-
cloneVNode,
|
|
35
|
-
defineComponent,
|
|
36
|
-
h,
|
|
37
|
-
onBeforeUnmount,
|
|
38
|
-
onMounted,
|
|
39
|
-
ref,
|
|
40
|
-
watch,
|
|
41
|
-
} from 'vue';
|
|
33
|
+
import { ${hasNamedSlots ? "cloneVNode, defineComponent, h" : "defineComponent, h"} } from 'vue';
|
|
42
34
|
|
|
43
35
|
import ${JSON.stringify(wcModuleId)};
|
|
44
|
-
|
|
45
|
-
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
46
|
-
const EVENT_NAMES = ${JSON.stringify(eventNames)};
|
|
47
|
-
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
48
|
-
|
|
36
|
+
${hasNamedSlots ? `\nconst NAMED_SLOTS = ${JSON.stringify(slotNames)};\n` : ""}
|
|
49
37
|
export const ${component.name} = defineComponent({
|
|
50
38
|
name: ${JSON.stringify(component.name)},
|
|
39
|
+
inheritAttrs: false,
|
|
51
40
|
|
|
52
|
-
|
|
53
|
-
|
|
41
|
+
setup(_props, { attrs, slots }) {
|
|
42
|
+
return () => {
|
|
43
|
+
${generateVueChildren(slotNames)}
|
|
44
|
+
return h(${JSON.stringify(component.tag)}, attrs, children);
|
|
45
|
+
};
|
|
54
46
|
},
|
|
47
|
+
});
|
|
48
|
+
${hasNamedSlots ? VUE_NAMED_SLOT_HELPERS : ""}
|
|
49
|
+
`.trimStart();
|
|
50
|
+
}
|
|
51
|
+
function generateEventBridgeVueWrapper(input) {
|
|
52
|
+
const { component, wcModuleId } = input;
|
|
53
|
+
const capabilities = getCapabilities(component);
|
|
54
|
+
const { eventNames, models, propNames, slotNames } = capabilities;
|
|
55
|
+
if (!propNames.length && !eventNames.length) return generateMinimalVueWrapper(input);
|
|
56
|
+
const hasProps = propNames.length > 0;
|
|
57
|
+
const hasEvents = eventNames.length > 0;
|
|
58
|
+
const emitNames = Array.from(new Set([...eventNames, ...models.map((model) => model.updateEvent)]));
|
|
59
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
60
|
+
return `
|
|
61
|
+
import {
|
|
62
|
+
${[
|
|
63
|
+
...hasNamedSlots ? ["cloneVNode"] : [],
|
|
64
|
+
"defineComponent",
|
|
65
|
+
...hasProps ? ["getCurrentInstance"] : [],
|
|
66
|
+
"h",
|
|
67
|
+
...hasEvents ? ["onBeforeUnmount"] : [],
|
|
68
|
+
...hasProps || hasEvents ? ["onMounted"] : [],
|
|
69
|
+
...hasProps ? ["onUpdated"] : [],
|
|
70
|
+
"ref"
|
|
71
|
+
].join(",\n ")},
|
|
72
|
+
} from 'vue';
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
import ${JSON.stringify(wcModuleId)};
|
|
75
|
+
${generateVueConstants(capabilities)}
|
|
76
|
+
export const ${component.name} = defineComponent({
|
|
77
|
+
name: ${JSON.stringify(component.name)},
|
|
78
|
+
inheritAttrs: false,
|
|
79
|
+
${hasProps ? `\n props: {\n ${generateVueProps(component)}\n },\n` : ""}
|
|
80
|
+
${hasEvents ? ` emits: ${models.length ? JSON.stringify(emitNames) : "EVENT_NAMES"},\n` : ""}
|
|
81
|
+
setup(${hasProps ? "props" : "_props"}, { attrs, slots${hasEvents ? ", emit" : ""} }) {
|
|
59
82
|
const elRef = ref(null);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
83
|
+
${generateVuePropSetup(hasProps)}
|
|
84
|
+
${generateVueEventSetup(hasEvents, models.length > 0)}
|
|
85
|
+
${generateVueMountHook(hasProps, hasEvents)}
|
|
86
|
+
${hasProps ? " onUpdated(syncProps);\n" : ""}
|
|
87
|
+
${generateVueUnmountHook(hasEvents)}
|
|
88
|
+
return () => {
|
|
89
|
+
${generateVueChildren(slotNames)}
|
|
90
|
+
const hostProps = Object.assign({}, attrs);
|
|
91
|
+
hostProps.ref = elRef;
|
|
65
92
|
|
|
66
|
-
${
|
|
93
|
+
return h(${JSON.stringify(component.tag)}, hostProps, children);
|
|
67
94
|
};
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
${hasProps ? VUE_PROP_HELPERS : ""}
|
|
98
|
+
${hasNamedSlots ? VUE_NAMED_SLOT_HELPERS : ""}
|
|
99
|
+
${models.length ? VUE_MODEL_HELPERS : ""}
|
|
100
|
+
`.trimStart();
|
|
101
|
+
}
|
|
102
|
+
function getCapabilities(component) {
|
|
103
|
+
var _component$models;
|
|
104
|
+
const models = ((_component$models = component.models) !== null && _component$models !== void 0 ? _component$models : []).map((model) => ({
|
|
105
|
+
event: model.event,
|
|
106
|
+
eventPath: model.eventPath,
|
|
107
|
+
updateEvent: `update:${model.prop}`
|
|
108
|
+
}));
|
|
109
|
+
return {
|
|
110
|
+
propNames: Object.keys(component.props),
|
|
111
|
+
eventNames: Array.from(new Set([...Object.entries(component.events).map(([key, event]) => {
|
|
112
|
+
var _event$name, _event$key;
|
|
113
|
+
return (_event$name = event.name) !== null && _event$name !== void 0 ? _event$name : toKebabCase((_event$key = event.key) !== null && _event$key !== void 0 ? _event$key : key);
|
|
114
|
+
}), ...models.map((model) => model.event)])),
|
|
115
|
+
models,
|
|
116
|
+
slotNames: getNamedSlots(component)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function getNamedSlots(component) {
|
|
120
|
+
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
121
|
+
}
|
|
122
|
+
function generateVueConstants(capabilities) {
|
|
123
|
+
const { eventNames, models, propNames, slotNames } = capabilities;
|
|
124
|
+
const lines = [];
|
|
125
|
+
if (propNames.length) {
|
|
126
|
+
lines.push(`const PROP_KEYS = ${JSON.stringify(propNames)};`);
|
|
127
|
+
lines.push(`const PROP_INPUT_KEYS = ${JSON.stringify(createVuePropInputKeys(propNames))};`);
|
|
128
|
+
lines.push("const EMPTY_PROPS = {};");
|
|
129
|
+
}
|
|
130
|
+
if (eventNames.length) lines.push(`const EVENT_NAMES = ${JSON.stringify(eventNames)};`);
|
|
131
|
+
if (models.length) lines.push(`const MODEL_BINDINGS = ${JSON.stringify(models)};`);
|
|
132
|
+
if (slotNames.length) lines.push(`const NAMED_SLOTS = ${JSON.stringify(slotNames)};`);
|
|
133
|
+
return lines.length ? `${lines.join("\n")}\n` : "";
|
|
134
|
+
}
|
|
135
|
+
function generateVuePropSetup(hasProps) {
|
|
136
|
+
if (!hasProps) return "";
|
|
137
|
+
return ` const instance = getCurrentInstance();
|
|
138
|
+
const syncedPropPresence = [];
|
|
139
|
+
const syncedPropValues = [];
|
|
68
140
|
|
|
69
|
-
|
|
70
|
-
syncProps();
|
|
71
|
-
|
|
141
|
+
const syncProps = () => {
|
|
72
142
|
const el = elRef.value;
|
|
73
143
|
if (!el) return;
|
|
74
144
|
|
|
75
|
-
|
|
76
|
-
const handler = event => emit(eventName, event);
|
|
77
|
-
el.addEventListener(eventName, handler);
|
|
78
|
-
cleanups.push(() => el.removeEventListener(eventName, handler));
|
|
79
|
-
}
|
|
80
|
-
});
|
|
145
|
+
const rawProps = instance?.vnode.props || EMPTY_PROPS;
|
|
81
146
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
cleanups.length = 0;
|
|
85
|
-
});
|
|
147
|
+
for (let index = 0; index < PROP_KEYS.length; index += 1) {
|
|
148
|
+
const name = PROP_KEYS[index];
|
|
86
149
|
|
|
87
|
-
|
|
150
|
+
if (hasRawProp(rawProps, name)) {
|
|
151
|
+
const nextValue = props[name];
|
|
152
|
+
if (
|
|
153
|
+
!syncedPropPresence[index] ||
|
|
154
|
+
!Object.is(syncedPropValues[index], nextValue)
|
|
155
|
+
) {
|
|
156
|
+
el[name] = nextValue;
|
|
157
|
+
syncedPropValues[index] = nextValue;
|
|
158
|
+
}
|
|
159
|
+
syncedPropPresence[index] = true;
|
|
160
|
+
} else if (syncedPropPresence[index]) {
|
|
161
|
+
el[name] = undefined;
|
|
162
|
+
syncedPropPresence[index] = false;
|
|
163
|
+
syncedPropValues[index] = undefined;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
function generateVueEventSetup(hasEvents, hasModels) {
|
|
170
|
+
if (!hasEvents) return "";
|
|
171
|
+
return ` const eventHandlers = EVENT_NAMES.map(eventName => event => {
|
|
172
|
+
emit(eventName, event);
|
|
173
|
+
${hasModels ? `
|
|
174
|
+
for (const model of MODEL_BINDINGS) {
|
|
175
|
+
if (model.event !== eventName) continue;
|
|
176
|
+
emit(model.updateEvent, readEventPath(event, model.eventPath));
|
|
177
|
+
}
|
|
178
|
+
` : ""} });
|
|
179
|
+
let mountedEl = null;
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
function generateVueMountHook(hasProps, hasEvents) {
|
|
183
|
+
if (!hasProps && !hasEvents) return "";
|
|
184
|
+
return ` onMounted(() => {
|
|
185
|
+
${hasProps ? " syncProps();\n" : ""}${hasEvents ? `
|
|
186
|
+
mountedEl = elRef.value;
|
|
187
|
+
if (!mountedEl) return;
|
|
88
188
|
|
|
89
|
-
|
|
90
|
-
|
|
189
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
190
|
+
mountedEl.addEventListener(EVENT_NAMES[index], eventHandlers[index]);
|
|
191
|
+
}
|
|
192
|
+
` : ""} });
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
function generateVueUnmountHook(hasEvents) {
|
|
196
|
+
if (!hasEvents) return "";
|
|
197
|
+
return ` onBeforeUnmount(() => {
|
|
198
|
+
if (!mountedEl) return;
|
|
91
199
|
|
|
92
|
-
|
|
93
|
-
|
|
200
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
201
|
+
mountedEl.removeEventListener(EVENT_NAMES[index], eventHandlers[index]);
|
|
94
202
|
}
|
|
203
|
+
mountedEl = null;
|
|
204
|
+
});
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
function generateVueChildren(slotNames) {
|
|
208
|
+
if (!slotNames.length) return ` const children = slots.default ? slots.default() : undefined;
|
|
209
|
+
`;
|
|
210
|
+
return ` const children = slots.default ? slots.default() : [];
|
|
95
211
|
|
|
96
212
|
for (const name of NAMED_SLOTS) {
|
|
97
213
|
const slot = slots[name];
|
|
@@ -101,29 +217,7 @@ export const ${component.name} = defineComponent({
|
|
|
101
217
|
children.push(withSlot(name, vnode));
|
|
102
218
|
}
|
|
103
219
|
}
|
|
104
|
-
|
|
105
|
-
return h(
|
|
106
|
-
${JSON.stringify(component.tag)},
|
|
107
|
-
{
|
|
108
|
-
...attrs,
|
|
109
|
-
ref: elRef,
|
|
110
|
-
},
|
|
111
|
-
children,
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
function withSlot(name, vnode) {
|
|
118
|
-
if (!vnode) return vnode;
|
|
119
|
-
|
|
120
|
-
if (typeof vnode === 'string') {
|
|
121
|
-
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return cloneVNode(vnode, { slot: name });
|
|
125
|
-
}
|
|
126
|
-
`.trimStart();
|
|
220
|
+
`;
|
|
127
221
|
}
|
|
128
222
|
function generateVueProps(component) {
|
|
129
223
|
return Object.entries(component.props).map(([name, prop]) => {
|
|
@@ -132,32 +226,70 @@ function generateVueProps(component) {
|
|
|
132
226
|
}
|
|
133
227
|
function toVuePropOption(prop) {
|
|
134
228
|
var _typeMap$prop$type;
|
|
135
|
-
|
|
229
|
+
return `{ type: ${(_typeMap$prop$type = {
|
|
136
230
|
string: "String",
|
|
137
231
|
number: "Number",
|
|
138
232
|
boolean: "Boolean",
|
|
139
233
|
object: "Object",
|
|
140
234
|
array: "Array",
|
|
235
|
+
function: "Function",
|
|
141
236
|
unknown: "null"
|
|
142
|
-
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"
|
|
143
|
-
|
|
144
|
-
|
|
237
|
+
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"}, required: ${prop.required === true ? "true" : "false"} }`;
|
|
238
|
+
}
|
|
239
|
+
function createVuePropInputKeys(propNames) {
|
|
240
|
+
return Object.fromEntries(propNames.map((name) => [name, Array.from(new Set([name, toKebabCase(name)]))]));
|
|
241
|
+
}
|
|
242
|
+
function toKebabCase(value) {
|
|
243
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
244
|
+
}
|
|
245
|
+
const VUE_PROP_HELPERS = `
|
|
246
|
+
function hasRawProp(rawProps, name) {
|
|
247
|
+
const keys = PROP_INPUT_KEYS[name];
|
|
248
|
+
for (const key of keys) {
|
|
249
|
+
if (hasOwn(rawProps, key)) return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function hasOwn(source, key) {
|
|
255
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
256
|
+
}
|
|
257
|
+
`;
|
|
258
|
+
const VUE_NAMED_SLOT_HELPERS = `
|
|
259
|
+
function withSlot(name, vnode) {
|
|
260
|
+
if (!vnode) return vnode;
|
|
261
|
+
|
|
262
|
+
if (typeof vnode === 'string') {
|
|
263
|
+
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return cloneVNode(vnode, { slot: name });
|
|
145
267
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
268
|
+
`;
|
|
269
|
+
const VUE_MODEL_HELPERS = `
|
|
270
|
+
function readEventPath(event, path) {
|
|
271
|
+
if (!path) return event.detail;
|
|
272
|
+
|
|
273
|
+
let value = event;
|
|
274
|
+
for (const segment of path.split('.')) {
|
|
275
|
+
if (value == null) return undefined;
|
|
276
|
+
value = value[segment];
|
|
277
|
+
}
|
|
278
|
+
return value;
|
|
149
279
|
}
|
|
280
|
+
`;
|
|
150
281
|
//#endregion
|
|
151
282
|
//#region packages/web-c/output-vue-wrapper/src/index.ts
|
|
152
283
|
function vueWrapper(options = {}) {
|
|
153
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index;
|
|
284
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index, _options$wrapper;
|
|
154
285
|
const normalized = {
|
|
155
286
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "vue",
|
|
156
287
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
157
288
|
fileName: options.fileName,
|
|
158
|
-
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts :
|
|
159
|
-
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts :
|
|
160
|
-
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true
|
|
289
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
290
|
+
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts : true,
|
|
291
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
292
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
161
293
|
};
|
|
162
294
|
return {
|
|
163
295
|
name: "zeus-output-vue-wrapper",
|
|
@@ -177,7 +309,8 @@ function vueWrapper(options = {}) {
|
|
|
177
309
|
fileName: ctx.outputs.join("vue", ctx.outputs.getFileName("vue", component.tag)),
|
|
178
310
|
code: generateVueWrapper({
|
|
179
311
|
component,
|
|
180
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
312
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
313
|
+
mode: normalized.wrapper
|
|
181
314
|
})
|
|
182
315
|
});
|
|
183
316
|
if (normalized.index) modules.push({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DtsMode, ZeusComponentPlugin } from '@zeus-js/bundler-plugin';
|
|
2
2
|
|
|
3
|
+
type VueWrapperMode = 'minimal' | 'event-bridge';
|
|
3
4
|
export interface OutputVueWrapperOptions {
|
|
4
5
|
/**
|
|
5
6
|
* Vue wrapper output directory.
|
|
@@ -20,13 +21,13 @@ export interface OutputVueWrapperOptions {
|
|
|
20
21
|
/**
|
|
21
22
|
* Generate vue/index.d.ts.
|
|
22
23
|
*
|
|
23
|
-
* @default
|
|
24
|
+
* @default true
|
|
24
25
|
*/
|
|
25
26
|
dts?: DtsMode;
|
|
26
27
|
/**
|
|
27
28
|
* Generate vue/global.d.ts.
|
|
28
29
|
*
|
|
29
|
-
* @default
|
|
30
|
+
* @default true
|
|
30
31
|
*/
|
|
31
32
|
globalDts?: DtsMode;
|
|
32
33
|
/**
|
|
@@ -35,6 +36,15 @@ export interface OutputVueWrapperOptions {
|
|
|
35
36
|
* @default true
|
|
36
37
|
*/
|
|
37
38
|
index?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* minimal:
|
|
41
|
+
* Default. Vue wrapper only renders the custom element tag.
|
|
42
|
+
* No watch, no prop sync, no event listeners.
|
|
43
|
+
*
|
|
44
|
+
* event-bridge:
|
|
45
|
+
* Additional mode for React CustomEvent bridging.
|
|
46
|
+
*/
|
|
47
|
+
wrapper?: VueWrapperMode;
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
export declare function vueWrapper(options?: OutputVueWrapperOptions): ZeusComponentPlugin;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* output-vue-wrapper v0.1.0-beta.
|
|
2
|
+
* output-vue-wrapper v0.1.0-beta.4
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -18,76 +18,192 @@ function generateVueIndex(components, options) {
|
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region packages/web-c/output-vue-wrapper/src/generateVueWrapper.ts
|
|
20
20
|
function generateVueWrapper(input) {
|
|
21
|
+
var _input$component$mode;
|
|
22
|
+
return input.mode === "event-bridge" || ((_input$component$mode = input.component.models) === null || _input$component$mode === void 0 ? void 0 : _input$component$mode.length) ? generateEventBridgeVueWrapper(input) : generateMinimalVueWrapper(input);
|
|
23
|
+
}
|
|
24
|
+
function generateMinimalVueWrapper(input) {
|
|
21
25
|
const { component, wcModuleId } = input;
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
25
|
-
const syncPropsBody = generateVuePropSyncLines(propNames);
|
|
26
|
-
const watchDeps = propNames.map((name) => `props.${name}`).join(", ");
|
|
27
|
-
const watchBlock = propNames.length > 0 ? `watch(() => [${watchDeps}], syncProps);` : `// no reactive props`;
|
|
26
|
+
const slotNames = getNamedSlots(component);
|
|
27
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
28
28
|
return `
|
|
29
|
-
import {
|
|
30
|
-
cloneVNode,
|
|
31
|
-
defineComponent,
|
|
32
|
-
h,
|
|
33
|
-
onBeforeUnmount,
|
|
34
|
-
onMounted,
|
|
35
|
-
ref,
|
|
36
|
-
watch,
|
|
37
|
-
} from 'vue';
|
|
29
|
+
import { ${hasNamedSlots ? "cloneVNode, defineComponent, h" : "defineComponent, h"} } from 'vue';
|
|
38
30
|
|
|
39
31
|
import ${JSON.stringify(wcModuleId)};
|
|
40
|
-
|
|
41
|
-
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
42
|
-
const EVENT_NAMES = ${JSON.stringify(eventNames)};
|
|
43
|
-
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
44
|
-
|
|
32
|
+
${hasNamedSlots ? `\nconst NAMED_SLOTS = ${JSON.stringify(slotNames)};\n` : ""}
|
|
45
33
|
export const ${component.name} = defineComponent({
|
|
46
34
|
name: ${JSON.stringify(component.name)},
|
|
35
|
+
inheritAttrs: false,
|
|
47
36
|
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
setup(_props, { attrs, slots }) {
|
|
38
|
+
return () => {
|
|
39
|
+
${generateVueChildren(slotNames)}
|
|
40
|
+
return h(${JSON.stringify(component.tag)}, attrs, children);
|
|
41
|
+
};
|
|
50
42
|
},
|
|
43
|
+
});
|
|
44
|
+
${hasNamedSlots ? VUE_NAMED_SLOT_HELPERS : ""}
|
|
45
|
+
`.trimStart();
|
|
46
|
+
}
|
|
47
|
+
function generateEventBridgeVueWrapper(input) {
|
|
48
|
+
const { component, wcModuleId } = input;
|
|
49
|
+
const capabilities = getCapabilities(component);
|
|
50
|
+
const { eventNames, models, propNames, slotNames } = capabilities;
|
|
51
|
+
if (!propNames.length && !eventNames.length) return generateMinimalVueWrapper(input);
|
|
52
|
+
const hasProps = propNames.length > 0;
|
|
53
|
+
const hasEvents = eventNames.length > 0;
|
|
54
|
+
const emitNames = Array.from(new Set([...eventNames, ...models.map((model) => model.updateEvent)]));
|
|
55
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
56
|
+
return `
|
|
57
|
+
import {
|
|
58
|
+
${[
|
|
59
|
+
...hasNamedSlots ? ["cloneVNode"] : [],
|
|
60
|
+
"defineComponent",
|
|
61
|
+
...hasProps ? ["getCurrentInstance"] : [],
|
|
62
|
+
"h",
|
|
63
|
+
...hasEvents ? ["onBeforeUnmount"] : [],
|
|
64
|
+
...hasProps || hasEvents ? ["onMounted"] : [],
|
|
65
|
+
...hasProps ? ["onUpdated"] : [],
|
|
66
|
+
"ref"
|
|
67
|
+
].join(",\n ")},
|
|
68
|
+
} from 'vue';
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
70
|
+
import ${JSON.stringify(wcModuleId)};
|
|
71
|
+
${generateVueConstants(capabilities)}
|
|
72
|
+
export const ${component.name} = defineComponent({
|
|
73
|
+
name: ${JSON.stringify(component.name)},
|
|
74
|
+
inheritAttrs: false,
|
|
75
|
+
${hasProps ? `\n props: {\n ${generateVueProps(component)}\n },\n` : ""}
|
|
76
|
+
${hasEvents ? ` emits: ${models.length ? JSON.stringify(emitNames) : "EVENT_NAMES"},\n` : ""}
|
|
77
|
+
setup(${hasProps ? "props" : "_props"}, { attrs, slots${hasEvents ? ", emit" : ""} }) {
|
|
55
78
|
const elRef = ref(null);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
${generateVuePropSetup(hasProps)}
|
|
80
|
+
${generateVueEventSetup(hasEvents, models.length > 0)}
|
|
81
|
+
${generateVueMountHook(hasProps, hasEvents)}
|
|
82
|
+
${hasProps ? " onUpdated(syncProps);\n" : ""}
|
|
83
|
+
${generateVueUnmountHook(hasEvents)}
|
|
84
|
+
return () => {
|
|
85
|
+
${generateVueChildren(slotNames)}
|
|
86
|
+
const hostProps = Object.assign({}, attrs);
|
|
87
|
+
hostProps.ref = elRef;
|
|
61
88
|
|
|
62
|
-
${
|
|
89
|
+
return h(${JSON.stringify(component.tag)}, hostProps, children);
|
|
63
90
|
};
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
${hasProps ? VUE_PROP_HELPERS : ""}
|
|
94
|
+
${hasNamedSlots ? VUE_NAMED_SLOT_HELPERS : ""}
|
|
95
|
+
${models.length ? VUE_MODEL_HELPERS : ""}
|
|
96
|
+
`.trimStart();
|
|
97
|
+
}
|
|
98
|
+
function getCapabilities(component) {
|
|
99
|
+
var _component$models;
|
|
100
|
+
const models = ((_component$models = component.models) !== null && _component$models !== void 0 ? _component$models : []).map((model) => ({
|
|
101
|
+
event: model.event,
|
|
102
|
+
eventPath: model.eventPath,
|
|
103
|
+
updateEvent: `update:${model.prop}`
|
|
104
|
+
}));
|
|
105
|
+
return {
|
|
106
|
+
propNames: Object.keys(component.props),
|
|
107
|
+
eventNames: Array.from(new Set([...Object.entries(component.events).map(([key, event]) => {
|
|
108
|
+
var _event$name, _event$key;
|
|
109
|
+
return (_event$name = event.name) !== null && _event$name !== void 0 ? _event$name : toKebabCase((_event$key = event.key) !== null && _event$key !== void 0 ? _event$key : key);
|
|
110
|
+
}), ...models.map((model) => model.event)])),
|
|
111
|
+
models,
|
|
112
|
+
slotNames: getNamedSlots(component)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function getNamedSlots(component) {
|
|
116
|
+
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
117
|
+
}
|
|
118
|
+
function generateVueConstants(capabilities) {
|
|
119
|
+
const { eventNames, models, propNames, slotNames } = capabilities;
|
|
120
|
+
const lines = [];
|
|
121
|
+
if (propNames.length) {
|
|
122
|
+
lines.push(`const PROP_KEYS = ${JSON.stringify(propNames)};`);
|
|
123
|
+
lines.push(`const PROP_INPUT_KEYS = ${JSON.stringify(createVuePropInputKeys(propNames))};`);
|
|
124
|
+
lines.push("const EMPTY_PROPS = {};");
|
|
125
|
+
}
|
|
126
|
+
if (eventNames.length) lines.push(`const EVENT_NAMES = ${JSON.stringify(eventNames)};`);
|
|
127
|
+
if (models.length) lines.push(`const MODEL_BINDINGS = ${JSON.stringify(models)};`);
|
|
128
|
+
if (slotNames.length) lines.push(`const NAMED_SLOTS = ${JSON.stringify(slotNames)};`);
|
|
129
|
+
return lines.length ? `${lines.join("\n")}\n` : "";
|
|
130
|
+
}
|
|
131
|
+
function generateVuePropSetup(hasProps) {
|
|
132
|
+
if (!hasProps) return "";
|
|
133
|
+
return ` const instance = getCurrentInstance();
|
|
134
|
+
const syncedPropPresence = [];
|
|
135
|
+
const syncedPropValues = [];
|
|
64
136
|
|
|
65
|
-
|
|
66
|
-
syncProps();
|
|
67
|
-
|
|
137
|
+
const syncProps = () => {
|
|
68
138
|
const el = elRef.value;
|
|
69
139
|
if (!el) return;
|
|
70
140
|
|
|
71
|
-
|
|
72
|
-
const handler = event => emit(eventName, event);
|
|
73
|
-
el.addEventListener(eventName, handler);
|
|
74
|
-
cleanups.push(() => el.removeEventListener(eventName, handler));
|
|
75
|
-
}
|
|
76
|
-
});
|
|
141
|
+
const rawProps = instance?.vnode.props || EMPTY_PROPS;
|
|
77
142
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
cleanups.length = 0;
|
|
81
|
-
});
|
|
143
|
+
for (let index = 0; index < PROP_KEYS.length; index += 1) {
|
|
144
|
+
const name = PROP_KEYS[index];
|
|
82
145
|
|
|
83
|
-
|
|
146
|
+
if (hasRawProp(rawProps, name)) {
|
|
147
|
+
const nextValue = props[name];
|
|
148
|
+
if (
|
|
149
|
+
!syncedPropPresence[index] ||
|
|
150
|
+
!Object.is(syncedPropValues[index], nextValue)
|
|
151
|
+
) {
|
|
152
|
+
el[name] = nextValue;
|
|
153
|
+
syncedPropValues[index] = nextValue;
|
|
154
|
+
}
|
|
155
|
+
syncedPropPresence[index] = true;
|
|
156
|
+
} else if (syncedPropPresence[index]) {
|
|
157
|
+
el[name] = undefined;
|
|
158
|
+
syncedPropPresence[index] = false;
|
|
159
|
+
syncedPropValues[index] = undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
function generateVueEventSetup(hasEvents, hasModels) {
|
|
166
|
+
if (!hasEvents) return "";
|
|
167
|
+
return ` const eventHandlers = EVENT_NAMES.map(eventName => event => {
|
|
168
|
+
emit(eventName, event);
|
|
169
|
+
${hasModels ? `
|
|
170
|
+
for (const model of MODEL_BINDINGS) {
|
|
171
|
+
if (model.event !== eventName) continue;
|
|
172
|
+
emit(model.updateEvent, readEventPath(event, model.eventPath));
|
|
173
|
+
}
|
|
174
|
+
` : ""} });
|
|
175
|
+
let mountedEl = null;
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
function generateVueMountHook(hasProps, hasEvents) {
|
|
179
|
+
if (!hasProps && !hasEvents) return "";
|
|
180
|
+
return ` onMounted(() => {
|
|
181
|
+
${hasProps ? " syncProps();\n" : ""}${hasEvents ? `
|
|
182
|
+
mountedEl = elRef.value;
|
|
183
|
+
if (!mountedEl) return;
|
|
84
184
|
|
|
85
|
-
|
|
86
|
-
|
|
185
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
186
|
+
mountedEl.addEventListener(EVENT_NAMES[index], eventHandlers[index]);
|
|
187
|
+
}
|
|
188
|
+
` : ""} });
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
function generateVueUnmountHook(hasEvents) {
|
|
192
|
+
if (!hasEvents) return "";
|
|
193
|
+
return ` onBeforeUnmount(() => {
|
|
194
|
+
if (!mountedEl) return;
|
|
87
195
|
|
|
88
|
-
|
|
89
|
-
|
|
196
|
+
for (let index = 0; index < EVENT_NAMES.length; index += 1) {
|
|
197
|
+
mountedEl.removeEventListener(EVENT_NAMES[index], eventHandlers[index]);
|
|
90
198
|
}
|
|
199
|
+
mountedEl = null;
|
|
200
|
+
});
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
function generateVueChildren(slotNames) {
|
|
204
|
+
if (!slotNames.length) return ` const children = slots.default ? slots.default() : undefined;
|
|
205
|
+
`;
|
|
206
|
+
return ` const children = slots.default ? slots.default() : [];
|
|
91
207
|
|
|
92
208
|
for (const name of NAMED_SLOTS) {
|
|
93
209
|
const slot = slots[name];
|
|
@@ -97,29 +213,7 @@ export const ${component.name} = defineComponent({
|
|
|
97
213
|
children.push(withSlot(name, vnode));
|
|
98
214
|
}
|
|
99
215
|
}
|
|
100
|
-
|
|
101
|
-
return h(
|
|
102
|
-
${JSON.stringify(component.tag)},
|
|
103
|
-
{
|
|
104
|
-
...attrs,
|
|
105
|
-
ref: elRef,
|
|
106
|
-
},
|
|
107
|
-
children,
|
|
108
|
-
);
|
|
109
|
-
};
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
function withSlot(name, vnode) {
|
|
114
|
-
if (!vnode) return vnode;
|
|
115
|
-
|
|
116
|
-
if (typeof vnode === 'string') {
|
|
117
|
-
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return cloneVNode(vnode, { slot: name });
|
|
121
|
-
}
|
|
122
|
-
`.trimStart();
|
|
216
|
+
`;
|
|
123
217
|
}
|
|
124
218
|
function generateVueProps(component) {
|
|
125
219
|
return Object.entries(component.props).map(([name, prop]) => {
|
|
@@ -128,32 +222,70 @@ function generateVueProps(component) {
|
|
|
128
222
|
}
|
|
129
223
|
function toVuePropOption(prop) {
|
|
130
224
|
var _typeMap$prop$type;
|
|
131
|
-
|
|
225
|
+
return `{ type: ${(_typeMap$prop$type = {
|
|
132
226
|
string: "String",
|
|
133
227
|
number: "Number",
|
|
134
228
|
boolean: "Boolean",
|
|
135
229
|
object: "Object",
|
|
136
230
|
array: "Array",
|
|
231
|
+
function: "Function",
|
|
137
232
|
unknown: "null"
|
|
138
|
-
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"
|
|
139
|
-
|
|
140
|
-
|
|
233
|
+
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"}, required: ${prop.required === true ? "true" : "false"} }`;
|
|
234
|
+
}
|
|
235
|
+
function createVuePropInputKeys(propNames) {
|
|
236
|
+
return Object.fromEntries(propNames.map((name) => [name, Array.from(new Set([name, toKebabCase(name)]))]));
|
|
237
|
+
}
|
|
238
|
+
function toKebabCase(value) {
|
|
239
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
240
|
+
}
|
|
241
|
+
const VUE_PROP_HELPERS = `
|
|
242
|
+
function hasRawProp(rawProps, name) {
|
|
243
|
+
const keys = PROP_INPUT_KEYS[name];
|
|
244
|
+
for (const key of keys) {
|
|
245
|
+
if (hasOwn(rawProps, key)) return true;
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function hasOwn(source, key) {
|
|
251
|
+
return Object.prototype.hasOwnProperty.call(source, key);
|
|
252
|
+
}
|
|
253
|
+
`;
|
|
254
|
+
const VUE_NAMED_SLOT_HELPERS = `
|
|
255
|
+
function withSlot(name, vnode) {
|
|
256
|
+
if (!vnode) return vnode;
|
|
257
|
+
|
|
258
|
+
if (typeof vnode === 'string') {
|
|
259
|
+
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return cloneVNode(vnode, { slot: name });
|
|
141
263
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
264
|
+
`;
|
|
265
|
+
const VUE_MODEL_HELPERS = `
|
|
266
|
+
function readEventPath(event, path) {
|
|
267
|
+
if (!path) return event.detail;
|
|
268
|
+
|
|
269
|
+
let value = event;
|
|
270
|
+
for (const segment of path.split('.')) {
|
|
271
|
+
if (value == null) return undefined;
|
|
272
|
+
value = value[segment];
|
|
273
|
+
}
|
|
274
|
+
return value;
|
|
145
275
|
}
|
|
276
|
+
`;
|
|
146
277
|
//#endregion
|
|
147
278
|
//#region packages/web-c/output-vue-wrapper/src/index.ts
|
|
148
279
|
function vueWrapper(options = {}) {
|
|
149
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index;
|
|
280
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index, _options$wrapper;
|
|
150
281
|
const normalized = {
|
|
151
282
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "vue",
|
|
152
283
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
153
284
|
fileName: options.fileName,
|
|
154
|
-
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts :
|
|
155
|
-
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts :
|
|
156
|
-
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true
|
|
285
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
286
|
+
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts : true,
|
|
287
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
288
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
157
289
|
};
|
|
158
290
|
return {
|
|
159
291
|
name: "zeus-output-vue-wrapper",
|
|
@@ -173,7 +305,8 @@ function vueWrapper(options = {}) {
|
|
|
173
305
|
fileName: ctx.outputs.join("vue", ctx.outputs.getFileName("vue", component.tag)),
|
|
174
306
|
code: generateVueWrapper({
|
|
175
307
|
component,
|
|
176
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
308
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
309
|
+
mode: normalized.wrapper
|
|
177
310
|
})
|
|
178
311
|
});
|
|
179
312
|
if (normalized.index) modules.push({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeus-js/output-vue-wrapper",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "Zeus Vue wrapper output plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
15
|
"types": "./dist/output-vue-wrapper.d.ts",
|
|
16
|
+
"module": "./dist/output-vue-wrapper.esm-bundler.js",
|
|
17
|
+
"import": "./dist/output-vue-wrapper.esm-bundler.js",
|
|
18
|
+
"require": "./index.js",
|
|
16
19
|
"node": {
|
|
17
20
|
"production": "./dist/output-vue-wrapper.cjs.prod.js",
|
|
18
21
|
"development": "./dist/output-vue-wrapper.cjs.js",
|
|
19
22
|
"default": "./index.js"
|
|
20
|
-
}
|
|
21
|
-
"module": "./dist/output-vue-wrapper.esm-bundler.js",
|
|
22
|
-
"import": "./dist/output-vue-wrapper.esm-bundler.js",
|
|
23
|
-
"require": "./index.js"
|
|
23
|
+
}
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"sideEffects": false,
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@zeus-js/
|
|
40
|
-
"@zeus-js/component-
|
|
41
|
-
"@zeus-js/
|
|
39
|
+
"@zeus-js/bundler-plugin": "0.1.0-beta.4",
|
|
40
|
+
"@zeus-js/component-dts": "0.1.0-beta.4",
|
|
41
|
+
"@zeus-js/component-analyzer": "0.1.0-beta.4"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"vue": ">=3"
|