@compelem/compiler 0.30.0-beta.4 → 0.30.0-beta.6

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.
@@ -494,12 +494,21 @@ function extractClassInfo(node, resolve) {
494
494
  const imported = resolve(d);
495
495
  return imported === "query" || imported === "queryAll";
496
496
  })) queryFields.add(name);
497
- if (member.type === "MethodDefinition") {
498
- if (member.kind === "get") getters.add(name);
499
- else methods.add(name);
497
+ const resDeco = (member.decorators ?? []).find((d) => {
498
+ const n = decoratorName(d);
499
+ return n ? resolve(n) === "resource" : false;
500
+ });
501
+ if (member.type === "MethodDefinition") if (member.kind === "get") {
502
+ getters.add(name);
503
+ const fn = member.value;
504
+ if (fn?.body) bodies.set(name, fn.body);
505
+ } else {
506
+ methods.add(name);
500
507
  const fn = member.value;
501
508
  if (fn?.body) bodies.set(name, fn.body);
502
- } else if (member.type === "PropertyDefinition") {
509
+ }
510
+ else if (member.type === "PropertyDefinition") if (resDeco) getters.add(name);
511
+ else {
503
512
  const init = member.value;
504
513
  if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
505
514
  methods.add(name);
@@ -1804,6 +1813,7 @@ function buildStaticLiteral(opts, codes = {}) {
1804
1813
  parts.push(mark("props", `props: ${emitFamily("props", codes.props)}`));
1805
1814
  parts.push(mark("states", `states: ${emitFamily("states", codes.states)}`));
1806
1815
  parts.push(mark("computedGetters", `computedGetters: ${emitFamily("computedGetters", codes.computedGetters)}`));
1816
+ parts.push(mark("resources", `resources: ${emitFamily("resources", codes.resources)}`));
1807
1817
  const watchCode = emitWatchEffects(codes.watchEffects, codes.inlinedWatchAncestors);
1808
1818
  if (watchCode !== void 0) parts.push(mark("watchEffects", `watchEffects: ${watchCode}`));
1809
1819
  if (codes.cssEffect) parts.push(mark("cssEffect", `cssEffect: ${codes.cssEffect}`));
@@ -2595,8 +2605,8 @@ function checkConventions(comp, ctx) {
2595
2605
  function record(ctx, path) {
2596
2606
  addDepPath(ctx.deps, path);
2597
2607
  }
2598
- /** TS 包装节点解包(`this as any` / `that!` 等)—— 统一实现在 utils/ast#unwrapExpr。
2599
- * 本副本原先比别处多认 `TSInstantiationExpression`、少认 `TSTypeAssertion`,已归一。 */
2608
+ /** TS 包装节点解包(`this as any` / `that!` / `<T>x` 等)—— 统一实现在 utils/ast#unwrapExpr,
2609
+ * 认 TSInstantiationExpression 与 TSTypeAssertion。 */
2600
2610
  /** 置失败标记;DEPS_TRACE=1 时输出原因标签(见文件头)。 */
2601
2611
  function failAt(ctx, where) {
2602
2612
  if (typeof process !== "undefined" && process.env?.DEPS_TRACE) console.error("[deps-fail]", where);
@@ -4399,6 +4409,234 @@ function extractFieldAccessors(comp, code, ctx, bindings = {}, inheritedAccessor
4399
4409
  };
4400
4410
  }
4401
4411
 
4412
+ //#endregion
4413
+ //#region src/analyze/resource-extract.ts
4414
+ const CODE = "E-RESOURCE";
4415
+ /** 选项闭集,逐字对齐运行期 `ResourceDecoratorOptions` */
4416
+ const OPT_KEYS = new Set([
4417
+ "timeout",
4418
+ "immediate",
4419
+ "track",
4420
+ "source"
4421
+ ]);
4422
+ /** 合法标识符:资源名要拼进 `get <name>()`,非法名会生成坏代码 */
4423
+ const IDENT = /^[A-Za-z_$][\w$]*$/;
4424
+ /**
4425
+ * 不能用作资源名的成员:`CompElem` 原型上的访问器、方法与生命周期回调,
4426
+ * 外加 `Object.prototype` 的成员。
4427
+ *
4428
+ * 运行期按 `__ce_static__.resources` 在原型上装 `get <name>()`,安装前会检查
4429
+ * 原型链上是否已有同名键,有则跳过 —— 撞名的结果是资源**静默不可用**。
4430
+ * 编译器无法读到运行期原型(`CompElem` 继承 `HTMLElement`,Node 侧无法加载),
4431
+ * 这份名单与 `src/CompElem.ts` 的成员表同步维护。
4432
+ */
4433
+ const RESERVED_NAMES = new Set([
4434
+ "cid",
4435
+ "attrs",
4436
+ "props",
4437
+ "renderRoot",
4438
+ "renderRoots",
4439
+ "parentComponent",
4440
+ "wrapperComponent",
4441
+ "rootComponent",
4442
+ "slots",
4443
+ "slotHooks",
4444
+ "cssSheets",
4445
+ "cssVars",
4446
+ "isMounted",
4447
+ "isDestroyed",
4448
+ "insertStyleSheet",
4449
+ "setup",
4450
+ "propsReady",
4451
+ "render",
4452
+ "beforeMount",
4453
+ "mounted",
4454
+ "beforeDestroyed",
4455
+ "destroyed",
4456
+ "destroy",
4457
+ "slotChange",
4458
+ "afterUpdate",
4459
+ "updateProps",
4460
+ "getSubViewNodes",
4461
+ "emit",
4462
+ "nextTick",
4463
+ "defineSignalField",
4464
+ "forceUpdate",
4465
+ "connectedCallback",
4466
+ "disconnectedCallback",
4467
+ "attributeChangedCallback",
4468
+ "constructor",
4469
+ "toString",
4470
+ "toLocaleString",
4471
+ "valueOf",
4472
+ "hasOwnProperty",
4473
+ "isPrototypeOf",
4474
+ "propertyIsEnumerable",
4475
+ "__proto__"
4476
+ ]);
4477
+ /** 编译期注入的私有方法前缀 */
4478
+ const LOADER_PREFIX = "__rl_";
4479
+ const SOURCE_PREFIX = "__rs_";
4480
+ /** 迁移提示:给出可直接照抄的写法 */
4481
+ const MIGRATE = "\n 正确形态(loader 是第一个参数,选项可选):\n @resource((self, signal) => …, { timeout: 3000 })\n rows!: ResourceView<Row[]>\n 之后模板里写 this.rows.data / this.rows.busy / this.rows.load()。";
4482
+ function extractResources(comp, code, ctx) {
4483
+ const result = {
4484
+ accessors: "",
4485
+ removals: [],
4486
+ runtimeHelpers: [],
4487
+ errors: []
4488
+ };
4489
+ const cls = comp.cls ?? ctx.localClasses.get(comp.className);
4490
+ if (!cls) return result;
4491
+ const isResourceDeco = (d) => comp.compelemImports.get(d) === "resource";
4492
+ const members = cls.body?.body ?? [];
4493
+ const targets = [];
4494
+ for (const member of members) {
4495
+ const dec = (member.decorators ?? []).find((d) => {
4496
+ const n = decoratorName(d);
4497
+ return n ? isResourceDeco(n) : false;
4498
+ });
4499
+ if (!dec) continue;
4500
+ targets.push({
4501
+ member,
4502
+ dec,
4503
+ rawName: keyName(member.key) ?? ""
4504
+ });
4505
+ }
4506
+ if (!targets.length) return result;
4507
+ const fail = (member, dec, message) => {
4508
+ const dStart = dec?.start ?? member.start;
4509
+ result.errors.push({
4510
+ start: Math.min(member.start, dStart),
4511
+ end: member.end,
4512
+ message
4513
+ });
4514
+ };
4515
+ /** 本族已提交的资源名,用于同类内重复检查(TS 也会报,但这里给更直白的 E 码) */
4516
+ const emitted = /* @__PURE__ */ new Set();
4517
+ const entries = [];
4518
+ let accessors = "";
4519
+ const removals = [];
4520
+ for (const { member, dec, rawName } of targets) {
4521
+ if (member.type !== "PropertyDefinition") {
4522
+ fail(member, dec, `${CODE}: @resource 只能装饰字段声明。${MIGRATE}`);
4523
+ continue;
4524
+ }
4525
+ if (!rawName) {
4526
+ fail(member, dec, `${CODE}: 无法识别被装饰的字段名(计算属性键不支持)`);
4527
+ continue;
4528
+ }
4529
+ if (rawName.startsWith("#")) {
4530
+ fail(member, dec, `${CODE}: 不支持私有字段('${rawName}')——视图访问器要挂在原型上才能被模板读取`);
4531
+ continue;
4532
+ }
4533
+ if (!IDENT.test(rawName)) {
4534
+ fail(member, dec, `${CODE}: 资源名 '${rawName}' 不是合法标识符(要用作 get ${rawName}())`);
4535
+ continue;
4536
+ }
4537
+ if (RESERVED_NAMES.has(rawName)) {
4538
+ fail(member, dec, `${CODE}: 资源名 '${rawName}' 与 CompElem 原型链上的成员同名——访问器不会安装,this.${rawName} 取不到资源视图。请改用其它字段名`);
4539
+ continue;
4540
+ }
4541
+ if (member.static === true) {
4542
+ fail(member, dec, `${CODE}: 不支持静态资源('${rawName}')——注入的是实例 getter,static 成员会被整段删掉`);
4543
+ continue;
4544
+ }
4545
+ if (member.value != null) {
4546
+ fail(member, dec, `${CODE}: 字段不能带初始值('${rawName} = …')——值只由 loader 提供,写在字段上的初始值会被删掉,且不会报错`);
4547
+ continue;
4548
+ }
4549
+ if (emitted.has(rawName)) {
4550
+ fail(member, dec, `${CODE}: '${rawName}' 已经有另一个 @resource,资源名必须唯一`);
4551
+ continue;
4552
+ }
4553
+ const otherDecos = (member.decorators ?? []).map(decoratorName).filter((d) => d && !isResourceDeco(d));
4554
+ if (otherDecos.length) {
4555
+ fail(member, dec, `${CODE}: @resource 不能与其它装饰器同用('${rawName}' 上还有 @${otherDecos.join(" / @")})`);
4556
+ continue;
4557
+ }
4558
+ const callArgs = dec.expression?.type === "CallExpression" ? dec.expression.arguments ?? [] : [];
4559
+ if (callArgs.length < 1 || callArgs.length > 2) {
4560
+ fail(member, dec, `${CODE}: @resource 需要 (loader, options?) 两个参数以内${MIGRATE}`);
4561
+ continue;
4562
+ }
4563
+ const loaderNode = unwrapExpr(callArgs[0]);
4564
+ if (loaderNode?.type !== "ArrowFunctionExpression") {
4565
+ fail(member, dec, loaderNode?.type === "ObjectExpression" ? `${CODE}: 形态已改为 @resource(loader, options?)——loader 从选项对象移到第一个参数${MIGRATE}` : `${CODE}: 第一个参数须是内联箭头函数 (self, signal) => …——编译期要把这段表达式搬进实例方法,引用式写法拿不到组件实例`);
4566
+ continue;
4567
+ }
4568
+ if ((loaderNode.params ?? []).length !== 2) {
4569
+ fail(member, dec, `${CODE}: loader 必须声明两个形参 (self, signal) —— 装饰器实参里没有 this,组件实例要显式传进来`);
4570
+ continue;
4571
+ }
4572
+ /** loader 表达式切片(搬进方法体后 `this` 才是实例) */
4573
+ const loaderExpr = `(${code.slice(loaderNode.start, loaderNode.end)})`;
4574
+ let sourceExpr;
4575
+ const keep = [];
4576
+ let optsBad = false;
4577
+ let optsNode;
4578
+ if (callArgs.length === 2) {
4579
+ optsNode = unwrapExpr(callArgs[1]);
4580
+ if (optsNode?.type !== "ObjectExpression") {
4581
+ fail(member, dec, `${CODE}: 第二个参数须是内联对象字面量(变量/import 无法静态前移)`);
4582
+ continue;
4583
+ }
4584
+ }
4585
+ for (const p of optsNode?.properties ?? []) {
4586
+ if (p.type === "SpreadElement" || p.type === "ExperimentalSpreadProperty") {
4587
+ fail(member, dec, `${CODE}: 选项不支持展开(spread 无法静态前移),请逐项写出`);
4588
+ optsBad = true;
4589
+ break;
4590
+ }
4591
+ if (p.computed) {
4592
+ fail(member, dec, `${CODE}: 选项键无法静态解析(计算属性键不支持)`);
4593
+ optsBad = true;
4594
+ break;
4595
+ }
4596
+ const k = keyName(p.key);
4597
+ if (!k) {
4598
+ fail(member, dec, `${CODE}: 选项键无法静态解析(计算属性键不支持)`);
4599
+ optsBad = true;
4600
+ break;
4601
+ }
4602
+ if (!OPT_KEYS.has(k)) {
4603
+ fail(member, dec, `${CODE}: ${k === "loader" || k === "fetch" || k === "fetcher" ? `'${k}' 不是选项——loader 是第一个参数,写成 @resource((self, signal) => …)${MIGRATE}` : ""}未知选项 '${k}'——ResourceDecoratorOptions 仅支持 ${[...OPT_KEYS].join(" / ")}`);
4604
+ optsBad = true;
4605
+ break;
4606
+ }
4607
+ if (k === "source") {
4608
+ const v = unwrapExpr(p.value);
4609
+ if (v?.type !== "ArrowFunctionExpression") {
4610
+ fail(member, dec, `${CODE}: source 须是内联箭头函数 (self) => …——编译期要把这段表达式搬进实例方法,引用式写法拿不到组件实例`);
4611
+ optsBad = true;
4612
+ break;
4613
+ }
4614
+ if ((v.params ?? []).length > 1) {
4615
+ fail(member, dec, `${CODE}: source 最多接收一个形参 (self)`);
4616
+ optsBad = true;
4617
+ break;
4618
+ }
4619
+ sourceExpr = `(${code.slice(v.start, v.end)})`;
4620
+ continue;
4621
+ }
4622
+ keep.push(code.slice(p.start, p.end));
4623
+ }
4624
+ if (optsBad) continue;
4625
+ emitted.add(rawName);
4626
+ const keyLit = JSON.stringify(rawName);
4627
+ accessors += `${DEBUG_DIV} resource ${rawName}\n${LOADER_PREFIX}${rawName}(__signal) { return ${loaderExpr}(this, __signal) }\n` + (sourceExpr ? `${SOURCE_PREFIX}${rawName}() { return ${sourceExpr}(this) }\n` : "");
4628
+ const parts = keep.slice();
4629
+ if (sourceExpr) parts.push("source: true");
4630
+ entries.push(`${keyLit}: { ${parts.join(", ")} }`);
4631
+ removals.push(lineSpan(code, Math.min(member.start, dec.start ?? member.start), member.end));
4632
+ }
4633
+ if (!removals.length) return result;
4634
+ result.accessors = accessors;
4635
+ result.removals = removals;
4636
+ result.resourcesCode = `{ ${entries.join(", ")} }`;
4637
+ return result;
4638
+ }
4639
+
4402
4640
  //#endregion
4403
4641
  //#region src/analyze/method-deco-extract.ts
4404
4642
  /** 本地变量前缀(避开用户标识符;`__` 前缀与框架其余注入一致)。 */
@@ -4712,8 +4950,7 @@ function staticHtmlFragmentOf(e, comp) {
4712
4950
  * 非 this 起始 / 含 computed 访问时返回 null。
4713
4951
  *
4714
4952
  * 统一实现在 utils/ast#thisChain(属性名走 keyName,`#private` 带 `#` 前缀,
4715
- * 与 render-body 侧对齐 —— 本副本原先手写分支漏了前缀,会把 `this.#x` 与
4716
- * `this.x` 混成同一个依赖键)。
4953
+ * 否则 `this.#x` 与 `this.x` 会混成同一个依赖键)。
4717
4954
  */
4718
4955
  /**
4719
4956
  * 提取 TAG 指令实参中的 this 链全前缀路径。
@@ -5287,78 +5524,93 @@ function checkTemplateRules(tree, tmpl, comp, ctx, knownTags) {
5287
5524
  });
5288
5525
  }
5289
5526
  }
5290
- for (const el of elements) for (const a of el.attrs) {
5291
- if (a.name.includes("⟬")) continue;
5292
- const varPart = a.parts.find((p) => p.type === "var");
5293
- const varCount = a.parts.filter((p) => p.type === "var").length;
5294
- if (a.name.startsWith("@") && !a.name.startsWith("@mutate")) {
5295
- const segs = a.name.slice(1).split(".");
5296
- const evName = (segs[0] ?? "").toLowerCase();
5297
- const mods = segs.slice(1);
5298
- const compEmits = localEmits.get(el.tag.toLowerCase());
5299
- if (!!compEmits && matchEmitName(compEmits, evName)) {
5300
- const bad = mods.filter((mo) => !COMP_EVENT_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5301
- if (bad.length) errs.push({
5527
+ for (const el of elements) {
5528
+ const seenDirectives = /* @__PURE__ */ new Set();
5529
+ for (const a of el.attrs) {
5530
+ if (!a.name.includes("⟬")) continue;
5531
+ const dm = /⟬Ċ⟭(\d+)/.exec(a.name);
5532
+ if (!dm) continue;
5533
+ const dv = tmpl.vars[+dm[1]];
5534
+ if (!dv?.isDirectiveCall || !dv.directiveName) continue;
5535
+ if (seenDirectives.has(dv.directiveName)) errs.push({
5536
+ ...pos,
5537
+ message: `E-DIRECTIVE-DUP: 元素 <${el.tag}> 上的指令 '${dv.directiveName}' 出现了两次以上;同一元素每种指令只能写一次(重复的那份运行期会被静默丢弃)`
5538
+ });
5539
+ else seenDirectives.add(dv.directiveName);
5540
+ }
5541
+ for (const a of el.attrs) {
5542
+ if (a.name.includes("⟬")) continue;
5543
+ const varPart = a.parts.find((p) => p.type === "var");
5544
+ const varCount = a.parts.filter((p) => p.type === "var").length;
5545
+ if (a.name.startsWith("@") && !a.name.startsWith("@mutate")) {
5546
+ const segs = a.name.slice(1).split(".");
5547
+ const evName = (segs[0] ?? "").toLowerCase();
5548
+ const mods = segs.slice(1);
5549
+ const compEmits = localEmits.get(el.tag.toLowerCase());
5550
+ if (!!compEmits && matchEmitName(compEmits, evName)) {
5551
+ const bad = mods.filter((mo) => !COMP_EVENT_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5552
+ if (bad.length) errs.push({
5553
+ ...pos,
5554
+ severity: "warn",
5555
+ message: `W-EVENT-COMP-MODIFIER: '${a.name}' 是组件事件(${el.tag} 的 @emits 已声明 '${evName}'),运行期只应用 debounce/throttle/once,[${bad.join(", ")}] 会被静默丢弃`
5556
+ });
5557
+ } else if (!KEY_EVENTS.has(evName) && NATIVE_EVENTS.has(evName)) {
5558
+ const bad = mods.filter((mo) => !EVENT_PLAIN_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5559
+ if (bad.length) errs.push({
5560
+ ...pos,
5561
+ message: `E-EVENT-MODIFIER: '${a.name}' 中的 [${bad.join(", ")}] 不是内置修饰符;运行期会把它当作 KeyboardEvent.key 过滤,而 '${evName}' 不是键盘事件 ⇒ 处理器恒不触发`
5562
+ });
5563
+ }
5564
+ if (varCount === 0 && mods.length === 0) errs.push({
5302
5565
  ...pos,
5303
5566
  severity: "warn",
5304
- message: `W-EVENT-COMP-MODIFIER: '${a.name}' 是组件事件(${el.tag} 的 @emits 已声明 '${evName}'),运行期只应用 debounce/throttle/once,[${bad.join(", ")}] 会被静默丢弃`
5305
- });
5306
- } else if (!KEY_EVENTS.has(evName) && NATIVE_EVENTS.has(evName)) {
5307
- const bad = mods.filter((mo) => !EVENT_PLAIN_MODS.has(mo) && !EVENT_FLOW_MOD_RE.test(mo));
5308
- if (bad.length) errs.push({
5309
- ...pos,
5310
- message: `E-EVENT-MODIFIER: '${a.name}' 中的 [${bad.join(", ")}] 不是内置修饰符;运行期会把它当作 KeyboardEvent.key 过滤,而 '${evName}' 不是键盘事件 ⇒ 处理器恒不触发`
5567
+ message: `W-EVENT-NO-HANDLER: '${a.name}' 没有插值处理器,运行期只注册空监听器(noop)——是否遗漏了 \${…}?`
5311
5568
  });
5312
5569
  }
5313
- if (varCount === 0 && mods.length === 0) errs.push({
5314
- ...pos,
5315
- severity: "warn",
5316
- message: `W-EVENT-NO-HANDLER: '${a.name}' 没有插值处理器,运行期只注册空监听器(noop)——是否遗漏了 \${…}?`
5317
- });
5318
- }
5319
- if (a.name.startsWith(".") && varCount === 0) {
5320
- errs.push({
5321
- ...pos,
5322
- message: `E-PROP-INTERP: Prop '${a.name}' 必须是插值(.foo="\${expr}");静态值请直接写属性或改用 bind 指令`
5323
- });
5324
- continue;
5325
- }
5326
- if (a.name.startsWith("@mutate")) {
5327
- if (!a.name.slice(1).split(".").slice(1).some((mo) => MUTATE_TYPE_MODS.has(mo))) errs.push({
5328
- ...pos,
5329
- message: `E-MUTATE-MODIFIER: 事件 '${a.name}' 缺少变异类型修饰符,必须使用 @mutate.attr/child/char/tree 之一(裸 @mutate 运行期无法 observe)`
5330
- });
5331
- }
5332
- if (a.name.startsWith("@") && varCount === 1 && varPart) {
5333
- const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5334
- if (INLINE_FN_RE.test(exprSource)) continue;
5335
- const m = THIS_MEMBER_RE.exec(exprSource);
5336
- if (m) {
5337
- const name = m[1];
5338
- if (members === null || members.has(name) || KNOWN_BASE_MEMBERS.has(name)) continue;
5570
+ if (a.name.startsWith(".") && varCount === 0) {
5339
5571
  errs.push({
5340
5572
  ...pos,
5341
- message: `E-EVENT-FN: 事件 '${a.name}' 的处理器 '${exprSource}' 不是组件 ${comp.className} 的成员`
5573
+ message: `E-PROP-INTERP: Prop '${a.name}' 必须是插值(.foo="\${expr}");静态值请直接写属性或改用 bind 指令`
5342
5574
  });
5575
+ continue;
5343
5576
  }
5344
- continue;
5345
- }
5346
- if (a.name === "ref" && varCount === 1 && varPart) {
5347
- const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5348
- const m = THIS_MEMBER_RE.exec(exprSource);
5349
- if (m && members !== null) {
5350
- const name = m[1];
5351
- if (!members.has(name)) errs.push({
5577
+ if (a.name.startsWith("@mutate")) {
5578
+ if (!a.name.slice(1).split(".").slice(1).some((mo) => MUTATE_TYPE_MODS.has(mo))) errs.push({
5352
5579
  ...pos,
5353
- message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 不是组件 ${comp.className} 的成员`
5580
+ message: `E-MUTATE-MODIFIER: 事件 '${a.name}' 缺少变异类型修饰符,必须使用 @mutate.attr/child/char/tree 之一(裸 @mutate 运行期无法 observe)`
5354
5581
  });
5355
- else {
5356
- const member = findMemberNode(comp, ctx, name);
5357
- if (member?.type === "PropertyDefinition" && member.value != null) {
5358
- if (!isCreateRefCall(member.value, comp)) errs.push({
5359
- ...pos,
5360
- message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 初始化器不是 createRef() 调用,运行时将报「Ref must be a RefObject」`
5361
- });
5582
+ }
5583
+ if (a.name.startsWith("@") && varCount === 1 && varPart) {
5584
+ const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5585
+ if (INLINE_FN_RE.test(exprSource)) continue;
5586
+ const m = THIS_MEMBER_RE.exec(exprSource);
5587
+ if (m) {
5588
+ const name = m[1];
5589
+ if (members === null || members.has(name) || KNOWN_BASE_MEMBERS.has(name)) continue;
5590
+ errs.push({
5591
+ ...pos,
5592
+ message: `E-EVENT-FN: 事件 '${a.name}' 的处理器 '${exprSource}' 不是组件 ${comp.className} 的成员`
5593
+ });
5594
+ }
5595
+ continue;
5596
+ }
5597
+ if (a.name === "ref" && varCount === 1 && varPart) {
5598
+ const exprSource = tmpl.vars[varPart.index]?.exprSource ?? "";
5599
+ const m = THIS_MEMBER_RE.exec(exprSource);
5600
+ if (m && members !== null) {
5601
+ const name = m[1];
5602
+ if (!members.has(name)) errs.push({
5603
+ ...pos,
5604
+ message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 不是组件 ${comp.className} 的成员`
5605
+ });
5606
+ else {
5607
+ const member = findMemberNode(comp, ctx, name);
5608
+ if (member?.type === "PropertyDefinition" && member.value != null) {
5609
+ if (!isCreateRefCall(member.value, comp)) errs.push({
5610
+ ...pos,
5611
+ message: `E-REF-TYPE: ref 绑定的 '${exprSource}' 初始化器不是 createRef() 调用,运行时将报「Ref must be a RefObject」`
5612
+ });
5613
+ }
5362
5614
  }
5363
5615
  }
5364
5616
  }
@@ -7929,16 +8181,21 @@ function compileFile(code, id, options = {}) {
7929
8181
  const mdRes = extractMethodDecos(comp, code, convCtx);
7930
8182
  conventionErrors.push(...mdRes.errors);
7931
8183
  const mdHelpers = mdRes.runtimeHelpers.map((n) => helperBindings[n] ?? n);
8184
+ const resRes = extractResources(comp, code, convCtx);
8185
+ conventionErrors.push(...resRes.errors);
8186
+ const resHelpers = resRes.runtimeHelpers.map((n) => helperBindings[n] ?? n);
7932
8187
  const allRemovals = [
7933
8188
  ...watchRemovals,
7934
8189
  ...fieldRes.removals,
7935
- ...mdRes.removals
8190
+ ...mdRes.removals,
8191
+ ...resRes.removals
7936
8192
  ];
7937
8193
  const fieldCodes = {
7938
8194
  props: fieldRes.propsCode,
7939
8195
  states: fieldRes.statesCode,
7940
8196
  computedGetters: fieldRes.computedCode,
7941
- accessors: fieldRes.accessors || void 0
8197
+ resources: resRes.resourcesCode,
8198
+ accessors: fieldRes.accessors + resRes.accessors || void 0
7942
8199
  };
7943
8200
  const ctorPlan = fieldRes.fieldInits.length > 0 || !!mdRes.ctorStmts ? planCtorInsert(comp, comp.cls ?? convCtx.localClasses.get(comp.className), code) : void 0;
7944
8201
  if (ctorPlan?.warn) conventionErrors.push({
@@ -7954,7 +8211,11 @@ function compileFile(code, id, options = {}) {
7954
8211
  methodDecoCtor: mdRes.ctorStmts || void 0,
7955
8212
  methodDecoDestroy: mdRes.destroyStmts || void 0,
7956
8213
  get helpers() {
7957
- const all = [...fieldHelpers, ...writers];
8214
+ const all = [
8215
+ ...fieldHelpers,
8216
+ ...resHelpers,
8217
+ ...writers
8218
+ ];
7958
8219
  return all.length ? [...new Set(all)] : void 0;
7959
8220
  },
7960
8221
  myfxHelpers: mdHelpers.length ? [...mdHelpers] : void 0
@@ -7999,19 +8260,20 @@ function compileFile(code, id, options = {}) {
7999
8260
  continue;
8000
8261
  }
8001
8262
  if (comp.skipInjection) {
8002
- if (!!fieldRes.accessors || fieldRes.propsCode !== void 0 || fieldRes.statesCode !== void 0 || fieldRes.computedCode !== void 0 || !!cssEffectCode || watchEffectsCode) {
8263
+ if (!!fieldRes.accessors || !!resRes.accessors || fieldRes.propsCode !== void 0 || fieldRes.statesCode !== void 0 || fieldRes.computedCode !== void 0 || resRes.resourcesCode !== void 0 || !!cssEffectCode || watchEffectsCode) {
8003
8264
  const mk = (label, body) => `${DEBUG_DIV} ${label}\n${body}`;
8004
8265
  const parts = [
8005
8266
  mk("inherits", `...Reflect.getPrototypeOf(this).__ce_static__`),
8006
8267
  mk("props", `props: ${emitFamily("props", fieldRes.propsCode)}`),
8007
8268
  mk("states", `states: ${emitFamily("states", fieldRes.statesCode)}`),
8008
- mk("computedGetters", `computedGetters: ${emitFamily("computedGetters", fieldRes.computedCode)}`)
8269
+ mk("computedGetters", `computedGetters: ${emitFamily("computedGetters", fieldRes.computedCode)}`),
8270
+ mk("resources", `resources: ${emitFamily("resources", resRes.resourcesCode)}`)
8009
8271
  ];
8010
8272
  if (watchEffectsCode) parts.push(mk("watchEffects", `watchEffects: ${emitWatchEffects(watchEffectsCode, watchRes.inlinedAncestors)}`));
8011
8273
  if (cssEffectCode) parts.push(mk("cssEffect", `cssEffect: ${cssEffectCode}`));
8012
8274
  edits.push({
8013
8275
  comp,
8014
- literal: `static __ce_static__ = {\n${parts.join(",\n")}\n};\n` + (fieldRes.accessors || ""),
8276
+ literal: `static __ce_static__ = {\n${parts.join(",\n")}\n};\n` + (fieldRes.accessors + resRes.accessors || ""),
8015
8277
  removals: allRemovals,
8016
8278
  ...fieldExtras
8017
8279
  });
@@ -8444,55 +8706,28 @@ function compileFile(code, id, options = {}) {
8444
8706
  additions
8445
8707
  };
8446
8708
  }
8447
-
8448
- //#endregion
8449
- //#region src/vite.ts
8450
- const TAG_RE = /@tag\(\s*['"]([^'"]+)['"]/g;
8451
8709
  /**
8452
- * 用于 transform 缓存键的按文件指纹(见 tagFingerprint)。
8453
- */
8454
- const FILE_HYPHEN_TAG_RE = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)/g;
8455
- /**
8456
- * 按文件标签指纹(替代全局 `knownTags.size`)。
8710
+ * 编译**已确定要编译**的源码,跳过 `include` 判定。
8711
+ *
8712
+ * 与 {@link compileFile} 的分工:
8713
+ * - `compileFile` 面向「磁盘上可能是组件、也可能不是」的输入,先按扩展名 +
8714
+ * 特征做 include 判定;
8715
+ * - `compileSource` 面向「调用方已经决定要编译」的输入 —— 典型是 SFC 插件把
8716
+ * `.ce` 分段拼成的虚拟 TypeScript 模块。虚拟模块的 id 仍是 `.ce`(为了
8717
+ * sourcemap 指向真实文件),天然过不了 `defaultInclude` 的扩展名检查,
8718
+ * 以前只能传 `include: () => true` 绕过整个判定(连 node_modules 排除也一起绕过)。
8719
+ *
8720
+ * `options.include` 在这里被忽略。
8457
8721
  */
8458
- function tagFingerprint(usedTags, knownTags) {
8459
- if (usedTags.size === 0) return "";
8460
- const inter = [];
8461
- for (const t of usedTags) if (knownTags.has(t)) inter.push(t);
8462
- if (inter.length === 0) return "";
8463
- inter.sort();
8464
- return hash32(inter.join("\n"));
8465
- }
8466
- const SKIP_DIRS = new Set([
8467
- "node_modules",
8468
- "dist",
8469
- "build",
8470
- "out",
8471
- "coverage"
8472
- ]);
8473
- /** 递归收集 root 下 .ts/.tsx 的 @tag 名(跨文件标签注册表,TEMPLATE-CODEGEN.md §4)。 */
8474
- function scanKnownTags(root, out, depth = 0) {
8475
- if (depth > 20) return;
8476
- let entries;
8477
- try {
8478
- entries = readdirSync(root, { withFileTypes: true });
8479
- } catch {
8480
- return;
8481
- }
8482
- for (const e of entries) {
8483
- if (e.name.startsWith(".")) continue;
8484
- if (e.isDirectory()) {
8485
- if (SKIP_DIRS.has(e.name)) continue;
8486
- scanKnownTags(join(root, e.name), out, depth + 1);
8487
- } else if (/\.tsx?$/.test(e.name) && !e.name.endsWith(".d.ts")) try {
8488
- const code = readFileSync(join(root, e.name), "utf-8");
8489
- if (code.indexOf("@tag") < 0 && code.indexOf("CompElem") < 0) continue;
8490
- TAG_RE.lastIndex = 0;
8491
- let m;
8492
- while (m = TAG_RE.exec(code)) out.add(m[1].toLowerCase());
8493
- } catch {}
8494
- }
8722
+ function compileSource(code, id, options = {}) {
8723
+ return compileFile(code, id, {
8724
+ ...options,
8725
+ include: () => true
8726
+ });
8495
8727
  }
8728
+
8729
+ //#endregion
8730
+ //#region src/diagnostics.ts
8496
8731
  /** 惰性构建换行偏移索引(升序;仅在有诊断时付费一次)。 */
8497
8732
  function newlineIndex(code) {
8498
8733
  const arr = [];
@@ -8519,13 +8754,22 @@ function offsetToLineCol(code, offset, nl) {
8519
8754
  const lastNl = lo > 0 ? nl[lo - 1] : -1;
8520
8755
  return `${lo + 1}:${offset - lastNl}`;
8521
8756
  }
8757
+ /** 相对 root 的展示路径(统一正斜杠,便于点击跳转)。 */
8758
+ function relativeToRoot(id, root) {
8759
+ const norm = (s) => s.replace(/\\/g, "/");
8760
+ const rel = norm(id);
8761
+ const base = norm(root);
8762
+ return rel.startsWith(base) ? rel.slice(base.length + 1) : id;
8763
+ }
8522
8764
  /**
8523
- * 从 diagnostics 收集**约定诊断**为可上报文本,按 severity 分为 errs(E-*)与 warns(W-*)
8765
+ * 从 diagnostics 收集**约定诊断**为可上报文本,按 severity 分为 errs(E-*)与 warns(W-*)。
8766
+ *
8767
+ * `root` 只用于把绝对路径压成相对路径,不影响判定。
8524
8768
  */
8525
- function collectErrors(res, code, id, root) {
8769
+ function collectDiagnostics(res, code, id, root) {
8526
8770
  const errs = [];
8527
8771
  const warns = [];
8528
- const rel = id.startsWith(root) ? id.replace(/\\/g, "/").slice(root.replace(/\\/g, "/").length + 1) : id;
8772
+ const rel = relativeToRoot(id, root);
8529
8773
  let nl;
8530
8774
  for (const d of res.diagnostics) {
8531
8775
  const list = d.conventionErrors?.map((e) => ({
@@ -8544,8 +8788,13 @@ function collectErrors(res, code, id, root) {
8544
8788
  warns
8545
8789
  };
8546
8790
  }
8547
- /** 按 mode 上报违规(error 阻断构建 / warn 控制台 / silent 忽略)。 */
8548
- function reportErrors(msgs, mode, head, thisArg) {
8791
+ /**
8792
+ * 按 mode 上报违规:error 走插件上下文的 `this.error`(阻断构建),warn 走控制台。
8793
+ *
8794
+ * `thisArg` 是 Rollup/Vite 的插件上下文;纯 API 调用时传 undefined 也能工作
8795
+ * (退化为 `throw`)。
8796
+ */
8797
+ function reportDiagnostics(msgs, mode, head, thisArg) {
8549
8798
  if (mode === "silent" || !msgs.length) return;
8550
8799
  const text = head + "\n " + msgs.join("\n ");
8551
8800
  if (mode === "error") {
@@ -8554,6 +8803,86 @@ function reportErrors(msgs, mode, head, thisArg) {
8554
8803
  else throw new Error(text);
8555
8804
  } else console.warn(text);
8556
8805
  }
8806
+ /**
8807
+ * 一站式上报:W 码恒为控制台提示(不随档位阻断),E 码按 mode 处理。
8808
+ *
8809
+ * 返回是否发生了阻断(仅用于调用方决定是否继续)。
8810
+ */
8811
+ function reportConventionDiagnostics(collected, mode, thisArg, tag = "[compelem/compiler]") {
8812
+ if (mode === "silent") return;
8813
+ const { errs, warns } = collected;
8814
+ if (warns.length) console.warn(`${tag} ${warns.length} 条提示:\n ` + warns.join("\n "));
8815
+ reportDiagnostics(errs, mode, `${tag} 检测到 ${errs.length} 处约定错误:`, thisArg);
8816
+ }
8817
+
8818
+ //#endregion
8819
+ //#region src/tag-registry.ts
8820
+ /** `@tag('x-foo')` / `@tag(\`x-foo\`)`;一次扫描内复用,调用方负责重置 lastIndex。 */
8821
+ const TAG_RE = /@tag\s*\(\s*['"`]([^'"`]+)['"`]/g;
8822
+ /** 默认只把 `.ts` / `.tsx` 当作组件来源。 */
8823
+ const DEFAULT_TAG_EXT = /\.tsx?$/;
8824
+ const SKIP_DIRS = new Set([
8825
+ "node_modules",
8826
+ "dist",
8827
+ "build",
8828
+ "out",
8829
+ "coverage"
8830
+ ]);
8831
+ /**
8832
+ * 递归收集 root 下匹配 `extensions` 的文件里的 `@tag` 名,写入 `out`。
8833
+ *
8834
+ * 读失败的文件直接跳过:注册表是 best-effort 的,它只会**放宽**校验,
8835
+ * 不该让一次权限错误打断构建。`node_modules` 等目录不参与扫描。
8836
+ */
8837
+ function scanKnownTags(root, out, options = {}) {
8838
+ const { extensions = DEFAULT_TAG_EXT, maxDepth = 20 } = options;
8839
+ scanDir(root, out, extensions, 0, maxDepth);
8840
+ }
8841
+ function scanDir(root, out, ext, depth, maxDepth) {
8842
+ if (depth > maxDepth) return;
8843
+ let entries;
8844
+ try {
8845
+ entries = readdirSync(root, { withFileTypes: true });
8846
+ } catch {
8847
+ return;
8848
+ }
8849
+ for (const e of entries) {
8850
+ if (e.name.startsWith(".")) continue;
8851
+ if (e.isDirectory()) {
8852
+ if (SKIP_DIRS.has(e.name)) continue;
8853
+ scanDir(join(root, e.name), out, ext, depth + 1, maxDepth);
8854
+ } else if (ext.test(e.name) && !e.name.endsWith(".d.ts")) {
8855
+ let code;
8856
+ try {
8857
+ code = readFileSync(join(root, e.name), "utf-8");
8858
+ } catch {
8859
+ continue;
8860
+ }
8861
+ if (code.indexOf("@tag") < 0 && code.indexOf("CompElem") < 0) continue;
8862
+ TAG_RE.lastIndex = 0;
8863
+ let m;
8864
+ while (m = TAG_RE.exec(code)) out.add(m[1].toLowerCase());
8865
+ }
8866
+ }
8867
+ }
8868
+
8869
+ //#endregion
8870
+ //#region src/vite.ts
8871
+ /**
8872
+ * 用于 transform 缓存键的按文件指纹(见 tagFingerprint)。
8873
+ */
8874
+ const FILE_HYPHEN_TAG_RE = /<([a-z][a-z0-9]*(?:-[a-z0-9]+)+)/g;
8875
+ /**
8876
+ * 按文件标签指纹(替代全局 `knownTags.size`)。
8877
+ */
8878
+ function tagFingerprint(usedTags, knownTags) {
8879
+ if (usedTags.size === 0) return "";
8880
+ const inter = [];
8881
+ for (const t of usedTags) if (knownTags.has(t)) inter.push(t);
8882
+ if (inter.length === 0) return "";
8883
+ inter.sort();
8884
+ return hash32(inter.join("\n"));
8885
+ }
8557
8886
  /** compelem 编译期优化插件。 */
8558
8887
  function compelemCompiler(options = {}) {
8559
8888
  let root = process.cwd();
@@ -8607,11 +8936,7 @@ function compelemCompiler(options = {}) {
8607
8936
  }
8608
8937
  transformCache.set(cacheKey, res);
8609
8938
  }
8610
- if (convMode !== "silent") {
8611
- const { errs, warns } = collectErrors(res, code, id, root);
8612
- if (warns.length) console.warn(`[compelem/compiler] ${warns.length} 条提示:\n ` + warns.join("\n "));
8613
- reportErrors(errs, convMode, `[compelem/compiler] 检测到 ${errs.length} 处约定错误:`, this);
8614
- }
8939
+ if (convMode !== "silent") reportConventionDiagnostics(collectDiagnostics(res, code, id, root), convMode, this);
8615
8940
  if (!res.changed) return null;
8616
8941
  if (options.emitJs) emitCompiledJs(id, res.code, root);
8617
8942
  if (options.verbose) {
@@ -8646,5 +8971,5 @@ function emitCompiledJs(id, code, root) {
8646
8971
  var vite_default = compelemCompiler;
8647
8972
 
8648
8973
  //#endregion
8649
- export { buildStaticLiteral as a, StaticAnalysisError as c, analyzeRender as d, analyzeFile as f, parse$1 as h, defaultInclude as i, assertStaticAnalysis as l, isCompelemSource as m, vite_default as n, injectInto as o, collectCompelemImports as p, compileFile as r, DEBUG_DIV as s, compelemCompiler as t, inlineTemplateMethods as u };
8650
- //# sourceMappingURL=vite-CY_6E-Ut.js.map
8974
+ export { collectCompelemImports as C, analyzeFile as S, parse$1 as T, DEBUG_DIV as _, scanKnownTags as a, inlineTemplateMethods as b, offsetToLineCol as c, reportDiagnostics as d, compileFile as f, injectInto as g, buildStaticLiteral as h, TAG_RE as i, relativeToRoot as l, defaultInclude as m, vite_default as n, collectDiagnostics as o, compileSource as p, DEFAULT_TAG_EXT as r, newlineIndex as s, compelemCompiler as t, reportConventionDiagnostics as u, StaticAnalysisError as v, isCompelemSource as w, analyzeRender as x, assertStaticAnalysis as y };
8975
+ //# sourceMappingURL=vite-C95uwtJI.js.map