@weapp-vite/web 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.mjs CHANGED
@@ -8,6 +8,7 @@ import MagicString from "magic-string";
8
8
  import { dirname as dirname2, extname, join, normalize, posix, relative as relative2, resolve } from "pathe";
9
9
 
10
10
  // src/compiler/wxml.ts
11
+ import { readFileSync } from "fs";
11
12
  import { parseDocument } from "htmlparser2";
12
13
  import { dirname, relative } from "pathe";
13
14
 
@@ -74,6 +75,8 @@ function normalizeTagName(name) {
74
75
  return "select";
75
76
  case "block":
76
77
  return "#fragment";
78
+ case "slot":
79
+ return "slot";
77
80
  default:
78
81
  return name || "div";
79
82
  }
@@ -89,6 +92,13 @@ function normalizeAttributeName(name) {
89
92
  }
90
93
 
91
94
  // src/compiler/wxml.ts
95
+ function shouldMarkWxsImport(pathname) {
96
+ const lower = pathname.toLowerCase();
97
+ if (lower.endsWith(".wxs") || lower.endsWith(".wxs.ts") || lower.endsWith(".wxs.js")) {
98
+ return false;
99
+ }
100
+ return lower.endsWith(".ts") || lower.endsWith(".js");
101
+ }
92
102
  function isRenderableNode(node) {
93
103
  if (node.type === "directive" || node.type === "comment") {
94
104
  return false;
@@ -295,6 +305,140 @@ function buildExpression(parts, scopeVar, wxsVar) {
295
305
  });
296
306
  return `(${segments.join(" + ")})`;
297
307
  }
308
+ function hasTopLevelColon(expression) {
309
+ let depth = 0;
310
+ let inSingleQuote = false;
311
+ let inDoubleQuote = false;
312
+ let inTemplate = false;
313
+ let escaped = false;
314
+ let sawTopLevelQuestion = false;
315
+ for (let index = 0; index < expression.length; index += 1) {
316
+ const char = expression[index];
317
+ if (inSingleQuote) {
318
+ if (escaped) {
319
+ escaped = false;
320
+ continue;
321
+ }
322
+ if (char === "\\") {
323
+ escaped = true;
324
+ continue;
325
+ }
326
+ if (char === "'") {
327
+ inSingleQuote = false;
328
+ }
329
+ continue;
330
+ }
331
+ if (inDoubleQuote) {
332
+ if (escaped) {
333
+ escaped = false;
334
+ continue;
335
+ }
336
+ if (char === "\\") {
337
+ escaped = true;
338
+ continue;
339
+ }
340
+ if (char === '"') {
341
+ inDoubleQuote = false;
342
+ }
343
+ continue;
344
+ }
345
+ if (inTemplate) {
346
+ if (escaped) {
347
+ escaped = false;
348
+ continue;
349
+ }
350
+ if (char === "\\") {
351
+ escaped = true;
352
+ continue;
353
+ }
354
+ if (char === "`") {
355
+ inTemplate = false;
356
+ }
357
+ continue;
358
+ }
359
+ if (char === "'") {
360
+ inSingleQuote = true;
361
+ continue;
362
+ }
363
+ if (char === '"') {
364
+ inDoubleQuote = true;
365
+ continue;
366
+ }
367
+ if (char === "`") {
368
+ inTemplate = true;
369
+ continue;
370
+ }
371
+ if (char === "(" || char === "[" || char === "{") {
372
+ depth += 1;
373
+ continue;
374
+ }
375
+ if (char === ")" || char === "]" || char === "}") {
376
+ depth = Math.max(0, depth - 1);
377
+ continue;
378
+ }
379
+ if (depth !== 0) {
380
+ continue;
381
+ }
382
+ if (char === "?") {
383
+ sawTopLevelQuestion = true;
384
+ continue;
385
+ }
386
+ if (char === ":") {
387
+ return !sawTopLevelQuestion;
388
+ }
389
+ }
390
+ return false;
391
+ }
392
+ function shouldWrapShorthandObject(expression) {
393
+ const trimmed = expression.trim();
394
+ if (!trimmed) {
395
+ return false;
396
+ }
397
+ if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith("(")) {
398
+ return false;
399
+ }
400
+ return hasTopLevelColon(trimmed);
401
+ }
402
+ function buildTemplateDataExpression(raw, scopeVar, wxsVar) {
403
+ const trimmed = raw.trim();
404
+ const parts = parseInterpolations(trimmed);
405
+ if (parts.length === 1 && parts[0]?.type === "expr") {
406
+ const expr = parts[0].value.trim();
407
+ if (expr) {
408
+ const normalizedExpr = shouldWrapShorthandObject(expr) ? `{ ${expr} }` : expr;
409
+ return buildExpression([{ type: "expr", value: normalizedExpr }], scopeVar, wxsVar);
410
+ }
411
+ }
412
+ return buildExpression(parseInterpolations(raw), scopeVar, wxsVar);
413
+ }
414
+ function createDependencyContext() {
415
+ return {
416
+ warnings: [],
417
+ dependencies: [],
418
+ dependencySet: /* @__PURE__ */ new Set(),
419
+ visited: /* @__PURE__ */ new Set(),
420
+ active: /* @__PURE__ */ new Set(),
421
+ circularWarnings: /* @__PURE__ */ new Set()
422
+ };
423
+ }
424
+ function addDependency(value, context, direct) {
425
+ if (!context.dependencySet.has(value)) {
426
+ context.dependencySet.add(value);
427
+ context.dependencies.push(value);
428
+ direct?.push(value);
429
+ }
430
+ }
431
+ function warnReadTemplate(context, target) {
432
+ context.warnings.push(`[web] \u65E0\u6CD5\u8BFB\u53D6\u6A21\u677F\u4F9D\u8D56: ${target}`);
433
+ }
434
+ function warnCircularTemplate(context, from, target) {
435
+ const key = `${from}=>${target}`;
436
+ if (context.circularWarnings.has(key)) {
437
+ return;
438
+ }
439
+ context.circularWarnings.add(key);
440
+ context.warnings.push(`[web] WXML \u5FAA\u73AF\u5F15\u7528: ${from} -> ${target}`);
441
+ }
298
442
  function extractFor(attribs) {
299
443
  const expr = attribs["wx:for"];
300
444
  const itemName = attribs["wx:for-item"]?.trim() || "item";
@@ -339,6 +483,25 @@ function parseEventAttribute(name) {
339
483
  }
340
484
  return { prefix: name.slice(0, name.length - match[1].length), rawEvent: match[1] };
341
485
  }
486
+ function resolveComponentTagName(name, componentTags) {
487
+ if (!componentTags) {
488
+ return void 0;
489
+ }
490
+ return componentTags[name] ?? componentTags[name.toLowerCase()];
491
+ }
492
+ var PROPERTY_BIND_EXCLUSIONS = /* @__PURE__ */ new Set(["class", "style", "id", "slot"]);
493
+ function shouldBindAsProperty(name) {
494
+ if (PROPERTY_BIND_EXCLUSIONS.has(name)) {
495
+ return false;
496
+ }
497
+ if (name.startsWith("data-") || name.startsWith("aria-")) {
498
+ return false;
499
+ }
500
+ return true;
501
+ }
502
+ function normalizePropertyName(name) {
503
+ return name.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
504
+ }
342
505
  function renderAttributes(attribs, scopeVar, wxsVar, options) {
343
506
  let buffer = "";
344
507
  for (const [rawName, rawValue] of Object.entries(attribs)) {
@@ -357,24 +520,31 @@ function renderAttributes(attribs, scopeVar, wxsVar, options) {
357
520
  buffer += ` @${domEvent}=\${ctx.event(${JSON.stringify(event)}, ${handlerExpr}, ${scopeVar}, ${wxsVar}, ${JSON.stringify(flags)})}`;
358
521
  continue;
359
522
  }
360
- const name = normalizeAttributeName(rawName);
523
+ const useProperty = options?.preferProperty && shouldBindAsProperty(rawName);
524
+ const name = useProperty ? normalizePropertyName(rawName) : normalizeAttributeName(rawName);
361
525
  const expr = buildExpression(parseInterpolations(rawValue ?? ""), scopeVar, wxsVar);
362
- buffer += ` ${name}=\${${expr}}`;
526
+ buffer += ` ${useProperty ? "." : ""}${name}=\${${expr}}`;
363
527
  }
364
528
  return buffer;
365
529
  }
366
530
  var Renderer = class {
367
- renderNodes(nodes, scopeVar, wxsVar) {
531
+ renderNodes(nodes, scopeVar, wxsVar, componentTags) {
368
532
  const parts = [];
369
533
  for (let index = 0; index < nodes.length; index += 1) {
370
534
  const node = nodes[index];
371
535
  if (isConditionalElement(node)) {
372
- const { rendered, endIndex } = this.renderConditionalSequence(nodes, index, scopeVar, wxsVar);
536
+ const { rendered, endIndex } = this.renderConditionalSequence(
537
+ nodes,
538
+ index,
539
+ scopeVar,
540
+ wxsVar,
541
+ componentTags
542
+ );
373
543
  parts.push(rendered);
374
544
  index = endIndex;
375
545
  continue;
376
546
  }
377
- parts.push(this.renderNode(node, scopeVar, wxsVar));
547
+ parts.push(this.renderNode(node, scopeVar, wxsVar, componentTags));
378
548
  }
379
549
  if (parts.length === 0) {
380
550
  return '""';
@@ -384,7 +554,7 @@ var Renderer = class {
384
554
  }
385
555
  return `[${parts.join(", ")}]`;
386
556
  }
387
- renderConditionalSequence(nodes, startIndex, scopeVar, wxsVar) {
557
+ renderConditionalSequence(nodes, startIndex, scopeVar, wxsVar, componentTags) {
388
558
  const branches = [];
389
559
  let cursor = startIndex;
390
560
  while (cursor < nodes.length) {
@@ -410,44 +580,44 @@ var Renderer = class {
410
580
  if (!node) {
411
581
  return { rendered: '""', endIndex: startIndex };
412
582
  }
413
- return { rendered: this.renderNode(node, scopeVar, wxsVar), endIndex: startIndex };
583
+ return { rendered: this.renderNode(node, scopeVar, wxsVar, componentTags), endIndex: startIndex };
414
584
  }
415
585
  let expr = '""';
416
586
  for (let index = branches.length - 1; index >= 0; index -= 1) {
417
587
  const { node, attribs } = branches[index];
418
588
  const cleanedAttribs = stripControlAttributes(attribs);
419
589
  if ("wx:else" in attribs) {
420
- expr = this.renderElement(node, scopeVar, wxsVar, { overrideAttribs: cleanedAttribs });
590
+ expr = this.renderElement(node, scopeVar, wxsVar, componentTags, { overrideAttribs: cleanedAttribs });
421
591
  continue;
422
592
  }
423
593
  const conditionExpr = attribs["wx:if"] ?? attribs["wx:elif"] ?? "";
424
- const rendered = this.renderElement(node, scopeVar, wxsVar, { overrideAttribs: cleanedAttribs });
594
+ const rendered = this.renderElement(node, scopeVar, wxsVar, componentTags, { overrideAttribs: cleanedAttribs });
425
595
  const condition = buildExpression(parseInterpolations(conditionExpr), scopeVar, wxsVar);
426
596
  expr = `(${condition} ? ${rendered} : ${expr})`;
427
597
  }
428
598
  return { rendered: expr, endIndex: startIndex + branches.length - 1 };
429
599
  }
430
- renderNode(node, scopeVar, wxsVar) {
600
+ renderNode(node, scopeVar, wxsVar, componentTags) {
431
601
  if (node.type === "text") {
432
602
  const parts = parseInterpolations(node.data ?? "");
433
603
  return buildExpression(parts, scopeVar, wxsVar);
434
604
  }
435
605
  if (node.type === "element") {
436
606
  if (node.name === "template" && node.attribs?.is) {
437
- return this.renderTemplateInvoke(node, scopeVar, wxsVar);
607
+ return this.renderTemplateInvoke(node, scopeVar, wxsVar, componentTags);
438
608
  }
439
- return this.renderElement(node, scopeVar, wxsVar);
609
+ return this.renderElement(node, scopeVar, wxsVar, componentTags);
440
610
  }
441
611
  return '""';
442
612
  }
443
- renderTemplateInvoke(node, scopeVar, wxsVar) {
613
+ renderTemplateInvoke(node, scopeVar, wxsVar, _componentTags) {
444
614
  const attribs = node.attribs ?? {};
445
615
  const isExpr = buildExpression(parseInterpolations(attribs.is ?? ""), scopeVar, wxsVar);
446
- const dataExpr = attribs.data ? buildExpression(parseInterpolations(attribs.data), scopeVar, wxsVar) : void 0;
616
+ const dataExpr = attribs.data ? buildTemplateDataExpression(attribs.data, scopeVar, wxsVar) : void 0;
447
617
  const scopeExpr = dataExpr ? `ctx.mergeScope(${scopeVar}, ${dataExpr})` : scopeVar;
448
618
  return `ctx.renderTemplate(__templates, ${isExpr}, ${scopeExpr}, ctx)`;
449
619
  }
450
- renderElement(node, scopeVar, wxsVar, options = {}) {
620
+ renderElement(node, scopeVar, wxsVar, componentTags, options = {}) {
451
621
  const attribs = options.overrideAttribs ?? node.attribs ?? {};
452
622
  if (!options.skipFor) {
453
623
  const forInfo = extractFor(node.attribs ?? {});
@@ -461,19 +631,24 @@ var Renderer = class {
461
631
  node,
462
632
  "__scope",
463
633
  wxsVar,
634
+ componentTags,
464
635
  { skipFor: true, overrideAttribs: forInfo.restAttribs }
465
636
  );
466
637
  const keyExpr = `ctx.key(${JSON.stringify(forInfo.key ?? "")}, ${itemVar}, ${indexVar}, ${scopeExpr}, ${wxsVar})`;
467
638
  return `repeat(${listExpr}, (${itemVar}, ${indexVar}) => ${keyExpr}, (${itemVar}, ${indexVar}) => { const __scope = ${scopeExpr}; return ${itemRender}; })`;
468
639
  }
469
640
  }
470
- const tagName = normalizeTagName(node.name ?? "");
641
+ const customTag = resolveComponentTagName(node.name ?? "", componentTags);
642
+ const tagName = customTag ?? normalizeTagName(node.name ?? "");
471
643
  if (tagName === "#fragment") {
472
- return this.renderNodes(node.children ?? [], scopeVar, wxsVar);
644
+ return this.renderNodes(node.children ?? [], scopeVar, wxsVar, componentTags);
473
645
  }
474
- const attrs = renderAttributes(attribs, scopeVar, wxsVar, { skipControl: true });
646
+ const attrs = renderAttributes(attribs, scopeVar, wxsVar, {
647
+ skipControl: true,
648
+ preferProperty: Boolean(customTag)
649
+ });
475
650
  const childNodes = node.children ?? [];
476
- const children = childNodes.map((child) => `\${${this.renderNode(child, scopeVar, wxsVar)}}`).join("");
651
+ const children = childNodes.map((child) => `\${${this.renderNode(child, scopeVar, wxsVar, componentTags)}}`).join("");
477
652
  if (SELF_CLOSING_TAGS.has(tagName) && childNodes.length === 0) {
478
653
  return `html\`<${tagName}${attrs} />\``;
479
654
  }
@@ -493,29 +668,38 @@ function collectSpecialNodes(nodes, context) {
493
668
  });
494
669
  continue;
495
670
  }
496
- if (name === "import" && node.attribs?.src) {
671
+ if ((name === "import" || name === "wx-import") && node.attribs?.src) {
497
672
  const resolved = context.resolveTemplate(node.attribs.src);
498
673
  if (resolved) {
499
674
  context.imports.push({
500
675
  id: resolved,
501
676
  importName: `__wxml_import_${context.imports.length}`
502
677
  });
678
+ } else {
679
+ context.warnings.push(`[web] \u65E0\u6CD5\u89E3\u6790\u6A21\u677F\u4F9D\u8D56: ${node.attribs.src} (from ${context.sourceId})`);
503
680
  }
504
681
  continue;
505
682
  }
506
- if (name === "include" && node.attribs?.src) {
683
+ if ((name === "include" || name === "wx-include") && node.attribs?.src) {
507
684
  const resolved = context.resolveTemplate(node.attribs.src);
508
685
  if (resolved) {
509
686
  context.includes.push({
510
687
  id: resolved,
511
688
  importName: `__wxml_include_${context.includes.length}`
512
689
  });
690
+ } else {
691
+ context.warnings.push(`[web] \u65E0\u6CD5\u89E3\u6790\u6A21\u677F\u4F9D\u8D56: ${node.attribs.src} (from ${context.sourceId})`);
513
692
  }
514
693
  continue;
515
694
  }
516
695
  if (name === "wxs") {
517
696
  const moduleName = node.attribs?.module?.trim();
518
697
  if (moduleName) {
698
+ const previousSource = context.wxsModules.get(moduleName);
699
+ if (previousSource) {
700
+ context.warnings.push(`[web] WXS \u6A21\u5757\u540D\u91CD\u590D: ${moduleName} (from ${context.sourceId})`);
701
+ }
702
+ context.wxsModules.set(moduleName, context.sourceId);
519
703
  if (node.attribs?.src) {
520
704
  const resolved = context.resolveWxs(node.attribs.src);
521
705
  if (resolved) {
@@ -558,8 +742,50 @@ function normalizeTemplatePath(pathname) {
558
742
  return pathname.split("\\").join("/");
559
743
  }
560
744
  function compileWxml(options) {
745
+ const dependencyContext = options.dependencyContext ?? createDependencyContext();
746
+ const expandDependencies = options.expandDependencies ?? !options.dependencyContext;
747
+ const warnings = dependencyContext.warnings;
748
+ const expandDependencyTree = (dependencies2, importer) => {
749
+ for (const target of dependencies2) {
750
+ if (!target) {
751
+ continue;
752
+ }
753
+ if (dependencyContext.active.has(target)) {
754
+ warnCircularTemplate(dependencyContext, importer, target);
755
+ continue;
756
+ }
757
+ if (dependencyContext.visited.has(target)) {
758
+ continue;
759
+ }
760
+ dependencyContext.visited.add(target);
761
+ dependencyContext.active.add(target);
762
+ let source;
763
+ try {
764
+ source = readFileSync(target, "utf8");
765
+ } catch {
766
+ warnReadTemplate(dependencyContext, target);
767
+ dependencyContext.active.delete(target);
768
+ continue;
769
+ }
770
+ try {
771
+ const result = compileWxml({
772
+ id: target,
773
+ source,
774
+ resolveTemplatePath: options.resolveTemplatePath,
775
+ resolveWxsPath: options.resolveWxsPath,
776
+ dependencyContext,
777
+ expandDependencies: false
778
+ });
779
+ expandDependencyTree(result.dependencies, target);
780
+ } catch (error) {
781
+ if (error instanceof Error && error.message) {
782
+ warnings.push(`[web] \u65E0\u6CD5\u89E3\u6790\u6A21\u677F\u4F9D\u8D56: ${target} ${error.message}`);
783
+ }
784
+ }
785
+ dependencyContext.active.delete(target);
786
+ }
787
+ };
561
788
  let nodes = parseWxml(options.source);
562
- const warnings = [];
563
789
  let navigationBarAttrs;
564
790
  if (options.navigationBar) {
565
791
  const extracted = extractNavigationBarFromPageMeta(nodes);
@@ -573,11 +799,15 @@ function compileWxml(options) {
573
799
  const includes = [];
574
800
  const imports = [];
575
801
  const wxs = [];
802
+ const wxsModules = /* @__PURE__ */ new Map();
576
803
  const renderNodesList = collectSpecialNodes(nodes, {
577
804
  templates,
578
805
  includes,
579
806
  imports,
580
807
  wxs,
808
+ wxsModules,
809
+ warnings,
810
+ sourceId: options.id,
581
811
  resolveTemplate: (raw) => options.resolveTemplatePath(raw, options.id),
582
812
  resolveWxs: (raw) => options.resolveWxsPath(raw, options.id)
583
813
  });
@@ -594,22 +824,23 @@ function compileWxml(options) {
594
824
  `import { repeat } from 'lit/directives/repeat.js'`
595
825
  ];
596
826
  const bodyLines = [];
597
- const dependencies = [];
827
+ const directDependencies = [];
598
828
  for (const entry of imports) {
599
829
  const importPath = normalizeTemplatePath(toRelativeImport(options.id, entry.id));
600
830
  importLines.push(`import { templates as ${entry.importName} } from '${importPath}'`);
601
- dependencies.push(entry.id);
831
+ addDependency(entry.id, dependencyContext, directDependencies);
602
832
  }
603
833
  for (const entry of includes) {
604
834
  const importPath = normalizeTemplatePath(toRelativeImport(options.id, entry.id));
605
835
  importLines.push(`import { render as ${entry.importName} } from '${importPath}'`);
606
- dependencies.push(entry.id);
836
+ addDependency(entry.id, dependencyContext, directDependencies);
607
837
  }
608
838
  for (const entry of wxs) {
609
839
  if (entry.kind === "src") {
610
- const importPath = normalizeTemplatePath(toRelativeImport(options.id, entry.value));
840
+ const baseImport = normalizeTemplatePath(toRelativeImport(options.id, entry.value));
841
+ const importPath = shouldMarkWxsImport(entry.value) ? `${baseImport}?wxs` : baseImport;
611
842
  importLines.push(`import ${entry.importName} from '${importPath}'`);
612
- dependencies.push(entry.value);
843
+ addDependency(entry.value, dependencyContext, directDependencies);
613
844
  }
614
845
  }
615
846
  if (templates.length > 0 || imports.length > 0) {
@@ -618,7 +849,7 @@ function compileWxml(options) {
618
849
  templatePairs.push(`...${entry.importName}`);
619
850
  }
620
851
  for (const template of templates) {
621
- const rendered = renderer.renderNodes(template.nodes, "scope", "__wxs_modules");
852
+ const rendered = renderer.renderNodes(template.nodes, "scope", "__wxs_modules", options.componentTags);
622
853
  templatePairs.push(`${JSON.stringify(template.name)}: (scope, ctx) => ${rendered}`);
623
854
  }
624
855
  bodyLines.push(`const __templates = { ${templatePairs.join(", ")} }`);
@@ -655,7 +886,7 @@ function compileWxml(options) {
655
886
  bodyLines.push(`const __wxs_modules = {}`);
656
887
  }
657
888
  const includesRender = includes.map((entry) => `${entry.importName}(scope, ctx)`);
658
- const renderContent = renderer.renderNodes(renderNodesList, "scope", "__wxs_modules");
889
+ const renderContent = renderer.renderNodes(renderNodesList, "scope", "__wxs_modules", options.componentTags);
659
890
  const contentExpr = includesRender.length > 0 ? `[${[...includesRender, renderContent].join(", ")}]` : renderContent;
660
891
  bodyLines.push(`export function render(scope, ctx) {`);
661
892
  if (wxs.length > 0) {
@@ -665,7 +896,14 @@ function compileWxml(options) {
665
896
  bodyLines.push(`}`);
666
897
  bodyLines.push(`export const templates = __templates`);
667
898
  bodyLines.push(`export default render`);
899
+ if (expandDependencies) {
900
+ dependencyContext.visited.add(options.id);
901
+ dependencyContext.active.add(options.id);
902
+ expandDependencyTree(directDependencies, options.id);
903
+ dependencyContext.active.delete(options.id);
904
+ }
668
905
  const code = [...importLines, "", ...bodyLines].join("\n");
906
+ const dependencies = expandDependencies ? dependencyContext.dependencies : directDependencies;
669
907
  return {
670
908
  code,
671
909
  dependencies,
@@ -674,37 +912,76 @@ function compileWxml(options) {
674
912
  }
675
913
 
676
914
  // src/compiler/wxs.ts
677
- var REQUIRE_RE = /require\\(\\s*['"]([^'"]+)['"]\\s*\\)/g;
915
+ var REQUIRE_RE = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
678
916
  function normalizePath(p) {
679
917
  return p.split("\\\\").join("/");
680
918
  }
681
- function ensureWxsExtension(pathname) {
682
- if (pathname.endsWith(".wxs") || pathname.endsWith(".wxs.ts") || pathname.endsWith(".wxs.js")) {
919
+ function isPlainWxsScript(pathname) {
920
+ const lower = pathname.toLowerCase();
921
+ if (lower.endsWith(".wxs") || lower.endsWith(".wxs.ts") || lower.endsWith(".wxs.js")) {
922
+ return false;
923
+ }
924
+ return lower.endsWith(".ts") || lower.endsWith(".js");
925
+ }
926
+ function appendWxsQuery(pathname) {
927
+ if (pathname.includes("?wxs") || pathname.includes("&wxs")) {
683
928
  return pathname;
684
929
  }
685
- return `${pathname}.wxs`;
930
+ return `${pathname}${pathname.includes("?") ? "&" : "?"}wxs`;
931
+ }
932
+ function isSupportedRequirePath(request) {
933
+ return request.startsWith(".") || request.startsWith("/");
686
934
  }
687
935
  function transformWxsToEsm(code, id, options) {
688
936
  const dependencies = [];
689
937
  const importLines = [];
690
938
  const mapEntries = [];
691
- let match;
939
+ const warnings = [];
692
940
  const seen = /* @__PURE__ */ new Set();
693
- while (match = REQUIRE_RE.exec(code)) {
941
+ while (true) {
942
+ const match = REQUIRE_RE.exec(code);
943
+ if (!match) {
944
+ break;
945
+ }
694
946
  const request = match[1];
695
947
  if (!request || seen.has(request)) {
696
948
  continue;
697
949
  }
698
950
  seen.add(request);
699
- const resolved = ensureWxsExtension(options.resolvePath(request, id) ?? request);
700
- const importPath = normalizePath(options.toImportPath?.(resolved, id) ?? resolved);
951
+ if (!isSupportedRequirePath(request)) {
952
+ warnings.push(`[@weapp-vite/web] WXS require \u4EC5\u652F\u6301\u76F8\u5BF9\u6216\u7EDD\u5BF9\u8DEF\u5F84: ${request} (from ${id})`);
953
+ continue;
954
+ }
955
+ const resolved = options.resolvePath(request, id);
956
+ if (!resolved) {
957
+ warnings.push(`[@weapp-vite/web] \u65E0\u6CD5\u89E3\u6790 WXS require: ${request} (from ${id})`);
958
+ continue;
959
+ }
960
+ let importPath = normalizePath(options.toImportPath?.(resolved, id) ?? resolved);
961
+ if (isPlainWxsScript(resolved)) {
962
+ importPath = appendWxsQuery(importPath);
963
+ }
701
964
  const importName = `__wxs_dep_${dependencies.length}`;
702
965
  importLines.push(`import ${importName} from '${importPath}'`);
703
966
  mapEntries.push(`[${JSON.stringify(request)}, ${importName}]`);
704
967
  dependencies.push(resolved);
705
968
  }
706
969
  const requireMap = `const __wxs_require_map = new Map([${mapEntries.join(", ")}])`;
707
- const requireFn = `function require(id) { return __wxs_require_map.get(id) }`;
970
+ const requireFn = [
971
+ `const __wxs_require_warned = new Set()`,
972
+ `function require(id) {`,
973
+ ` if (__wxs_require_map.has(id)) {`,
974
+ ` return __wxs_require_map.get(id)`,
975
+ ` }`,
976
+ ` if (!__wxs_require_warned.has(id)) {`,
977
+ ` __wxs_require_warned.add(id)`,
978
+ ` if (typeof console !== 'undefined' && typeof console.warn === 'function') {`,
979
+ ` console.warn(\`[@weapp-vite/web] WXS require \u672A\u89E3\u6790: \${id}\`)`,
980
+ ` }`,
981
+ ` }`,
982
+ ` return undefined`,
983
+ `}`
984
+ ].join("\\n");
708
985
  const moduleInit = `const module = { exports: {} }\\nconst exports = module.exports`;
709
986
  const helpers = `const getRegExp = (pattern, flags) => new RegExp(pattern, flags)\\nconst getDate = (value) => (value == null ? new Date() : new Date(value))`;
710
987
  const body = [
@@ -716,7 +993,11 @@ function transformWxsToEsm(code, id, options) {
716
993
  code,
717
994
  `export default module.exports`
718
995
  ].join("\\n");
719
- return { code: body, dependencies };
996
+ return {
997
+ code: body,
998
+ dependencies,
999
+ warnings: warnings.length > 0 ? warnings : void 0
1000
+ };
720
1001
  }
721
1002
 
722
1003
  // src/css/wxss.ts
@@ -741,6 +1022,12 @@ function transformWxssToCss(source, options) {
741
1022
  };
742
1023
  }
743
1024
 
1025
+ // src/shared/slugify.ts
1026
+ function slugify(id, prefix) {
1027
+ const normalized = id.replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").toLowerCase();
1028
+ return `${prefix}-${normalized || "index"}`;
1029
+ }
1030
+
744
1031
  // src/plugin.ts
745
1032
  var traverseCandidate = (() => {
746
1033
  const mod = _babelTraverse;
@@ -764,6 +1051,7 @@ var STYLE_EXTS = [".wxss", ".scss", ".less", ".css"];
764
1051
  var TRANSFORM_STYLE_EXTS = [".wxss"];
765
1052
  var TEMPLATE_EXTS = [".wxml", ".axml", ".swan", ".ttml", ".qml", ".ksml", ".xhsml", ".html"];
766
1053
  var WXS_EXTS = [".wxs", ".wxs.ts", ".wxs.js"];
1054
+ var WXS_RESOLVE_EXTS = [".wxs", ".wxs.ts", ".wxs.js", ".ts", ".js"];
767
1055
  var ENTRY_ID = "\0@weapp-vite/web/entry";
768
1056
  function isTemplateFile(id) {
769
1057
  const lower = id.toLowerCase();
@@ -773,13 +1061,20 @@ function isWxsFile(id) {
773
1061
  const lower = id.toLowerCase();
774
1062
  return WXS_EXTS.some((ext) => lower.endsWith(ext));
775
1063
  }
1064
+ function hasWxsQuery(id) {
1065
+ return id.includes("?wxs") || id.includes("&wxs");
1066
+ }
776
1067
  function weappWebPlugin(options = {}) {
777
1068
  let root = process.cwd();
778
1069
  let srcRoot = resolve(root, options.srcDir ?? "src");
779
1070
  let enableHmr = false;
780
1071
  const moduleMeta = /* @__PURE__ */ new Map();
781
1072
  const pageNavigationMap = /* @__PURE__ */ new Map();
1073
+ const templateComponentMap = /* @__PURE__ */ new Map();
1074
+ const componentTagMap = /* @__PURE__ */ new Map();
1075
+ const componentIdMap = /* @__PURE__ */ new Map();
782
1076
  let appNavigationDefaults = {};
1077
+ let appComponentTags = {};
783
1078
  let scanResult = {
784
1079
  app: void 0,
785
1080
  pages: [],
@@ -795,10 +1090,10 @@ function weappWebPlugin(options = {}) {
795
1090
  root = config.root;
796
1091
  srcRoot = resolve(root, options.srcDir ?? "src");
797
1092
  enableHmr = config.command === "serve";
798
- await scanProject();
1093
+ await scanProject(this.warn?.bind(this));
799
1094
  },
800
1095
  async buildStart() {
801
- await scanProject();
1096
+ await scanProject(this.warn?.bind(this));
802
1097
  },
803
1098
  resolveId(id) {
804
1099
  if (id === "/@weapp-vite/web/entry" || id === "@weapp-vite/web/entry") {
@@ -815,19 +1110,22 @@ function weappWebPlugin(options = {}) {
815
1110
  async handleHotUpdate(ctx) {
816
1111
  const clean = cleanUrl(ctx.file);
817
1112
  if (clean.endsWith(".json") || isTemplateFile(clean) || isWxsFile(clean) || clean.endsWith(".wxss") || SCRIPT_EXTS.includes(extname(clean))) {
818
- await scanProject();
1113
+ await scanProject(this.warn?.bind(this));
819
1114
  }
820
1115
  },
821
1116
  transform(code, id) {
822
1117
  const clean = cleanUrl(id);
823
1118
  if (isTemplateFile(clean)) {
824
- const navigationConfig = pageNavigationMap.get(normalizePath2(clean));
1119
+ const normalizedId = normalizePath2(clean);
1120
+ const navigationConfig = pageNavigationMap.get(normalizedId);
1121
+ const componentTags = templateComponentMap.get(normalizedId);
825
1122
  const { code: compiled, dependencies, warnings } = compileWxml({
826
1123
  id: clean,
827
1124
  source: code,
828
1125
  resolveTemplatePath,
829
1126
  resolveWxsPath,
830
- navigationBar: navigationConfig ? { config: navigationConfig } : void 0
1127
+ navigationBar: navigationConfig ? { config: navigationConfig } : void 0,
1128
+ componentTags
831
1129
  });
832
1130
  if (dependencies.length > 0 && "addWatchFile" in this) {
833
1131
  for (const dep of dependencies) {
@@ -841,8 +1139,8 @@ function weappWebPlugin(options = {}) {
841
1139
  }
842
1140
  return { code: compiled, map: null };
843
1141
  }
844
- if (isWxsFile(clean)) {
845
- const { code: compiled, dependencies } = transformWxsToEsm(code, clean, {
1142
+ if (isWxsFile(clean) || hasWxsQuery(id)) {
1143
+ const { code: compiled, dependencies, warnings } = transformWxsToEsm(code, clean, {
846
1144
  resolvePath: resolveWxsPath,
847
1145
  toImportPath: (resolved, importer) => normalizePath2(toRelativeImport2(importer, resolved))
848
1146
  });
@@ -851,6 +1149,11 @@ function weappWebPlugin(options = {}) {
851
1149
  this.addWatchFile(dep);
852
1150
  }
853
1151
  }
1152
+ if (warnings?.length && "warn" in this) {
1153
+ for (const warning of warnings) {
1154
+ this.warn(warning);
1155
+ }
1156
+ }
854
1157
  return { code: compiled, map: null };
855
1158
  }
856
1159
  if (TRANSFORM_STYLE_EXTS.some((ext) => clean.endsWith(ext))) {
@@ -981,12 +1284,25 @@ if (import.meta.hot) { import.meta.hot.accept() }
981
1284
  };
982
1285
  }
983
1286
  };
984
- async function scanProject() {
1287
+ async function scanProject(warn) {
985
1288
  moduleMeta.clear();
986
1289
  pageNavigationMap.clear();
1290
+ templateComponentMap.clear();
1291
+ componentTagMap.clear();
1292
+ componentIdMap.clear();
987
1293
  appNavigationDefaults = {};
1294
+ appComponentTags = {};
988
1295
  const pages = /* @__PURE__ */ new Map();
989
1296
  const components = /* @__PURE__ */ new Map();
1297
+ const reportWarning = (message) => {
1298
+ if (warn) {
1299
+ warn(message);
1300
+ return;
1301
+ }
1302
+ if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
1303
+ process.emitWarning(message);
1304
+ }
1305
+ };
990
1306
  const appScript = await resolveScriptFile(join(srcRoot, "app"));
991
1307
  if (appScript) {
992
1308
  moduleMeta.set(
@@ -1002,6 +1318,11 @@ if (import.meta.hot) { import.meta.hot.accept() }
1002
1318
  const appJsonPath = join(srcRoot, "app.json");
1003
1319
  if (await fs.pathExists(appJsonPath)) {
1004
1320
  const appJson = await readJsonFile(appJsonPath);
1321
+ if (appJson) {
1322
+ appComponentTags = await collectComponentTagsFromConfig(appJson, srcRoot, appJsonPath, reportWarning, (tags) => {
1323
+ appComponentTags = tags;
1324
+ });
1325
+ }
1005
1326
  if (appJson?.pages && Array.isArray(appJson.pages)) {
1006
1327
  for (const page of appJson.pages) {
1007
1328
  if (typeof page === "string") {
@@ -1059,17 +1380,22 @@ if (import.meta.hot) { import.meta.hot.accept() }
1059
1380
  script,
1060
1381
  id: toPosixId(pageId)
1061
1382
  });
1383
+ const pageComponentTags = pageJson ? await collectComponentTagsFromConfig(pageJson, dirname2(script), pageJsonPath, reportWarning) : await collectComponentTagsFromJson(pageJsonPath, dirname2(script), reportWarning);
1384
+ if (template) {
1385
+ const mergedTags = mergeComponentTags(appComponentTags, pageComponentTags);
1386
+ if (Object.keys(mergedTags).length > 0) {
1387
+ templateComponentMap.set(normalizePath2(template), mergedTags);
1388
+ } else {
1389
+ templateComponentMap.delete(normalizePath2(template));
1390
+ }
1391
+ }
1062
1392
  if (pageJson) {
1063
- await collectComponentsFromConfig(pageJson, dirname2(script));
1064
1393
  if (template) {
1065
1394
  const config = mergeNavigationConfig(appNavigationDefaults, pickNavigationConfig(pageJson));
1066
1395
  pageNavigationMap.set(normalizePath2(template), config);
1067
1396
  }
1068
- } else {
1069
- await collectComponentsFromJson(pageJsonPath, dirname2(script));
1070
- if (template) {
1071
- pageNavigationMap.set(normalizePath2(template), { ...appNavigationDefaults });
1072
- }
1397
+ } else if (template) {
1398
+ pageNavigationMap.set(normalizePath2(template), { ...appNavigationDefaults });
1073
1399
  }
1074
1400
  }
1075
1401
  async function collectComponent(componentId, importerDir) {
@@ -1099,26 +1425,94 @@ if (import.meta.hot) { import.meta.hot.accept() }
1099
1425
  script,
1100
1426
  id: componentIdPosix
1101
1427
  });
1102
- await collectComponentsFromJson(`${script.replace(new RegExp(`${extname(script)}$`), "")}.json`, dirname2(script));
1428
+ const componentJsonPath = `${script.replace(new RegExp(`${extname(script)}$`), "")}.json`;
1429
+ const componentTags = await collectComponentTagsFromJson(componentJsonPath, dirname2(script), reportWarning);
1430
+ if (template) {
1431
+ const mergedTags = mergeComponentTags(appComponentTags, componentTags);
1432
+ if (Object.keys(mergedTags).length > 0) {
1433
+ templateComponentMap.set(normalizePath2(template), mergedTags);
1434
+ } else {
1435
+ templateComponentMap.delete(normalizePath2(template));
1436
+ }
1437
+ }
1103
1438
  }
1104
- async function collectComponentsFromConfig(json, importerDir) {
1439
+ async function collectComponentTagsFromConfig(json, importerDir, jsonPath, warn2, onResolved) {
1105
1440
  const usingComponents = json.usingComponents;
1106
1441
  if (!usingComponents || typeof usingComponents !== "object") {
1107
- return;
1442
+ return {};
1108
1443
  }
1109
- for (const value of Object.values(usingComponents)) {
1110
- if (typeof value !== "string") {
1444
+ const tags = {};
1445
+ const resolved = [];
1446
+ for (const [rawKey, rawValue] of Object.entries(usingComponents)) {
1447
+ if (typeof rawValue !== "string") {
1448
+ continue;
1449
+ }
1450
+ const key = normalizeComponentKey(rawKey);
1451
+ if (!key) {
1452
+ continue;
1453
+ }
1454
+ const script = await resolveComponentScript(rawValue, importerDir);
1455
+ if (!script) {
1456
+ warn2(`[@weapp-vite/web] usingComponents entry "${rawKey}" not found: ${rawValue} (${jsonPath})`);
1111
1457
  continue;
1112
1458
  }
1113
- await collectComponent(value, importerDir);
1459
+ const tag = getComponentTag(script);
1460
+ if (tag) {
1461
+ tags[key] = tag;
1462
+ resolved.push({ rawValue });
1463
+ }
1464
+ }
1465
+ onResolved?.(tags);
1466
+ for (const entry of resolved) {
1467
+ await collectComponent(entry.rawValue, importerDir);
1114
1468
  }
1469
+ return tags;
1115
1470
  }
1116
- async function collectComponentsFromJson(jsonPath, importerDir) {
1471
+ async function collectComponentTagsFromJson(jsonPath, importerDir, warn2) {
1117
1472
  const json = await readJsonFile(jsonPath);
1118
1473
  if (!json) {
1119
- return;
1474
+ return {};
1475
+ }
1476
+ return collectComponentTagsFromConfig(json, importerDir, jsonPath, warn2);
1477
+ }
1478
+ function mergeComponentTags(base, overrides) {
1479
+ if (!Object.keys(base).length && !Object.keys(overrides).length) {
1480
+ return {};
1481
+ }
1482
+ return {
1483
+ ...base,
1484
+ ...overrides
1485
+ };
1486
+ }
1487
+ function normalizeComponentKey(raw) {
1488
+ return raw.trim().toLowerCase();
1489
+ }
1490
+ function getComponentId(script) {
1491
+ const cached = componentIdMap.get(script);
1492
+ if (cached) {
1493
+ return cached;
1494
+ }
1495
+ const idRelative = relative2(srcRoot, script).replace(new RegExp(`${extname(script)}$`), "");
1496
+ const componentIdPosix = toPosixId(idRelative);
1497
+ componentIdMap.set(script, componentIdPosix);
1498
+ return componentIdPosix;
1499
+ }
1500
+ function getComponentTag(script) {
1501
+ const cached = componentTagMap.get(script);
1502
+ if (cached) {
1503
+ return cached;
1120
1504
  }
1121
- await collectComponentsFromConfig(json, importerDir);
1505
+ const id = getComponentId(script);
1506
+ const tag = slugify(id, "wv-component");
1507
+ componentTagMap.set(script, tag);
1508
+ return tag;
1509
+ }
1510
+ async function resolveComponentScript(raw, importerDir) {
1511
+ const base = resolveComponentBase(raw, importerDir);
1512
+ if (!base) {
1513
+ return void 0;
1514
+ }
1515
+ return resolveScriptFile(base);
1122
1516
  }
1123
1517
  function resolveComponentBase(raw, importerDir) {
1124
1518
  if (!raw) {
@@ -1214,11 +1608,18 @@ function resolveTemplatePathSync(raw, importer, srcRoot) {
1214
1608
  return resolveFileWithExtensionsSync(base, TEMPLATE_EXTS);
1215
1609
  }
1216
1610
  function resolveWxsPathSync(raw, importer, srcRoot) {
1217
- const base = resolveImportBase(raw, importer, srcRoot);
1218
- if (!base) {
1611
+ if (!raw) {
1612
+ return void 0;
1613
+ }
1614
+ let base;
1615
+ if (raw.startsWith(".")) {
1616
+ base = resolve(dirname2(importer), raw);
1617
+ } else if (raw.startsWith("/")) {
1618
+ base = resolve(srcRoot, raw.slice(1));
1619
+ } else {
1219
1620
  return void 0;
1220
1621
  }
1221
- return resolveFileWithExtensionsSync(base, WXS_EXTS);
1622
+ return resolveFileWithExtensionsSync(base, WXS_RESOLVE_EXTS);
1222
1623
  }
1223
1624
  async function resolveScriptFile(basePath) {
1224
1625
  const ext = extname(basePath);