@zeus-js/output-react-wrapper 0.1.0-beta.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.
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.0
|
|
3
|
+
* (c) 2026 baicie
|
|
4
|
+
* Released under the MIT License.
|
|
5
|
+
**/
|
|
6
|
+
Object.defineProperties(exports, {
|
|
7
|
+
__esModule: { value: true },
|
|
8
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
9
|
+
});
|
|
10
|
+
let _zeus_js_bundler_plugin = require("@zeus-js/bundler-plugin");
|
|
11
|
+
let _zeus_js_component_dts = require("@zeus-js/component-dts");
|
|
12
|
+
//#region packages/web-c/output-react-wrapper/src/generateReactIndex.ts
|
|
13
|
+
function generateReactIndex(components, options) {
|
|
14
|
+
const lines = [];
|
|
15
|
+
for (const component of components) {
|
|
16
|
+
const fileName = options.getFileName(component.tag);
|
|
17
|
+
lines.push(`export { ${component.name} } from './${fileName}';`);
|
|
18
|
+
}
|
|
19
|
+
lines.push("");
|
|
20
|
+
return lines.join("\n");
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region packages/web-c/output-react-wrapper/src/naming.ts
|
|
24
|
+
function toReactEventProp(eventName) {
|
|
25
|
+
return "on" + eventName.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
29
|
+
function generateReactWrapper(input) {
|
|
30
|
+
const { component, namedSlots, wcModuleId } = input;
|
|
31
|
+
const propNames = Object.keys(component.props);
|
|
32
|
+
const eventNames = Object.keys(component.events);
|
|
33
|
+
const slotNames = getNamedSlots(component, namedSlots);
|
|
34
|
+
const eventPropNames = eventNames.map(toReactEventProp);
|
|
35
|
+
const destructuredPropNames = [
|
|
36
|
+
...propNames,
|
|
37
|
+
...eventPropNames,
|
|
38
|
+
...slotNames
|
|
39
|
+
];
|
|
40
|
+
const destructuredProps = destructuredPropNames.length ? `${destructuredPropNames.join(",\n ")},` : "";
|
|
41
|
+
return `
|
|
42
|
+
import React, {
|
|
43
|
+
forwardRef,
|
|
44
|
+
useEffect,
|
|
45
|
+
useImperativeHandle,
|
|
46
|
+
useRef,
|
|
47
|
+
} from 'react';
|
|
48
|
+
|
|
49
|
+
import ${JSON.stringify(wcModuleId)};
|
|
50
|
+
|
|
51
|
+
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
52
|
+
const EVENT_MAP = ${JSON.stringify(createReactEventMap(eventNames))};
|
|
53
|
+
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
54
|
+
|
|
55
|
+
export const ${component.name} = forwardRef(function ${component.name}(props, ref) {
|
|
56
|
+
const {
|
|
57
|
+
children,
|
|
58
|
+
className,
|
|
59
|
+
style,
|
|
60
|
+
${destructuredProps}
|
|
61
|
+
...rest
|
|
62
|
+
} = props;
|
|
63
|
+
|
|
64
|
+
const innerRef = useRef(null);
|
|
65
|
+
|
|
66
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
67
|
+
|
|
68
|
+
${generatePropSyncLines(propNames)}
|
|
69
|
+
|
|
70
|
+
${generateEventEffects(eventNames)}
|
|
71
|
+
|
|
72
|
+
const slotChildren = [];
|
|
73
|
+
|
|
74
|
+
${generateNamedSlotRenderLines(slotNames)}
|
|
75
|
+
|
|
76
|
+
if (children != null) {
|
|
77
|
+
slotChildren.push(children);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return React.createElement(
|
|
81
|
+
${JSON.stringify(component.tag)},
|
|
82
|
+
{
|
|
83
|
+
...rest,
|
|
84
|
+
ref: innerRef,
|
|
85
|
+
className,
|
|
86
|
+
style,
|
|
87
|
+
},
|
|
88
|
+
...slotChildren,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function createNamedSlot(name, value) {
|
|
93
|
+
if (value == null || value === false) return null;
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
React.isValidElement(value) &&
|
|
97
|
+
value.type !== React.Fragment
|
|
98
|
+
) {
|
|
99
|
+
return React.cloneElement(value, { slot: name });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return React.createElement(
|
|
103
|
+
'span',
|
|
104
|
+
{
|
|
105
|
+
slot: name,
|
|
106
|
+
style: { display: 'contents' },
|
|
107
|
+
},
|
|
108
|
+
value,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
`.trimStart();
|
|
112
|
+
}
|
|
113
|
+
function createReactEventMap(eventNames) {
|
|
114
|
+
const map = {};
|
|
115
|
+
for (const eventName of eventNames) map[toReactEventProp(eventName)] = eventName;
|
|
116
|
+
return map;
|
|
117
|
+
}
|
|
118
|
+
function generatePropSyncLines(propNames) {
|
|
119
|
+
if (!propNames.length) return "// no props";
|
|
120
|
+
return `useEffect(() => {
|
|
121
|
+
const el = innerRef.current;
|
|
122
|
+
if (!el) return;
|
|
123
|
+
|
|
124
|
+
${propNames.map((name) => `el.${name} = ${name};`).join("\n ")}
|
|
125
|
+
}, [${propNames.join(", ")}]);`;
|
|
126
|
+
}
|
|
127
|
+
function generateEventEffects(eventNames) {
|
|
128
|
+
return eventNames.map((eventName) => {
|
|
129
|
+
const propName = toReactEventProp(eventName);
|
|
130
|
+
return `
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const el = innerRef.current;
|
|
133
|
+
if (!el || !${propName}) return;
|
|
134
|
+
|
|
135
|
+
const handler = event => {
|
|
136
|
+
${propName}(event);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
el.addEventListener(${JSON.stringify(eventName)}, handler);
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
el.removeEventListener(${JSON.stringify(eventName)}, handler);
|
|
143
|
+
};
|
|
144
|
+
}, [${propName}]);
|
|
145
|
+
`;
|
|
146
|
+
}).join("");
|
|
147
|
+
}
|
|
148
|
+
function getNamedSlots(component, namedSlots) {
|
|
149
|
+
if (namedSlots === "none") return [];
|
|
150
|
+
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
151
|
+
}
|
|
152
|
+
function generateNamedSlotRenderLines(namedSlots) {
|
|
153
|
+
return namedSlots.map((name) => {
|
|
154
|
+
return `
|
|
155
|
+
{
|
|
156
|
+
const node = createNamedSlot(${JSON.stringify(name)}, ${name});
|
|
157
|
+
if (node != null) slotChildren.push(node);
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
}).join("");
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
164
|
+
function reactWrapper(options = {}) {
|
|
165
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots;
|
|
166
|
+
const normalized = {
|
|
167
|
+
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "react",
|
|
168
|
+
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
169
|
+
fileName: options.fileName,
|
|
170
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : "auto",
|
|
171
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
172
|
+
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props"
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
name: "zeus-output-react-wrapper",
|
|
176
|
+
external: ["react"],
|
|
177
|
+
setup(ctx) {
|
|
178
|
+
ctx.outputs.register("react", {
|
|
179
|
+
outDir: normalized.outDir,
|
|
180
|
+
stripPrefix: normalized.stripPrefix,
|
|
181
|
+
fileName: normalized.fileName ? (tag) => normalized.fileName(tag) : void 0
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
virtualModules(ctx) {
|
|
185
|
+
if (!ctx.outputs.has("wc")) ctx.error("[zeus-output-react-wrapper] react() requires wc() plugin.");
|
|
186
|
+
const modules = [];
|
|
187
|
+
for (const component of ctx.manifest.components) modules.push({
|
|
188
|
+
id: `zeus:react:${component.tag}`,
|
|
189
|
+
fileName: ctx.outputs.join("react", ctx.outputs.getFileName("react", component.tag)),
|
|
190
|
+
code: generateReactWrapper({
|
|
191
|
+
component,
|
|
192
|
+
namedSlots: normalized.namedSlots,
|
|
193
|
+
wcModuleId: `zeus:wc:${component.tag}`
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
if (normalized.index) modules.push({
|
|
197
|
+
id: "zeus:react:index",
|
|
198
|
+
fileName: ctx.outputs.join("react", "index.js"),
|
|
199
|
+
code: generateReactIndex(ctx.manifest.components, { getFileName: (tag) => ctx.outputs.getFileName("react", tag) })
|
|
200
|
+
});
|
|
201
|
+
return modules;
|
|
202
|
+
},
|
|
203
|
+
generateBundle(ctx) {
|
|
204
|
+
if (!(0, _zeus_js_bundler_plugin.resolvePluginDts)(normalized.dts, ctx)) return [];
|
|
205
|
+
return [{
|
|
206
|
+
type: "asset",
|
|
207
|
+
fileName: ctx.outputs.join("react", "index.d.ts"),
|
|
208
|
+
source: (0, _zeus_js_component_dts.generateReactDts)(ctx.manifest)
|
|
209
|
+
}];
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
exports.default = reactWrapper;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.0
|
|
3
|
+
* (c) 2026 baicie
|
|
4
|
+
* Released under the MIT License.
|
|
5
|
+
**/
|
|
6
|
+
Object.defineProperties(exports, {
|
|
7
|
+
__esModule: { value: true },
|
|
8
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
9
|
+
});
|
|
10
|
+
let _zeus_js_bundler_plugin = require("@zeus-js/bundler-plugin");
|
|
11
|
+
let _zeus_js_component_dts = require("@zeus-js/component-dts");
|
|
12
|
+
//#region packages/web-c/output-react-wrapper/src/generateReactIndex.ts
|
|
13
|
+
function generateReactIndex(components, options) {
|
|
14
|
+
const lines = [];
|
|
15
|
+
for (const component of components) {
|
|
16
|
+
const fileName = options.getFileName(component.tag);
|
|
17
|
+
lines.push(`export { ${component.name} } from './${fileName}';`);
|
|
18
|
+
}
|
|
19
|
+
lines.push("");
|
|
20
|
+
return lines.join("\n");
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region packages/web-c/output-react-wrapper/src/naming.ts
|
|
24
|
+
function toReactEventProp(eventName) {
|
|
25
|
+
return "on" + eventName.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
29
|
+
function generateReactWrapper(input) {
|
|
30
|
+
const { component, namedSlots, wcModuleId } = input;
|
|
31
|
+
const propNames = Object.keys(component.props);
|
|
32
|
+
const eventNames = Object.keys(component.events);
|
|
33
|
+
const slotNames = getNamedSlots(component, namedSlots);
|
|
34
|
+
const eventPropNames = eventNames.map(toReactEventProp);
|
|
35
|
+
const destructuredPropNames = [
|
|
36
|
+
...propNames,
|
|
37
|
+
...eventPropNames,
|
|
38
|
+
...slotNames
|
|
39
|
+
];
|
|
40
|
+
const destructuredProps = destructuredPropNames.length ? `${destructuredPropNames.join(",\n ")},` : "";
|
|
41
|
+
return `
|
|
42
|
+
import React, {
|
|
43
|
+
forwardRef,
|
|
44
|
+
useEffect,
|
|
45
|
+
useImperativeHandle,
|
|
46
|
+
useRef,
|
|
47
|
+
} from 'react';
|
|
48
|
+
|
|
49
|
+
import ${JSON.stringify(wcModuleId)};
|
|
50
|
+
|
|
51
|
+
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
52
|
+
const EVENT_MAP = ${JSON.stringify(createReactEventMap(eventNames))};
|
|
53
|
+
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
54
|
+
|
|
55
|
+
export const ${component.name} = forwardRef(function ${component.name}(props, ref) {
|
|
56
|
+
const {
|
|
57
|
+
children,
|
|
58
|
+
className,
|
|
59
|
+
style,
|
|
60
|
+
${destructuredProps}
|
|
61
|
+
...rest
|
|
62
|
+
} = props;
|
|
63
|
+
|
|
64
|
+
const innerRef = useRef(null);
|
|
65
|
+
|
|
66
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
67
|
+
|
|
68
|
+
${generatePropSyncLines(propNames)}
|
|
69
|
+
|
|
70
|
+
${generateEventEffects(eventNames)}
|
|
71
|
+
|
|
72
|
+
const slotChildren = [];
|
|
73
|
+
|
|
74
|
+
${generateNamedSlotRenderLines(slotNames)}
|
|
75
|
+
|
|
76
|
+
if (children != null) {
|
|
77
|
+
slotChildren.push(children);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return React.createElement(
|
|
81
|
+
${JSON.stringify(component.tag)},
|
|
82
|
+
{
|
|
83
|
+
...rest,
|
|
84
|
+
ref: innerRef,
|
|
85
|
+
className,
|
|
86
|
+
style,
|
|
87
|
+
},
|
|
88
|
+
...slotChildren,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function createNamedSlot(name, value) {
|
|
93
|
+
if (value == null || value === false) return null;
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
React.isValidElement(value) &&
|
|
97
|
+
value.type !== React.Fragment
|
|
98
|
+
) {
|
|
99
|
+
return React.cloneElement(value, { slot: name });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return React.createElement(
|
|
103
|
+
'span',
|
|
104
|
+
{
|
|
105
|
+
slot: name,
|
|
106
|
+
style: { display: 'contents' },
|
|
107
|
+
},
|
|
108
|
+
value,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
`.trimStart();
|
|
112
|
+
}
|
|
113
|
+
function createReactEventMap(eventNames) {
|
|
114
|
+
const map = {};
|
|
115
|
+
for (const eventName of eventNames) map[toReactEventProp(eventName)] = eventName;
|
|
116
|
+
return map;
|
|
117
|
+
}
|
|
118
|
+
function generatePropSyncLines(propNames) {
|
|
119
|
+
if (!propNames.length) return "// no props";
|
|
120
|
+
return `useEffect(() => {
|
|
121
|
+
const el = innerRef.current;
|
|
122
|
+
if (!el) return;
|
|
123
|
+
|
|
124
|
+
${propNames.map((name) => `el.${name} = ${name};`).join("\n ")}
|
|
125
|
+
}, [${propNames.join(", ")}]);`;
|
|
126
|
+
}
|
|
127
|
+
function generateEventEffects(eventNames) {
|
|
128
|
+
return eventNames.map((eventName) => {
|
|
129
|
+
const propName = toReactEventProp(eventName);
|
|
130
|
+
return `
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const el = innerRef.current;
|
|
133
|
+
if (!el || !${propName}) return;
|
|
134
|
+
|
|
135
|
+
const handler = event => {
|
|
136
|
+
${propName}(event);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
el.addEventListener(${JSON.stringify(eventName)}, handler);
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
el.removeEventListener(${JSON.stringify(eventName)}, handler);
|
|
143
|
+
};
|
|
144
|
+
}, [${propName}]);
|
|
145
|
+
`;
|
|
146
|
+
}).join("");
|
|
147
|
+
}
|
|
148
|
+
function getNamedSlots(component, namedSlots) {
|
|
149
|
+
if (namedSlots === "none") return [];
|
|
150
|
+
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
151
|
+
}
|
|
152
|
+
function generateNamedSlotRenderLines(namedSlots) {
|
|
153
|
+
return namedSlots.map((name) => {
|
|
154
|
+
return `
|
|
155
|
+
{
|
|
156
|
+
const node = createNamedSlot(${JSON.stringify(name)}, ${name});
|
|
157
|
+
if (node != null) slotChildren.push(node);
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
}).join("");
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
164
|
+
function reactWrapper(options = {}) {
|
|
165
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots;
|
|
166
|
+
const normalized = {
|
|
167
|
+
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "react",
|
|
168
|
+
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
169
|
+
fileName: options.fileName,
|
|
170
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : "auto",
|
|
171
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
172
|
+
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props"
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
name: "zeus-output-react-wrapper",
|
|
176
|
+
external: ["react"],
|
|
177
|
+
setup(ctx) {
|
|
178
|
+
ctx.outputs.register("react", {
|
|
179
|
+
outDir: normalized.outDir,
|
|
180
|
+
stripPrefix: normalized.stripPrefix,
|
|
181
|
+
fileName: normalized.fileName ? (tag) => normalized.fileName(tag) : void 0
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
virtualModules(ctx) {
|
|
185
|
+
if (!ctx.outputs.has("wc")) ctx.error("[zeus-output-react-wrapper] react() requires wc() plugin.");
|
|
186
|
+
const modules = [];
|
|
187
|
+
for (const component of ctx.manifest.components) modules.push({
|
|
188
|
+
id: `zeus:react:${component.tag}`,
|
|
189
|
+
fileName: ctx.outputs.join("react", ctx.outputs.getFileName("react", component.tag)),
|
|
190
|
+
code: generateReactWrapper({
|
|
191
|
+
component,
|
|
192
|
+
namedSlots: normalized.namedSlots,
|
|
193
|
+
wcModuleId: `zeus:wc:${component.tag}`
|
|
194
|
+
})
|
|
195
|
+
});
|
|
196
|
+
if (normalized.index) modules.push({
|
|
197
|
+
id: "zeus:react:index",
|
|
198
|
+
fileName: ctx.outputs.join("react", "index.js"),
|
|
199
|
+
code: generateReactIndex(ctx.manifest.components, { getFileName: (tag) => ctx.outputs.getFileName("react", tag) })
|
|
200
|
+
});
|
|
201
|
+
return modules;
|
|
202
|
+
},
|
|
203
|
+
generateBundle(ctx) {
|
|
204
|
+
if (!(0, _zeus_js_bundler_plugin.resolvePluginDts)(normalized.dts, ctx)) return [];
|
|
205
|
+
return [{
|
|
206
|
+
type: "asset",
|
|
207
|
+
fileName: ctx.outputs.join("react", "index.d.ts"),
|
|
208
|
+
source: (0, _zeus_js_component_dts.generateReactDts)(ctx.manifest)
|
|
209
|
+
}];
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
exports.default = reactWrapper;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { DtsMode, ZeusComponentPlugin } from '@zeus-js/bundler-plugin';
|
|
2
|
+
|
|
3
|
+
export interface OutputReactWrapperOptions {
|
|
4
|
+
/**
|
|
5
|
+
* React wrapper output directory.
|
|
6
|
+
*
|
|
7
|
+
* @default 'react'
|
|
8
|
+
*/
|
|
9
|
+
outDir?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Strip tag prefix for file name.
|
|
12
|
+
*
|
|
13
|
+
* @default false
|
|
14
|
+
*/
|
|
15
|
+
stripPrefix?: string | false;
|
|
16
|
+
/**
|
|
17
|
+
* Custom file name.
|
|
18
|
+
*/
|
|
19
|
+
fileName?: (tag: string) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Generate react/index.d.ts.
|
|
22
|
+
*
|
|
23
|
+
* @default 'auto'
|
|
24
|
+
*/
|
|
25
|
+
dts?: DtsMode;
|
|
26
|
+
/**
|
|
27
|
+
* Generate react/index.js.
|
|
28
|
+
*
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
index?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Named slot strategy.
|
|
34
|
+
*
|
|
35
|
+
* props:
|
|
36
|
+
* <ZCard header={<div />} />
|
|
37
|
+
*
|
|
38
|
+
* none:
|
|
39
|
+
* only children/default slot
|
|
40
|
+
*
|
|
41
|
+
* @default 'props'
|
|
42
|
+
*/
|
|
43
|
+
namedSlots?: 'props' | 'none';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export declare function reactWrapper(options?: OutputReactWrapperOptions): ZeusComponentPlugin;
|
|
47
|
+
|
|
48
|
+
export { reactWrapper as default };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* output-react-wrapper v0.1.0-beta.0
|
|
3
|
+
* (c) 2026 baicie
|
|
4
|
+
* Released under the MIT License.
|
|
5
|
+
**/
|
|
6
|
+
import { resolvePluginDts } from "@zeus-js/bundler-plugin";
|
|
7
|
+
import { generateReactDts } from "@zeus-js/component-dts";
|
|
8
|
+
//#region packages/web-c/output-react-wrapper/src/generateReactIndex.ts
|
|
9
|
+
function generateReactIndex(components, options) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
for (const component of components) {
|
|
12
|
+
const fileName = options.getFileName(component.tag);
|
|
13
|
+
lines.push(`export { ${component.name} } from './${fileName}';`);
|
|
14
|
+
}
|
|
15
|
+
lines.push("");
|
|
16
|
+
return lines.join("\n");
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region packages/web-c/output-react-wrapper/src/naming.ts
|
|
20
|
+
function toReactEventProp(eventName) {
|
|
21
|
+
return "on" + eventName.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region packages/web-c/output-react-wrapper/src/generateReactWrapper.ts
|
|
25
|
+
function generateReactWrapper(input) {
|
|
26
|
+
const { component, namedSlots, wcModuleId } = input;
|
|
27
|
+
const propNames = Object.keys(component.props);
|
|
28
|
+
const eventNames = Object.keys(component.events);
|
|
29
|
+
const slotNames = getNamedSlots(component, namedSlots);
|
|
30
|
+
const eventPropNames = eventNames.map(toReactEventProp);
|
|
31
|
+
const destructuredPropNames = [
|
|
32
|
+
...propNames,
|
|
33
|
+
...eventPropNames,
|
|
34
|
+
...slotNames
|
|
35
|
+
];
|
|
36
|
+
const destructuredProps = destructuredPropNames.length ? `${destructuredPropNames.join(",\n ")},` : "";
|
|
37
|
+
return `
|
|
38
|
+
import React, {
|
|
39
|
+
forwardRef,
|
|
40
|
+
useEffect,
|
|
41
|
+
useImperativeHandle,
|
|
42
|
+
useRef,
|
|
43
|
+
} from 'react';
|
|
44
|
+
|
|
45
|
+
import ${JSON.stringify(wcModuleId)};
|
|
46
|
+
|
|
47
|
+
const PROP_KEYS = ${JSON.stringify(propNames)};
|
|
48
|
+
const EVENT_MAP = ${JSON.stringify(createReactEventMap(eventNames))};
|
|
49
|
+
const NAMED_SLOTS = ${JSON.stringify(slotNames)};
|
|
50
|
+
|
|
51
|
+
export const ${component.name} = forwardRef(function ${component.name}(props, ref) {
|
|
52
|
+
const {
|
|
53
|
+
children,
|
|
54
|
+
className,
|
|
55
|
+
style,
|
|
56
|
+
${destructuredProps}
|
|
57
|
+
...rest
|
|
58
|
+
} = props;
|
|
59
|
+
|
|
60
|
+
const innerRef = useRef(null);
|
|
61
|
+
|
|
62
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
63
|
+
|
|
64
|
+
${generatePropSyncLines(propNames)}
|
|
65
|
+
|
|
66
|
+
${generateEventEffects(eventNames)}
|
|
67
|
+
|
|
68
|
+
const slotChildren = [];
|
|
69
|
+
|
|
70
|
+
${generateNamedSlotRenderLines(slotNames)}
|
|
71
|
+
|
|
72
|
+
if (children != null) {
|
|
73
|
+
slotChildren.push(children);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return React.createElement(
|
|
77
|
+
${JSON.stringify(component.tag)},
|
|
78
|
+
{
|
|
79
|
+
...rest,
|
|
80
|
+
ref: innerRef,
|
|
81
|
+
className,
|
|
82
|
+
style,
|
|
83
|
+
},
|
|
84
|
+
...slotChildren,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
function createNamedSlot(name, value) {
|
|
89
|
+
if (value == null || value === false) return null;
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
React.isValidElement(value) &&
|
|
93
|
+
value.type !== React.Fragment
|
|
94
|
+
) {
|
|
95
|
+
return React.cloneElement(value, { slot: name });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return React.createElement(
|
|
99
|
+
'span',
|
|
100
|
+
{
|
|
101
|
+
slot: name,
|
|
102
|
+
style: { display: 'contents' },
|
|
103
|
+
},
|
|
104
|
+
value,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
`.trimStart();
|
|
108
|
+
}
|
|
109
|
+
function createReactEventMap(eventNames) {
|
|
110
|
+
const map = {};
|
|
111
|
+
for (const eventName of eventNames) map[toReactEventProp(eventName)] = eventName;
|
|
112
|
+
return map;
|
|
113
|
+
}
|
|
114
|
+
function generatePropSyncLines(propNames) {
|
|
115
|
+
if (!propNames.length) return "// no props";
|
|
116
|
+
return `useEffect(() => {
|
|
117
|
+
const el = innerRef.current;
|
|
118
|
+
if (!el) return;
|
|
119
|
+
|
|
120
|
+
${propNames.map((name) => `el.${name} = ${name};`).join("\n ")}
|
|
121
|
+
}, [${propNames.join(", ")}]);`;
|
|
122
|
+
}
|
|
123
|
+
function generateEventEffects(eventNames) {
|
|
124
|
+
return eventNames.map((eventName) => {
|
|
125
|
+
const propName = toReactEventProp(eventName);
|
|
126
|
+
return `
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
const el = innerRef.current;
|
|
129
|
+
if (!el || !${propName}) return;
|
|
130
|
+
|
|
131
|
+
const handler = event => {
|
|
132
|
+
${propName}(event);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
el.addEventListener(${JSON.stringify(eventName)}, handler);
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
el.removeEventListener(${JSON.stringify(eventName)}, handler);
|
|
139
|
+
};
|
|
140
|
+
}, [${propName}]);
|
|
141
|
+
`;
|
|
142
|
+
}).join("");
|
|
143
|
+
}
|
|
144
|
+
function getNamedSlots(component, namedSlots) {
|
|
145
|
+
if (namedSlots === "none") return [];
|
|
146
|
+
return Object.keys(component.slots).filter((name) => name !== "default");
|
|
147
|
+
}
|
|
148
|
+
function generateNamedSlotRenderLines(namedSlots) {
|
|
149
|
+
return namedSlots.map((name) => {
|
|
150
|
+
return `
|
|
151
|
+
{
|
|
152
|
+
const node = createNamedSlot(${JSON.stringify(name)}, ${name});
|
|
153
|
+
if (node != null) slotChildren.push(node);
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
}).join("");
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region packages/web-c/output-react-wrapper/src/index.ts
|
|
160
|
+
function reactWrapper(options = {}) {
|
|
161
|
+
var _options$outDir, _options$stripPrefix, _options$dts, _options$index, _options$namedSlots;
|
|
162
|
+
const normalized = {
|
|
163
|
+
outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "react",
|
|
164
|
+
stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
|
|
165
|
+
fileName: options.fileName,
|
|
166
|
+
dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : "auto",
|
|
167
|
+
index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
|
|
168
|
+
namedSlots: (_options$namedSlots = options.namedSlots) !== null && _options$namedSlots !== void 0 ? _options$namedSlots : "props"
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
name: "zeus-output-react-wrapper",
|
|
172
|
+
external: ["react"],
|
|
173
|
+
setup(ctx) {
|
|
174
|
+
ctx.outputs.register("react", {
|
|
175
|
+
outDir: normalized.outDir,
|
|
176
|
+
stripPrefix: normalized.stripPrefix,
|
|
177
|
+
fileName: normalized.fileName ? (tag) => normalized.fileName(tag) : void 0
|
|
178
|
+
});
|
|
179
|
+
},
|
|
180
|
+
virtualModules(ctx) {
|
|
181
|
+
if (!ctx.outputs.has("wc")) ctx.error("[zeus-output-react-wrapper] react() requires wc() plugin.");
|
|
182
|
+
const modules = [];
|
|
183
|
+
for (const component of ctx.manifest.components) modules.push({
|
|
184
|
+
id: `zeus:react:${component.tag}`,
|
|
185
|
+
fileName: ctx.outputs.join("react", ctx.outputs.getFileName("react", component.tag)),
|
|
186
|
+
code: generateReactWrapper({
|
|
187
|
+
component,
|
|
188
|
+
namedSlots: normalized.namedSlots,
|
|
189
|
+
wcModuleId: `zeus:wc:${component.tag}`
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
if (normalized.index) modules.push({
|
|
193
|
+
id: "zeus:react:index",
|
|
194
|
+
fileName: ctx.outputs.join("react", "index.js"),
|
|
195
|
+
code: generateReactIndex(ctx.manifest.components, { getFileName: (tag) => ctx.outputs.getFileName("react", tag) })
|
|
196
|
+
});
|
|
197
|
+
return modules;
|
|
198
|
+
},
|
|
199
|
+
generateBundle(ctx) {
|
|
200
|
+
if (!resolvePluginDts(normalized.dts, ctx)) return [];
|
|
201
|
+
return [{
|
|
202
|
+
type: "asset",
|
|
203
|
+
fileName: ctx.outputs.join("react", "index.d.ts"),
|
|
204
|
+
source: generateReactDts(ctx.manifest)
|
|
205
|
+
}];
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
export { reactWrapper as default };
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zeus-js/output-react-wrapper",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Zeus React wrapper output plugin",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "dist/output-react-wrapper.esm-bundler.js",
|
|
8
|
+
"types": "dist/output-react-wrapper.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/output-react-wrapper.d.ts",
|
|
16
|
+
"node": {
|
|
17
|
+
"production": "./dist/output-react-wrapper.cjs.prod.js",
|
|
18
|
+
"development": "./dist/output-react-wrapper.cjs.js",
|
|
19
|
+
"default": "./index.js"
|
|
20
|
+
},
|
|
21
|
+
"module": "./dist/output-react-wrapper.esm-bundler.js",
|
|
22
|
+
"import": "./dist/output-react-wrapper.esm-bundler.js",
|
|
23
|
+
"require": "./index.js"
|
|
24
|
+
},
|
|
25
|
+
"./*": "./*"
|
|
26
|
+
},
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/baicie/zeus"
|
|
31
|
+
},
|
|
32
|
+
"buildOptions": {
|
|
33
|
+
"name": "ZeusOutputReactWrapper",
|
|
34
|
+
"formats": [
|
|
35
|
+
"esm-bundler",
|
|
36
|
+
"cjs"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@zeus-js/bundler-plugin": "0.1.0-beta.0",
|
|
41
|
+
"@zeus-js/component-analyzer": "0.1.0-beta.0",
|
|
42
|
+
"@zeus-js/component-dts": "0.1.0-beta.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": ">=18 || >=19"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"react": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"keywords": [
|
|
53
|
+
"zeus",
|
|
54
|
+
"react",
|
|
55
|
+
"web-components"
|
|
56
|
+
],
|
|
57
|
+
"author": "Baicie",
|
|
58
|
+
"license": "MIT"
|
|
59
|
+
}
|