@zeus-js/output-wc 0.1.0-beta.1 → 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-wc v0.1.0-beta.1
2
+ * output-wc v0.1.0-beta.3
3
3
  * (c) 2026 baicie
4
4
  * Released under the MIT License.
5
5
  **/
@@ -176,6 +176,136 @@ function getVirtualIndexId() {
176
176
  return "zeus:wc:index";
177
177
  }
178
178
  //#endregion
179
+ //#region packages/web-c/output-wc/src/generateLazyEntry.ts
180
+ function generateLazyEntry(options) {
181
+ const { component, outPath, sourceImport } = options;
182
+ const source = sourceImport !== null && sourceImport !== void 0 ? sourceImport : toRelativeImport(component.source, outPath);
183
+ return [
184
+ `import { mountElementDefinition } from "@zeus-js/runtime-dom";`,
185
+ `import { ${component.exportName} } from ${JSON.stringify(source)};`,
186
+ "",
187
+ `export function createComponent(hostRef) {`,
188
+ ` let mounted;`,
189
+ ` const mountState = {};`,
190
+ "",
191
+ ` return {`,
192
+ ` connected() {`,
193
+ ` if (mounted) return;`,
194
+ "",
195
+ ` mounted = mountElementDefinition(`,
196
+ ` ${component.exportName},`,
197
+ ` hostRef.host,`,
198
+ ` hostRef.values,`,
199
+ ` mountState,`,
200
+ ` );`,
201
+ ` },`,
202
+ "",
203
+ ` disconnected() {`,
204
+ ` mounted?.dispose();`,
205
+ ` mounted = undefined;`,
206
+ ` },`,
207
+ "",
208
+ ` propertyChanged(name, oldValue, newValue) {`,
209
+ ` mounted?.propertyChanged(name, oldValue, newValue);`,
210
+ ` },`,
211
+ ` };`,
212
+ `}`,
213
+ "",
214
+ `export default { createComponent };`,
215
+ ""
216
+ ].join("\n");
217
+ }
218
+ function toRelativeImport(source, outPath) {
219
+ const sourceParts = normalizePath(source).split("/");
220
+ const outParts = normalizePath(outPath).split("/");
221
+ let common = 0;
222
+ for (let i = 0; i < Math.min(sourceParts.length, outParts.length); i++) if (sourceParts[i] === outParts[i]) common++;
223
+ else break;
224
+ return [...outParts.slice(common).map(() => ".."), ...sourceParts.slice(common)].join("/");
225
+ }
226
+ //#endregion
227
+ //#region packages/web-c/output-wc/src/generateLazyManifest.ts
228
+ function generateLazyManifest(options) {
229
+ const { components, getEntryFileName } = options;
230
+ return `export const components = [
231
+ ${components.map((component) => {
232
+ var _component$runtimePro, _component$meta$shado, _component$meta;
233
+ const entryFile = getEntryFileName(component.tag).replace(/\\/g, "/");
234
+ const props = generatePropsArray((_component$runtimePro = component.runtimeProps) !== null && _component$runtimePro !== void 0 ? _component$runtimePro : component.props);
235
+ const importPath = entryFile.startsWith(".") ? JSON.stringify(entryFile) : JSON.stringify(`./${entryFile}`);
236
+ return ` {
237
+ tagName: ${JSON.stringify(component.tag)},
238
+ shadow: ${(_component$meta$shado = (_component$meta = component.meta) === null || _component$meta === void 0 ? void 0 : _component$meta.shadow) !== null && _component$meta$shado !== void 0 ? _component$meta$shado : false},
239
+ load: () => import(${importPath}),
240
+ props: ${props},
241
+ }`;
242
+ }).join(",\n")}
243
+ ];
244
+ `;
245
+ }
246
+ function generatePropsArray(props) {
247
+ const entries = Object.entries(props);
248
+ if (entries.length === 0) return "[]";
249
+ return `[\n${entries.map(([name, prop]) => {
250
+ const parts = [`name: ${JSON.stringify(name)}`];
251
+ if (!isAttributeBackedType(prop.type) || prop.attr === false) parts.push("attrName: false");
252
+ else {
253
+ var _prop$attr;
254
+ const attrName = (_prop$attr = prop.attr) !== null && _prop$attr !== void 0 ? _prop$attr : toKebabCase(name);
255
+ if (attrName !== toKebabCase(name)) parts.push(`attrName: ${JSON.stringify(attrName)}`);
256
+ }
257
+ parts.push(`type: ${JSON.stringify(prop.type)}`);
258
+ if (prop.reflect && isAttributeBackedType(prop.type)) parts.push("reflect: true");
259
+ return ` { ${parts.join(", ")} }`;
260
+ }).join(",\n")}\n ]`;
261
+ }
262
+ function isAttributeBackedType(type) {
263
+ return type === "string" || type === "number" || type === "boolean";
264
+ }
265
+ function toKebabCase(value) {
266
+ return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
267
+ }
268
+ //#endregion
269
+ //#region packages/web-c/output-wc/src/generateLoader.ts
270
+ function generateLoader() {
271
+ return `import { bootstrapLazy } from "@zeus-js/web-c-runtime";
272
+ import { components } from "./components.manifest.js";
273
+
274
+ let defined = false;
275
+
276
+ export function defineCustomElements() {
277
+ if (defined) {
278
+ return;
279
+ }
280
+
281
+ if (typeof customElements === "undefined") {
282
+ return;
283
+ }
284
+
285
+ bootstrapLazy(components);
286
+
287
+ defined = true;
288
+ }
289
+
290
+ export const defineLazyElements = defineCustomElements;
291
+ `;
292
+ }
293
+ function generateAutoEntry() {
294
+ return `import { defineCustomElements } from "./loader.js";
295
+
296
+ defineCustomElements();
297
+
298
+ export {};
299
+ `;
300
+ }
301
+ function generateLazyIndex() {
302
+ return `export {
303
+ defineCustomElements,
304
+ defineLazyElements,
305
+ } from "./loader.js";
306
+ `;
307
+ }
308
+ //#endregion
179
309
  //#region packages/web-c/output-wc/src/generateManifest.ts
180
310
  function generateZeusComponentsManifest(manifest) {
181
311
  return `${JSON.stringify(manifest, null, 2)}\n`;
@@ -183,17 +313,21 @@ function generateZeusComponentsManifest(manifest) {
183
313
  //#endregion
184
314
  //#region packages/web-c/output-wc/src/index.ts
185
315
  function wc(options = {}) {
186
- var _options$outDir, _options$stripPrefix, _options$manifestFile, _options$customElemen, _options$dts, _options$jsxDts, _options$index, _options$warnOnFileNa;
316
+ var _options$register, _options$outDir, _options$stripPrefix, _options$manifestFile, _options$customElemen, _options$dts, _options$jsxDts, _options$index, _options$warnOnFileNa, _options$auto, _options$entryFileNam;
317
+ const registerMode = (_options$register = options.register) !== null && _options$register !== void 0 ? _options$register : "lazy";
187
318
  const normalized = {
188
319
  outDir: (_options$outDir = options.outDir) !== null && _options$outDir !== void 0 ? _options$outDir : "wc",
189
320
  stripPrefix: (_options$stripPrefix = options.stripPrefix) !== null && _options$stripPrefix !== void 0 ? _options$stripPrefix : false,
190
321
  fileName: options.fileName,
191
- manifestFile: (_options$manifestFile = options.manifestFile) !== null && _options$manifestFile !== void 0 ? _options$manifestFile : "zeus.components.json",
192
- customElementsFile: (_options$customElemen = options.customElementsFile) !== null && _options$customElemen !== void 0 ? _options$customElemen : "custom-elements.json",
193
- dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : "auto",
194
- jsxDts: (_options$jsxDts = options.jsxDts) !== null && _options$jsxDts !== void 0 ? _options$jsxDts : "auto",
322
+ manifestFile: registerMode === "lazy" ? false : (_options$manifestFile = options.manifestFile) !== null && _options$manifestFile !== void 0 ? _options$manifestFile : "zeus.components.json",
323
+ customElementsFile: registerMode === "lazy" ? false : (_options$customElemen = options.customElementsFile) !== null && _options$customElemen !== void 0 ? _options$customElemen : "custom-elements.json",
324
+ dts: (_options$dts = options.dts) !== null && _options$dts !== void 0 ? _options$dts : true,
325
+ jsxDts: (_options$jsxDts = options.jsxDts) !== null && _options$jsxDts !== void 0 ? _options$jsxDts : true,
195
326
  index: (_options$index = options.index) !== null && _options$index !== void 0 ? _options$index : true,
196
- warnOnFileNameCollision: (_options$warnOnFileNa = options.warnOnFileNameCollision) !== null && _options$warnOnFileNa !== void 0 ? _options$warnOnFileNa : true
327
+ warnOnFileNameCollision: (_options$warnOnFileNa = options.warnOnFileNameCollision) !== null && _options$warnOnFileNa !== void 0 ? _options$warnOnFileNa : true,
328
+ register: registerMode,
329
+ auto: (_options$auto = options.auto) !== null && _options$auto !== void 0 ? _options$auto : true,
330
+ entryFileName: (_options$entryFileNam = options.entryFileName) !== null && _options$entryFileNam !== void 0 ? _options$entryFileNam : ((tag) => `${tag}.entry`)
197
331
  };
198
332
  let _outDir = normalized.outDir;
199
333
  return {
@@ -208,7 +342,32 @@ function wc(options = {}) {
208
342
  },
209
343
  buildStart(ctx) {
210
344
  if (!ctx.manifest) return;
211
- checkFileNameCollisions(ctx.manifest.components, normalized, { warn: ctx.warn });
345
+ if (normalized.register === "lazy") for (const component of ctx.manifest.components) {
346
+ var _component$runtimePro, _component$runtimePro2;
347
+ const diagnostics = (_component$runtimePro = component.runtimePropsDiagnostics) !== null && _component$runtimePro !== void 0 ? _component$runtimePro : [];
348
+ if (diagnostics.length > 0) ctx.error([`[zeus-output-wc] <${component.tag}> cannot be emitted with register:"lazy" because its runtime props are not statically analyzable.`, ...diagnostics.map((message) => `- ${message}`)].join("\n"));
349
+ const runtimeProps = (_component$runtimePro2 = component.runtimeProps) !== null && _component$runtimePro2 !== void 0 ? _component$runtimePro2 : {};
350
+ for (const [name, prop] of Object.entries(runtimeProps)) {
351
+ if (isAttributeBackedRuntimeProp(prop.type)) continue;
352
+ if (prop.attr !== void 0 && prop.attr !== false) ctx.error([
353
+ `[zeus-output-wc] <${component.tag}> prop "${name}" cannot use attr:${JSON.stringify(prop.attr)} with register:"lazy".`,
354
+ "Lazy Web-C only supports attributes for string, number and boolean props.",
355
+ "Use attr:false and pass object/array/function values as properties instead."
356
+ ].join("\n"));
357
+ if (prop.reflect) ctx.error([
358
+ `[zeus-output-wc] <${component.tag}> prop "${name}" cannot use reflect:true with register:"lazy".`,
359
+ "Lazy Web-C only reflects string, number and boolean props.",
360
+ "Use a string/number/boolean prop or remove reflect:true."
361
+ ].join("\n"));
362
+ }
363
+ }
364
+ checkFileNameCollisions(ctx.manifest.components, {
365
+ getFileName: (tag) => {
366
+ if (normalized.register === "lazy") return getLazyEntryFileName(normalized.entryFileName, tag);
367
+ return normalizeFileName(tag, normalized.stripPrefix, normalized.fileName);
368
+ },
369
+ warnOnFileNameCollision: normalized.warnOnFileNameCollision
370
+ }, { warn: ctx.warn });
212
371
  },
213
372
  virtualModules(ctx) {
214
373
  if (!ctx.manifest) return [];
@@ -222,7 +381,55 @@ function wc(options = {}) {
222
381
  if (hasOutputs) return ctx.outputs.join("wc", fileName);
223
382
  return node_path.default.posix.join(_outDir, fileName);
224
383
  };
225
- for (const component of ctx.manifest.components) {
384
+ const isLazy = normalized.register === "lazy";
385
+ if (isLazy) {
386
+ modules.push({
387
+ id: "zeus:wc:components.manifest",
388
+ fileName: joinPath("components.manifest.js"),
389
+ code: generateLazyManifest({
390
+ components: ctx.manifest.components,
391
+ getEntryFileName: (tag) => getLazyEntryFileName(normalized.entryFileName, tag)
392
+ })
393
+ });
394
+ modules.push({
395
+ id: "zeus:wc:loader",
396
+ fileName: joinPath("loader.js"),
397
+ code: generateLoader()
398
+ });
399
+ if (normalized.auto) modules.push({
400
+ id: "zeus:wc:auto",
401
+ fileName: joinPath("auto.js"),
402
+ code: generateAutoEntry()
403
+ });
404
+ }
405
+ for (const component of ctx.manifest.components) if (isLazy) {
406
+ /**
407
+ * Compatibility module consumed by Vue / React wrappers.
408
+ *
409
+ * It only registers lazy Proxy Elements.
410
+ * It does not import the real component implementation.
411
+ */
412
+ modules.push({
413
+ id: getVirtualComponentId(component),
414
+ code: `
415
+ import { defineCustomElements } from "zeus:wc:loader";
416
+
417
+ defineCustomElements();
418
+
419
+ export {};
420
+ `.trimStart()
421
+ });
422
+ const fileName = joinPath(getLazyEntryFileName(normalized.entryFileName, component.tag));
423
+ modules.push({
424
+ id: `zeus:wc:entry:${component.tag}`,
425
+ fileName,
426
+ code: generateLazyEntry({
427
+ component,
428
+ outPath: fileName,
429
+ sourceImport: toAbsoluteImportPath(ctx.root, component.source)
430
+ })
431
+ });
432
+ } else {
226
433
  const fileName = joinPath(getFileName(component.tag));
227
434
  modules.push({
228
435
  id: getVirtualComponentId(component),
@@ -233,7 +440,7 @@ function wc(options = {}) {
233
440
  })
234
441
  });
235
442
  }
236
- if (normalized.index) modules.push({
443
+ if (normalized.index && !isLazy) modules.push({
237
444
  id: getVirtualIndexId(),
238
445
  fileName: joinPath("index.js"),
239
446
  code: generateWCIndex({
@@ -241,16 +448,18 @@ function wc(options = {}) {
241
448
  getFileName
242
449
  })
243
450
  });
451
+ if (isLazy && normalized.index) modules.push({
452
+ id: getVirtualIndexId(),
453
+ fileName: joinPath("index.js"),
454
+ code: generateLazyIndex()
455
+ });
244
456
  return modules;
245
457
  },
246
458
  generateBundle(ctx) {
247
459
  if (!ctx.manifest) return [];
248
460
  const files = [];
249
- if (normalized.manifestFile) files.push({
250
- type: "asset",
251
- fileName: normalized.manifestFile,
252
- source: generateZeusComponentsManifest(ctx.manifest)
253
- });
461
+ const dts = (0, _zeus_js_bundler_plugin.resolvePluginDts)(normalized.dts, ctx);
462
+ const jsxDts = (0, _zeus_js_bundler_plugin.resolvePluginDts)(normalized.jsxDts, ctx);
254
463
  const hasOutputs = ctx.outputs.has("wc");
255
464
  const getFileName = (tag) => {
256
465
  if (hasOutputs) return ctx.outputs.getFileName("wc", tag);
@@ -260,7 +469,32 @@ function wc(options = {}) {
260
469
  if (hasOutputs) return ctx.outputs.join("wc", fileName);
261
470
  return node_path.default.posix.join(_outDir, fileName);
262
471
  };
263
- if (normalized.customElementsFile) files.push({
472
+ const isLazy = normalized.register === "lazy";
473
+ if (isLazy) {
474
+ if (dts) {
475
+ const loaderDts = (0, _zeus_js_component_dts.generateLoaderDts)(ctx.manifest);
476
+ files.push({
477
+ type: "asset",
478
+ fileName: joinPath("loader.d.ts"),
479
+ source: loaderDts
480
+ });
481
+ if (normalized.index) files.push({
482
+ type: "asset",
483
+ fileName: joinPath("index.d.ts"),
484
+ source: loaderDts
485
+ });
486
+ }
487
+ if (jsxDts) files.push({
488
+ type: "asset",
489
+ fileName: joinPath("types/jsx.d.ts"),
490
+ source: (0, _zeus_js_component_dts.generateWCJsxDts)(ctx.manifest)
491
+ });
492
+ } else if (normalized.manifestFile) files.push({
493
+ type: "asset",
494
+ fileName: normalized.manifestFile,
495
+ source: generateZeusComponentsManifest(ctx.manifest)
496
+ });
497
+ if (!isLazy && normalized.customElementsFile) files.push({
264
498
  type: "asset",
265
499
  fileName: normalized.customElementsFile,
266
500
  source: generateCustomElementsJson({
@@ -268,9 +502,7 @@ function wc(options = {}) {
268
502
  getModulePath: (component) => joinPath(getFileName(component.tag))
269
503
  })
270
504
  });
271
- const dts = (0, _zeus_js_bundler_plugin.resolvePluginDts)(normalized.dts, ctx);
272
- const jsxDts = (0, _zeus_js_bundler_plugin.resolvePluginDts)(normalized.jsxDts, ctx);
273
- if (dts || jsxDts) {
505
+ if (!isLazy && (dts || jsxDts)) {
274
506
  const dtsFiles = (0, _zeus_js_component_dts.generateWCDtsFiles)(ctx.manifest, {
275
507
  outDir: _outDir,
276
508
  stripPrefix: normalized.stripPrefix,
@@ -299,12 +531,22 @@ function normalizeFileName(tag, stripPrefix, fileName) {
299
531
  if (!name.endsWith(".js")) name = `${name}.js`;
300
532
  return name;
301
533
  }
534
+ function normalizeLazyEntryFileName(fileName) {
535
+ const normalized = fileName.replace(/\\/g, "/");
536
+ return normalized.endsWith(".js") ? normalized : `${normalized}.js`;
537
+ }
538
+ function getLazyEntryFileName(entryFileName, tag) {
539
+ return normalizeLazyEntryFileName(entryFileName(tag));
540
+ }
541
+ function isAttributeBackedRuntimeProp(type) {
542
+ return type === "string" || type === "number" || type === "boolean";
543
+ }
302
544
  function checkFileNameCollisions(components, options, reporter) {
303
545
  if (options.warnOnFileNameCollision === false) return;
304
546
  const map = /* @__PURE__ */ new Map();
305
547
  for (const component of components) {
306
548
  var _map$get;
307
- const fileName = normalizeFileName(component.tag, options.stripPrefix, options.fileName);
549
+ const fileName = options.getFileName(component.tag);
308
550
  const list = (_map$get = map.get(fileName)) !== null && _map$get !== void 0 ? _map$get : [];
309
551
  list.push(component);
310
552
  map.set(fileName, list);
@@ -1,5 +1,6 @@
1
1
  import { DtsMode, ZeusComponentPlugin } from '@zeus-js/bundler-plugin';
2
2
 
3
+ type WebCRegisterMode = 'lazy' | 'side-effect';
3
4
  export interface OutputWCOptions {
4
5
  /**
5
6
  * Web Component output directory.
@@ -35,13 +36,13 @@ export interface OutputWCOptions {
35
36
  /**
36
37
  * Generate WC d.ts.
37
38
  *
38
- * @default 'auto'
39
+ * @default true
39
40
  */
40
41
  dts?: DtsMode;
41
42
  /**
42
43
  * Generate JSX IntrinsicElements d.ts.
43
44
  *
44
- * @default 'auto'
45
+ * @default true
45
46
  */
46
47
  jsxDts?: DtsMode;
47
48
  /**
@@ -56,6 +57,30 @@ export interface OutputWCOptions {
56
57
  * @default true
57
58
  */
58
59
  warnOnFileNameCollision?: boolean;
60
+ /**
61
+ * lazy:
62
+ * Default. Generates Stencil-style lazy loader.
63
+ * On startup, registers lightweight ProxyClass; loads real component entry
64
+ * only when the element is connected to the DOM.
65
+ *
66
+ * side-effect:
67
+ * Immediately registers full components on import.
68
+ * Compatible with legacy behavior; not recommended as default.
69
+ */
70
+ register?: WebCRegisterMode;
71
+ /**
72
+ * Whether to generate the auto.js entry (lazy mode).
73
+ *
74
+ * @default true
75
+ */
76
+ auto?: boolean;
77
+ /**
78
+ * File name for lazy mode entry chunks.
79
+ * Receives the tag name, should return the file name (without .js).
80
+ *
81
+ * @default (tag) => `${tag}.entry`
82
+ */
83
+ entryFileName?: (tag: string) => string;
59
84
  }
60
85
 
61
86
  export declare function wc(options?: OutputWCOptions): ZeusComponentPlugin;