@zeus-js/output-vue-wrapper 0.1.0-beta.2 → 0.1.0-beta.3
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.3
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -22,32 +22,96 @@ 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
|
+
const { mode = "minimal" } = input;
|
|
26
|
+
if (mode === "minimal") return generateMinimalVueWrapper(input);
|
|
27
|
+
return generateEventBridgeVueWrapper(input);
|
|
28
|
+
}
|
|
29
|
+
function generateMinimalVueWrapper(input) {
|
|
30
|
+
const { component, wcModuleId } = input;
|
|
31
|
+
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
32
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
33
|
+
const vueImports = hasNamedSlots ? `cloneVNode, defineComponent, h` : `defineComponent, h`;
|
|
34
|
+
const namedSlotBlock = hasNamedSlots ? `
|
|
35
|
+
for (const name of NAMED_SLOTS) {
|
|
36
|
+
const slot = slots[name];
|
|
37
|
+
if (!slot) continue;
|
|
38
|
+
|
|
39
|
+
for (const vnode of slot()) {
|
|
40
|
+
children.push(withSlot(name, vnode));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
` : "";
|
|
44
|
+
const withSlotHelper = hasNamedSlots ? `
|
|
45
|
+
function withSlot(name, vnode) {
|
|
46
|
+
if (!vnode) return vnode;
|
|
47
|
+
|
|
48
|
+
if (typeof vnode === 'string') {
|
|
49
|
+
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return cloneVNode(vnode, { slot: name });
|
|
53
|
+
}
|
|
54
|
+
` : "";
|
|
55
|
+
return `
|
|
56
|
+
import { ${vueImports} } from 'vue';
|
|
57
|
+
|
|
58
|
+
import ${JSON.stringify(wcModuleId)};
|
|
59
|
+
|
|
60
|
+
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
61
|
+
|
|
62
|
+
export const ${component.name} = defineComponent({
|
|
63
|
+
name: ${JSON.stringify(component.name)},
|
|
64
|
+
inheritAttrs: false,
|
|
65
|
+
|
|
66
|
+
setup(_props, { attrs, slots }) {
|
|
67
|
+
return () => {
|
|
68
|
+
const children = [];
|
|
69
|
+
|
|
70
|
+
if (slots.default) {
|
|
71
|
+
children.push(...slots.default());
|
|
72
|
+
}
|
|
73
|
+
${namedSlotBlock}
|
|
74
|
+
return h(
|
|
75
|
+
${JSON.stringify(component.tag)},
|
|
76
|
+
{
|
|
77
|
+
...attrs,
|
|
78
|
+
},
|
|
79
|
+
children,
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
${withSlotHelper}
|
|
85
|
+
`.trimStart();
|
|
86
|
+
}
|
|
87
|
+
function generateEventBridgeVueWrapper(input) {
|
|
25
88
|
const { component, wcModuleId } = input;
|
|
26
89
|
const propNames = Object.keys(component.props);
|
|
27
90
|
const eventNames = Object.keys(component.events);
|
|
28
91
|
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
29
|
-
const
|
|
30
|
-
const watchDeps = propNames.map((name) => `props.${name}`).join(", ");
|
|
31
|
-
const watchBlock = propNames.length > 0 ? `watch(() => [${watchDeps}], syncProps);` : `// no reactive props`;
|
|
92
|
+
const propInputKeys = createVuePropInputKeys(propNames);
|
|
32
93
|
return `
|
|
33
94
|
import {
|
|
34
95
|
cloneVNode,
|
|
35
96
|
defineComponent,
|
|
97
|
+
getCurrentInstance,
|
|
36
98
|
h,
|
|
37
99
|
onBeforeUnmount,
|
|
38
100
|
onMounted,
|
|
101
|
+
onUpdated,
|
|
39
102
|
ref,
|
|
40
|
-
watch,
|
|
41
103
|
} from 'vue';
|
|
42
104
|
|
|
43
105
|
import ${JSON.stringify(wcModuleId)};
|
|
44
106
|
|
|
45
107
|
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
108
|
+
const PROP_INPUT_KEYS = ${JSON.stringify(propInputKeys)};
|
|
46
109
|
const EVENT_NAMES = ${JSON.stringify(eventNames)};
|
|
47
110
|
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
48
111
|
|
|
49
112
|
export const ${component.name} = defineComponent({
|
|
50
113
|
name: ${JSON.stringify(component.name)},
|
|
114
|
+
inheritAttrs: false,
|
|
51
115
|
|
|
52
116
|
props: {
|
|
53
117
|
${generateVueProps(component)}
|
|
@@ -57,13 +121,35 @@ export const ${component.name} = defineComponent({
|
|
|
57
121
|
|
|
58
122
|
setup(props, { attrs, slots, emit }) {
|
|
59
123
|
const elRef = ref(null);
|
|
124
|
+
const instance = getCurrentInstance();
|
|
60
125
|
const cleanups = [];
|
|
126
|
+
const syncedPropKeys = new Set();
|
|
127
|
+
|
|
128
|
+
const hasRawProp = name => {
|
|
129
|
+
const rawProps = instance?.vnode.props ?? {};
|
|
130
|
+
const keys = PROP_INPUT_KEYS[name] ?? [name];
|
|
131
|
+
|
|
132
|
+
return keys.some(key =>
|
|
133
|
+
Object.prototype.hasOwnProperty.call(rawProps, key),
|
|
134
|
+
);
|
|
135
|
+
};
|
|
61
136
|
|
|
62
137
|
const syncProps = () => {
|
|
63
138
|
const el = elRef.value;
|
|
64
139
|
if (!el) return;
|
|
65
140
|
|
|
66
|
-
|
|
141
|
+
for (const name of PROP_KEYS) {
|
|
142
|
+
if (hasRawProp(name)) {
|
|
143
|
+
el[name] = props[name];
|
|
144
|
+
syncedPropKeys.add(name);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (syncedPropKeys.has(name)) {
|
|
149
|
+
el[name] = undefined;
|
|
150
|
+
syncedPropKeys.delete(name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
67
153
|
};
|
|
68
154
|
|
|
69
155
|
onMounted(() => {
|
|
@@ -79,13 +165,14 @@ export const ${component.name} = defineComponent({
|
|
|
79
165
|
}
|
|
80
166
|
});
|
|
81
167
|
|
|
168
|
+
onUpdated(syncProps);
|
|
169
|
+
|
|
82
170
|
onBeforeUnmount(() => {
|
|
83
171
|
for (const cleanup of cleanups) cleanup();
|
|
84
172
|
cleanups.length = 0;
|
|
173
|
+
syncedPropKeys.clear();
|
|
85
174
|
});
|
|
86
175
|
|
|
87
|
-
${watchBlock}
|
|
88
|
-
|
|
89
176
|
return () => {
|
|
90
177
|
const children = [];
|
|
91
178
|
|
|
@@ -132,32 +219,33 @@ function generateVueProps(component) {
|
|
|
132
219
|
}
|
|
133
220
|
function toVuePropOption(prop) {
|
|
134
221
|
var _typeMap$prop$type;
|
|
135
|
-
|
|
222
|
+
return `{ type: ${(_typeMap$prop$type = {
|
|
136
223
|
string: "String",
|
|
137
224
|
number: "Number",
|
|
138
225
|
boolean: "Boolean",
|
|
139
226
|
object: "Object",
|
|
140
227
|
array: "Array",
|
|
141
228
|
unknown: "null"
|
|
142
|
-
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"
|
|
143
|
-
|
|
144
|
-
|
|
229
|
+
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"}, required: ${prop.required === true ? "true" : "false"} }`;
|
|
230
|
+
}
|
|
231
|
+
function createVuePropInputKeys(propNames) {
|
|
232
|
+
return Object.fromEntries(propNames.map((name) => [name, Array.from(new Set([name, toKebabCase(name)]))]));
|
|
145
233
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
return propNames.map((name) => `el.${name} = props.${name};`).join("\n ");
|
|
234
|
+
function toKebabCase(value) {
|
|
235
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
149
236
|
}
|
|
150
237
|
//#endregion
|
|
151
238
|
//#region packages/web-c/output-vue-wrapper/src/index.ts
|
|
152
239
|
function vueWrapper(options = {}) {
|
|
153
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index;
|
|
240
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index, _options$wrapper;
|
|
154
241
|
const normalized = {
|
|
155
242
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "vue",
|
|
156
243
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
157
244
|
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
|
|
245
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
246
|
+
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts : true,
|
|
247
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
248
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
161
249
|
};
|
|
162
250
|
return {
|
|
163
251
|
name: "zeus-output-vue-wrapper",
|
|
@@ -177,7 +265,8 @@ function vueWrapper(options = {}) {
|
|
|
177
265
|
fileName: ctx.outputs.join("vue", ctx.outputs.getFileName("vue", component.tag)),
|
|
178
266
|
code: generateVueWrapper({
|
|
179
267
|
component,
|
|
180
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
268
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
269
|
+
mode: normalized.wrapper
|
|
181
270
|
})
|
|
182
271
|
});
|
|
183
272
|
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.3
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -22,32 +22,96 @@ 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
|
+
const { mode = "minimal" } = input;
|
|
26
|
+
if (mode === "minimal") return generateMinimalVueWrapper(input);
|
|
27
|
+
return generateEventBridgeVueWrapper(input);
|
|
28
|
+
}
|
|
29
|
+
function generateMinimalVueWrapper(input) {
|
|
30
|
+
const { component, wcModuleId } = input;
|
|
31
|
+
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
32
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
33
|
+
const vueImports = hasNamedSlots ? `cloneVNode, defineComponent, h` : `defineComponent, h`;
|
|
34
|
+
const namedSlotBlock = hasNamedSlots ? `
|
|
35
|
+
for (const name of NAMED_SLOTS) {
|
|
36
|
+
const slot = slots[name];
|
|
37
|
+
if (!slot) continue;
|
|
38
|
+
|
|
39
|
+
for (const vnode of slot()) {
|
|
40
|
+
children.push(withSlot(name, vnode));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
` : "";
|
|
44
|
+
const withSlotHelper = hasNamedSlots ? `
|
|
45
|
+
function withSlot(name, vnode) {
|
|
46
|
+
if (!vnode) return vnode;
|
|
47
|
+
|
|
48
|
+
if (typeof vnode === 'string') {
|
|
49
|
+
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return cloneVNode(vnode, { slot: name });
|
|
53
|
+
}
|
|
54
|
+
` : "";
|
|
55
|
+
return `
|
|
56
|
+
import { ${vueImports} } from 'vue';
|
|
57
|
+
|
|
58
|
+
import ${JSON.stringify(wcModuleId)};
|
|
59
|
+
|
|
60
|
+
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
61
|
+
|
|
62
|
+
export const ${component.name} = defineComponent({
|
|
63
|
+
name: ${JSON.stringify(component.name)},
|
|
64
|
+
inheritAttrs: false,
|
|
65
|
+
|
|
66
|
+
setup(_props, { attrs, slots }) {
|
|
67
|
+
return () => {
|
|
68
|
+
const children = [];
|
|
69
|
+
|
|
70
|
+
if (slots.default) {
|
|
71
|
+
children.push(...slots.default());
|
|
72
|
+
}
|
|
73
|
+
${namedSlotBlock}
|
|
74
|
+
return h(
|
|
75
|
+
${JSON.stringify(component.tag)},
|
|
76
|
+
{
|
|
77
|
+
...attrs,
|
|
78
|
+
},
|
|
79
|
+
children,
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
${withSlotHelper}
|
|
85
|
+
`.trimStart();
|
|
86
|
+
}
|
|
87
|
+
function generateEventBridgeVueWrapper(input) {
|
|
25
88
|
const { component, wcModuleId } = input;
|
|
26
89
|
const propNames = Object.keys(component.props);
|
|
27
90
|
const eventNames = Object.keys(component.events);
|
|
28
91
|
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
29
|
-
const
|
|
30
|
-
const watchDeps = propNames.map((name) => `props.${name}`).join(", ");
|
|
31
|
-
const watchBlock = propNames.length > 0 ? `watch(() => [${watchDeps}], syncProps);` : `// no reactive props`;
|
|
92
|
+
const propInputKeys = createVuePropInputKeys(propNames);
|
|
32
93
|
return `
|
|
33
94
|
import {
|
|
34
95
|
cloneVNode,
|
|
35
96
|
defineComponent,
|
|
97
|
+
getCurrentInstance,
|
|
36
98
|
h,
|
|
37
99
|
onBeforeUnmount,
|
|
38
100
|
onMounted,
|
|
101
|
+
onUpdated,
|
|
39
102
|
ref,
|
|
40
|
-
watch,
|
|
41
103
|
} from 'vue';
|
|
42
104
|
|
|
43
105
|
import ${JSON.stringify(wcModuleId)};
|
|
44
106
|
|
|
45
107
|
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
108
|
+
const PROP_INPUT_KEYS = ${JSON.stringify(propInputKeys)};
|
|
46
109
|
const EVENT_NAMES = ${JSON.stringify(eventNames)};
|
|
47
110
|
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
48
111
|
|
|
49
112
|
export const ${component.name} = defineComponent({
|
|
50
113
|
name: ${JSON.stringify(component.name)},
|
|
114
|
+
inheritAttrs: false,
|
|
51
115
|
|
|
52
116
|
props: {
|
|
53
117
|
${generateVueProps(component)}
|
|
@@ -57,13 +121,35 @@ export const ${component.name} = defineComponent({
|
|
|
57
121
|
|
|
58
122
|
setup(props, { attrs, slots, emit }) {
|
|
59
123
|
const elRef = ref(null);
|
|
124
|
+
const instance = getCurrentInstance();
|
|
60
125
|
const cleanups = [];
|
|
126
|
+
const syncedPropKeys = new Set();
|
|
127
|
+
|
|
128
|
+
const hasRawProp = name => {
|
|
129
|
+
const rawProps = instance?.vnode.props ?? {};
|
|
130
|
+
const keys = PROP_INPUT_KEYS[name] ?? [name];
|
|
131
|
+
|
|
132
|
+
return keys.some(key =>
|
|
133
|
+
Object.prototype.hasOwnProperty.call(rawProps, key),
|
|
134
|
+
);
|
|
135
|
+
};
|
|
61
136
|
|
|
62
137
|
const syncProps = () => {
|
|
63
138
|
const el = elRef.value;
|
|
64
139
|
if (!el) return;
|
|
65
140
|
|
|
66
|
-
|
|
141
|
+
for (const name of PROP_KEYS) {
|
|
142
|
+
if (hasRawProp(name)) {
|
|
143
|
+
el[name] = props[name];
|
|
144
|
+
syncedPropKeys.add(name);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (syncedPropKeys.has(name)) {
|
|
149
|
+
el[name] = undefined;
|
|
150
|
+
syncedPropKeys.delete(name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
67
153
|
};
|
|
68
154
|
|
|
69
155
|
onMounted(() => {
|
|
@@ -79,13 +165,14 @@ export const ${component.name} = defineComponent({
|
|
|
79
165
|
}
|
|
80
166
|
});
|
|
81
167
|
|
|
168
|
+
onUpdated(syncProps);
|
|
169
|
+
|
|
82
170
|
onBeforeUnmount(() => {
|
|
83
171
|
for (const cleanup of cleanups) cleanup();
|
|
84
172
|
cleanups.length = 0;
|
|
173
|
+
syncedPropKeys.clear();
|
|
85
174
|
});
|
|
86
175
|
|
|
87
|
-
${watchBlock}
|
|
88
|
-
|
|
89
176
|
return () => {
|
|
90
177
|
const children = [];
|
|
91
178
|
|
|
@@ -132,32 +219,33 @@ function generateVueProps(component) {
|
|
|
132
219
|
}
|
|
133
220
|
function toVuePropOption(prop) {
|
|
134
221
|
var _typeMap$prop$type;
|
|
135
|
-
|
|
222
|
+
return `{ type: ${(_typeMap$prop$type = {
|
|
136
223
|
string: "String",
|
|
137
224
|
number: "Number",
|
|
138
225
|
boolean: "Boolean",
|
|
139
226
|
object: "Object",
|
|
140
227
|
array: "Array",
|
|
141
228
|
unknown: "null"
|
|
142
|
-
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"
|
|
143
|
-
|
|
144
|
-
|
|
229
|
+
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"}, required: ${prop.required === true ? "true" : "false"} }`;
|
|
230
|
+
}
|
|
231
|
+
function createVuePropInputKeys(propNames) {
|
|
232
|
+
return Object.fromEntries(propNames.map((name) => [name, Array.from(new Set([name, toKebabCase(name)]))]));
|
|
145
233
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
return propNames.map((name) => `el.${name} = props.${name};`).join("\n ");
|
|
234
|
+
function toKebabCase(value) {
|
|
235
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
149
236
|
}
|
|
150
237
|
//#endregion
|
|
151
238
|
//#region packages/web-c/output-vue-wrapper/src/index.ts
|
|
152
239
|
function vueWrapper(options = {}) {
|
|
153
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index;
|
|
240
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index, _options$wrapper;
|
|
154
241
|
const normalized = {
|
|
155
242
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "vue",
|
|
156
243
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
157
244
|
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
|
|
245
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
246
|
+
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts : true,
|
|
247
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
248
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
161
249
|
};
|
|
162
250
|
return {
|
|
163
251
|
name: "zeus-output-vue-wrapper",
|
|
@@ -177,7 +265,8 @@ function vueWrapper(options = {}) {
|
|
|
177
265
|
fileName: ctx.outputs.join("vue", ctx.outputs.getFileName("vue", component.tag)),
|
|
178
266
|
code: generateVueWrapper({
|
|
179
267
|
component,
|
|
180
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
268
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
269
|
+
mode: normalized.wrapper
|
|
181
270
|
})
|
|
182
271
|
});
|
|
183
272
|
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.3
|
|
3
3
|
* (c) 2026 baicie
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
**/
|
|
@@ -18,32 +18,96 @@ 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
|
+
const { mode = "minimal" } = input;
|
|
22
|
+
if (mode === "minimal") return generateMinimalVueWrapper(input);
|
|
23
|
+
return generateEventBridgeVueWrapper(input);
|
|
24
|
+
}
|
|
25
|
+
function generateMinimalVueWrapper(input) {
|
|
26
|
+
const { component, wcModuleId } = input;
|
|
27
|
+
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
28
|
+
const hasNamedSlots = slotNames.length > 0;
|
|
29
|
+
const vueImports = hasNamedSlots ? `cloneVNode, defineComponent, h` : `defineComponent, h`;
|
|
30
|
+
const namedSlotBlock = hasNamedSlots ? `
|
|
31
|
+
for (const name of NAMED_SLOTS) {
|
|
32
|
+
const slot = slots[name];
|
|
33
|
+
if (!slot) continue;
|
|
34
|
+
|
|
35
|
+
for (const vnode of slot()) {
|
|
36
|
+
children.push(withSlot(name, vnode));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
` : "";
|
|
40
|
+
const withSlotHelper = hasNamedSlots ? `
|
|
41
|
+
function withSlot(name, vnode) {
|
|
42
|
+
if (!vnode) return vnode;
|
|
43
|
+
|
|
44
|
+
if (typeof vnode === 'string') {
|
|
45
|
+
return h('span', { slot: name, style: 'display: contents' }, vnode);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return cloneVNode(vnode, { slot: name });
|
|
49
|
+
}
|
|
50
|
+
` : "";
|
|
51
|
+
return `
|
|
52
|
+
import { ${vueImports} } from 'vue';
|
|
53
|
+
|
|
54
|
+
import ${JSON.stringify(wcModuleId)};
|
|
55
|
+
|
|
56
|
+
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
57
|
+
|
|
58
|
+
export const ${component.name} = defineComponent({
|
|
59
|
+
name: ${JSON.stringify(component.name)},
|
|
60
|
+
inheritAttrs: false,
|
|
61
|
+
|
|
62
|
+
setup(_props, { attrs, slots }) {
|
|
63
|
+
return () => {
|
|
64
|
+
const children = [];
|
|
65
|
+
|
|
66
|
+
if (slots.default) {
|
|
67
|
+
children.push(...slots.default());
|
|
68
|
+
}
|
|
69
|
+
${namedSlotBlock}
|
|
70
|
+
return h(
|
|
71
|
+
${JSON.stringify(component.tag)},
|
|
72
|
+
{
|
|
73
|
+
...attrs,
|
|
74
|
+
},
|
|
75
|
+
children,
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
${withSlotHelper}
|
|
81
|
+
`.trimStart();
|
|
82
|
+
}
|
|
83
|
+
function generateEventBridgeVueWrapper(input) {
|
|
21
84
|
const { component, wcModuleId } = input;
|
|
22
85
|
const propNames = Object.keys(component.props);
|
|
23
86
|
const eventNames = Object.keys(component.events);
|
|
24
87
|
const slotNames = Object.keys(component.slots).filter((name) => name !== "default");
|
|
25
|
-
const
|
|
26
|
-
const watchDeps = propNames.map((name) => `props.${name}`).join(", ");
|
|
27
|
-
const watchBlock = propNames.length > 0 ? `watch(() => [${watchDeps}], syncProps);` : `// no reactive props`;
|
|
88
|
+
const propInputKeys = createVuePropInputKeys(propNames);
|
|
28
89
|
return `
|
|
29
90
|
import {
|
|
30
91
|
cloneVNode,
|
|
31
92
|
defineComponent,
|
|
93
|
+
getCurrentInstance,
|
|
32
94
|
h,
|
|
33
95
|
onBeforeUnmount,
|
|
34
96
|
onMounted,
|
|
97
|
+
onUpdated,
|
|
35
98
|
ref,
|
|
36
|
-
watch,
|
|
37
99
|
} from 'vue';
|
|
38
100
|
|
|
39
101
|
import ${JSON.stringify(wcModuleId)};
|
|
40
102
|
|
|
41
103
|
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
104
|
+
const PROP_INPUT_KEYS = ${JSON.stringify(propInputKeys)};
|
|
42
105
|
const EVENT_NAMES = ${JSON.stringify(eventNames)};
|
|
43
106
|
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
44
107
|
|
|
45
108
|
export const ${component.name} = defineComponent({
|
|
46
109
|
name: ${JSON.stringify(component.name)},
|
|
110
|
+
inheritAttrs: false,
|
|
47
111
|
|
|
48
112
|
props: {
|
|
49
113
|
${generateVueProps(component)}
|
|
@@ -53,13 +117,35 @@ export const ${component.name} = defineComponent({
|
|
|
53
117
|
|
|
54
118
|
setup(props, { attrs, slots, emit }) {
|
|
55
119
|
const elRef = ref(null);
|
|
120
|
+
const instance = getCurrentInstance();
|
|
56
121
|
const cleanups = [];
|
|
122
|
+
const syncedPropKeys = new Set();
|
|
123
|
+
|
|
124
|
+
const hasRawProp = name => {
|
|
125
|
+
const rawProps = instance?.vnode.props ?? {};
|
|
126
|
+
const keys = PROP_INPUT_KEYS[name] ?? [name];
|
|
127
|
+
|
|
128
|
+
return keys.some(key =>
|
|
129
|
+
Object.prototype.hasOwnProperty.call(rawProps, key),
|
|
130
|
+
);
|
|
131
|
+
};
|
|
57
132
|
|
|
58
133
|
const syncProps = () => {
|
|
59
134
|
const el = elRef.value;
|
|
60
135
|
if (!el) return;
|
|
61
136
|
|
|
62
|
-
|
|
137
|
+
for (const name of PROP_KEYS) {
|
|
138
|
+
if (hasRawProp(name)) {
|
|
139
|
+
el[name] = props[name];
|
|
140
|
+
syncedPropKeys.add(name);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (syncedPropKeys.has(name)) {
|
|
145
|
+
el[name] = undefined;
|
|
146
|
+
syncedPropKeys.delete(name);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
63
149
|
};
|
|
64
150
|
|
|
65
151
|
onMounted(() => {
|
|
@@ -75,13 +161,14 @@ export const ${component.name} = defineComponent({
|
|
|
75
161
|
}
|
|
76
162
|
});
|
|
77
163
|
|
|
164
|
+
onUpdated(syncProps);
|
|
165
|
+
|
|
78
166
|
onBeforeUnmount(() => {
|
|
79
167
|
for (const cleanup of cleanups) cleanup();
|
|
80
168
|
cleanups.length = 0;
|
|
169
|
+
syncedPropKeys.clear();
|
|
81
170
|
});
|
|
82
171
|
|
|
83
|
-
${watchBlock}
|
|
84
|
-
|
|
85
172
|
return () => {
|
|
86
173
|
const children = [];
|
|
87
174
|
|
|
@@ -128,32 +215,33 @@ function generateVueProps(component) {
|
|
|
128
215
|
}
|
|
129
216
|
function toVuePropOption(prop) {
|
|
130
217
|
var _typeMap$prop$type;
|
|
131
|
-
|
|
218
|
+
return `{ type: ${(_typeMap$prop$type = {
|
|
132
219
|
string: "String",
|
|
133
220
|
number: "Number",
|
|
134
221
|
boolean: "Boolean",
|
|
135
222
|
object: "Object",
|
|
136
223
|
array: "Array",
|
|
137
224
|
unknown: "null"
|
|
138
|
-
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"
|
|
139
|
-
|
|
140
|
-
|
|
225
|
+
}[prop.type]) !== null && _typeMap$prop$type !== void 0 ? _typeMap$prop$type : "null"}, required: ${prop.required === true ? "true" : "false"} }`;
|
|
226
|
+
}
|
|
227
|
+
function createVuePropInputKeys(propNames) {
|
|
228
|
+
return Object.fromEntries(propNames.map((name) => [name, Array.from(new Set([name, toKebabCase(name)]))]));
|
|
141
229
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
return propNames.map((name) => `el.${name} = props.${name};`).join("\n ");
|
|
230
|
+
function toKebabCase(value) {
|
|
231
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
145
232
|
}
|
|
146
233
|
//#endregion
|
|
147
234
|
//#region packages/web-c/output-vue-wrapper/src/index.ts
|
|
148
235
|
function vueWrapper(options = {}) {
|
|
149
|
-
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index;
|
|
236
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$globalDts, _options$index, _options$wrapper;
|
|
150
237
|
const normalized = {
|
|
151
238
|
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "vue",
|
|
152
239
|
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
153
240
|
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
|
|
241
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
|
|
242
|
+
globalDts: (_options$globalDts = options.globalDts) !== null && _options$globalDts !== void 0 ? _options$globalDts : true,
|
|
243
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
244
|
+
wrapper: (_options$wrapper = options.wrapper) !== null && _options$wrapper !== void 0 ? _options$wrapper : "minimal"
|
|
157
245
|
};
|
|
158
246
|
return {
|
|
159
247
|
name: "zeus-output-vue-wrapper",
|
|
@@ -173,7 +261,8 @@ function vueWrapper(options = {}) {
|
|
|
173
261
|
fileName: ctx.outputs.join("vue", ctx.outputs.getFileName("vue", component.tag)),
|
|
174
262
|
code: generateVueWrapper({
|
|
175
263
|
component,
|
|
176
|
-
wcModuleId: `zeus:wc:${component.tag}
|
|
264
|
+
wcModuleId: `zeus:wc:${component.tag}`,
|
|
265
|
+
mode: normalized.wrapper
|
|
177
266
|
})
|
|
178
267
|
});
|
|
179
268
|
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.3",
|
|
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.3",
|
|
40
|
+
"@zeus-js/component-dts": "0.1.0-beta.3",
|
|
41
|
+
"@zeus-js/component-analyzer": "0.1.0-beta.3"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"vue": ">=3"
|