@proteus-vue/plugin-vite 0.2.0-beta.2 → 0.2.0-beta.4

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/plugin.ts
2
- import fs4 from "node:fs";
3
- import path4 from "node:path";
2
+ import fs5 from "node:fs";
3
+ import path5 from "node:path";
4
4
  import { createRequire as createRequire3 } from "node:module";
5
5
 
6
6
  // ../types/src/router-config.ts
@@ -555,7 +555,7 @@ function runGenRoutes(options) {
555
555
  "details",
556
556
  "summary"
557
557
  ]);
558
- function extractTemplateBody(src) {
558
+ function extractTemplateBody2(src) {
559
559
  const n = src.length;
560
560
  let i = 0;
561
561
  while (i < n) {
@@ -611,7 +611,7 @@ function runGenRoutes(options) {
611
611
  }
612
612
  function collectComponents(file, skipSemantic = false) {
613
613
  const src = fs2.readFileSync(file, "utf-8");
614
- const tpl = extractTemplateBody(src);
614
+ const tpl = extractTemplateBody2(src);
615
615
  const customTags = new Set(Object.keys(config.rules?.customTags ?? {}));
616
616
  const gridRuleDisabled = (config.rules?.disabled ?? []).includes("fluid/semantic-grid");
617
617
  const semanticTags = skipSemantic && !gridRuleDisabled ? /* @__PURE__ */ new Set(["p-grid"]) : /* @__PURE__ */ new Set();
@@ -898,6 +898,138 @@ function createBundleCache(cacheDir) {
898
898
  };
899
899
  }
900
900
 
901
+ // src/tag-scan.ts
902
+ import fs4 from "node:fs";
903
+ import path4 from "node:path";
904
+ function extractTemplateBody(src) {
905
+ const n = src.length;
906
+ let i = 0;
907
+ while (i < n) {
908
+ const lt = src.indexOf("<", i);
909
+ if (lt < 0) return "";
910
+ if (src.startsWith("<!--", lt)) {
911
+ const e = src.indexOf("-->", lt + 4);
912
+ i = e < 0 ? n : e + 3;
913
+ continue;
914
+ }
915
+ const block = /^<(script|style)\b/i.exec(src.slice(lt, lt + 32));
916
+ if (block) {
917
+ const tag = block[1].toLowerCase();
918
+ const end = src.toLowerCase().indexOf(`</${tag}`, lt + block[0].length);
919
+ if (end < 0) return "";
920
+ const gt = src.indexOf(">", end);
921
+ i = gt < 0 ? n : gt + 1;
922
+ continue;
923
+ }
924
+ if (/^<template[\s>]/i.test(src.slice(lt, lt + 16))) {
925
+ const gt = src.indexOf(">", lt);
926
+ if (gt < 0) return "";
927
+ let depth = 1;
928
+ let j = gt + 1;
929
+ while (j < n) {
930
+ const l2 = src.indexOf("<", j);
931
+ if (l2 < 0) return src.slice(gt + 1);
932
+ if (src.startsWith("<!--", l2)) {
933
+ const e = src.indexOf("-->", l2 + 4);
934
+ j = e < 0 ? n : e + 3;
935
+ continue;
936
+ }
937
+ const seg = src.slice(l2, l2 + 16);
938
+ if (/^<\/template[\s>]/i.test(seg)) {
939
+ depth--;
940
+ if (depth === 0) return src.slice(gt + 1, l2);
941
+ j = l2 + "</template>".length;
942
+ continue;
943
+ }
944
+ if (/^<template[\s>]/i.test(seg)) {
945
+ depth++;
946
+ const g2 = src.indexOf(">", l2);
947
+ j = g2 < 0 ? n : g2 + 1;
948
+ continue;
949
+ }
950
+ j = l2 + 1;
951
+ }
952
+ return src.slice(gt + 1);
953
+ }
954
+ i = lt + 1;
955
+ }
956
+ return "";
957
+ }
958
+ function extractTags(tpl) {
959
+ const out = /* @__PURE__ */ new Set();
960
+ let idx = 0;
961
+ while (idx < tpl.length) {
962
+ const lt = tpl.indexOf("<", idx);
963
+ if (lt < 0) break;
964
+ if (tpl.startsWith("<!--", lt)) {
965
+ const e = tpl.indexOf("-->", lt + 4);
966
+ idx = e < 0 ? tpl.length : e + 3;
967
+ continue;
968
+ }
969
+ if (tpl.startsWith("</", lt)) {
970
+ idx = lt + 2;
971
+ continue;
972
+ }
973
+ const mm = /^([A-Za-z][\w-]*)/.exec(tpl.slice(lt + 1));
974
+ if (!mm) {
975
+ idx = lt + 1;
976
+ continue;
977
+ }
978
+ const raw = mm[1];
979
+ out.add(/[A-Z]/.test(raw) ? raw.replace(/\B([A-Z])/g, "-$1").toLowerCase() : raw);
980
+ idx = lt + 1 + mm[0].length;
981
+ }
982
+ return out;
983
+ }
984
+ function collectCompilerEmittedTags(templateBody) {
985
+ const out = /* @__PURE__ */ new Set();
986
+ if (/<(?:svg|circle|rect|ellipse|path|line|polyline|polygon)[\s>][\s\S]*?<animate\b/i.test(templateBody)) {
987
+ const shapeAnim = /<animate\s[^>]*attributeName\s*=\s*["'](cx|cy|r|rx|ry|x|y|width|height|d|points|stroke-dashoffset|stroke-dasharray)["']/i;
988
+ if (shapeAnim.test(templateBody)) out.add("p-svg-canvas");
989
+ }
990
+ return out;
991
+ }
992
+ function resolveComponentFile(componentsDir, tag) {
993
+ const dirIndex = path4.join(componentsDir, tag, "index.vue");
994
+ if (fs4.existsSync(dirIndex)) return dirIndex;
995
+ const flat = path4.join(componentsDir, `${tag}.vue`);
996
+ if (fs4.existsSync(flat)) return flat;
997
+ return null;
998
+ }
999
+ function collectUsedFrameworkComponents(pageFiles, componentsDir) {
1000
+ const used = /* @__PURE__ */ new Set();
1001
+ const visitedFiles = /* @__PURE__ */ new Set();
1002
+ const queue = [...pageFiles];
1003
+ while (queue.length) {
1004
+ const file = queue.pop();
1005
+ if (visitedFiles.has(file)) continue;
1006
+ visitedFiles.add(file);
1007
+ if (!fs4.existsSync(file)) continue;
1008
+ let tags;
1009
+ const body = (() => {
1010
+ try {
1011
+ return extractTemplateBody(fs4.readFileSync(file, "utf-8"));
1012
+ } catch {
1013
+ return "";
1014
+ }
1015
+ })();
1016
+ try {
1017
+ tags = extractTags(body);
1018
+ for (const t of collectCompilerEmittedTags(body)) tags.add(t);
1019
+ } catch {
1020
+ continue;
1021
+ }
1022
+ for (const tag of tags) {
1023
+ if (used.has(tag)) continue;
1024
+ const compFile = resolveComponentFile(componentsDir, tag);
1025
+ if (!compFile) continue;
1026
+ used.add(tag);
1027
+ queue.push(compFile);
1028
+ }
1029
+ }
1030
+ return used;
1031
+ }
1032
+
901
1033
  // src/plugin.ts
902
1034
  var MP_TAG_MAP = {
903
1035
  view: "proteus-view",
@@ -925,7 +1057,15 @@ var MP_ONLY_TAGS = /* @__PURE__ */ new Set([
925
1057
  "share-element",
926
1058
  "keyboard-accessory",
927
1059
  "cover-view",
928
- "cover-image"
1060
+ "cover-image",
1061
+ // ★端对齐批次3(2026-09-16):宿主能力组件的 MP 原生标签——其模板用 v-if 双分支
1062
+ // (MP 原生 / Web 降级),Web 端死分支不渲染但会被 resolveComponent 提升解析 → 声明为自定义元素消除告警。
1063
+ "rich-text",
1064
+ "map",
1065
+ "camera",
1066
+ "canvas",
1067
+ "ad",
1068
+ "web-view"
929
1069
  ]);
930
1070
  function defaultScopedPlugin() {
931
1071
  return {
@@ -960,13 +1100,13 @@ function resolvePkgPath(projectRoot, modPath) {
960
1100
  const m = modPath.match(/^node_modules\/((?:@[^/]+\/)?[^/]+)\/([\s\S]+)$/);
961
1101
  if (m) {
962
1102
  try {
963
- const pkgRequire = createRequire3(path4.join(projectRoot, "package.json"));
964
- const pkgRoot = path4.dirname(pkgRequire.resolve(`${m[1]}/package.json`));
965
- return path4.join(pkgRoot, m[2]);
1103
+ const pkgRequire = createRequire3(path5.join(projectRoot, "package.json"));
1104
+ const pkgRoot = path5.dirname(pkgRequire.resolve(`${m[1]}/package.json`));
1105
+ return path5.join(pkgRoot, m[2]);
966
1106
  } catch {
967
1107
  }
968
1108
  }
969
- return path4.join(projectRoot, modPath);
1109
+ return path5.join(projectRoot, modPath);
970
1110
  }
971
1111
  function preprocessStyle(lang, content) {
972
1112
  if (lang === "scss" || lang === "sass") {
@@ -984,11 +1124,11 @@ function preprocessStyle(lang, content) {
984
1124
  return content;
985
1125
  }
986
1126
  function loadStyleSrcWithVariant(src, fromFilename, platform = "mp") {
987
- const base = path4.resolve(path4.dirname(fromFilename), src);
988
- const hit = resolvePlatformVariant(base, platform, fs4.existsSync);
1127
+ const base = path5.resolve(path5.dirname(fromFilename), src);
1128
+ const hit = resolvePlatformVariant(base, platform, fs5.existsSync);
989
1129
  if (!hit) return null;
990
1130
  try {
991
- return fs4.readFileSync(hit, "utf-8");
1131
+ return fs5.readFileSync(hit, "utf-8");
992
1132
  } catch {
993
1133
  return null;
994
1134
  }
@@ -997,10 +1137,10 @@ var VENDOR_SINGLETONS = ["pinia", "vue", "@vue/devtools-api"];
997
1137
  function resolveSharedModule(appDir, absFrom, source, frameworkDir, resolveFrom, platform = "mp") {
998
1138
  if (source.startsWith("@proteus-vue/")) {
999
1139
  try {
1000
- const resolver = resolveFrom ? createRequire3(path4.join(resolveFrom, "package.json")) : require2;
1001
- const pkgRoot = path4.dirname(resolver.resolve(`${source}/package.json`));
1002
- const entry = path4.join(pkgRoot, "dist", "index.js");
1003
- if (!fs4.existsSync(entry)) return null;
1140
+ const resolver = resolveFrom ? createRequire3(path5.join(resolveFrom, "package.json")) : require2;
1141
+ const pkgRoot = path5.dirname(resolver.resolve(`${source}/package.json`));
1142
+ const entry = path5.join(pkgRoot, "dist", "index.js");
1143
+ if (!fs5.existsSync(entry)) return null;
1004
1144
  return { file: entry, relNoExt: `_proteus/${source.replace("@proteus-vue/", "")}` };
1005
1145
  } catch {
1006
1146
  return null;
@@ -1014,34 +1154,34 @@ function resolveSharedModule(appDir, absFrom, source, frameworkDir, resolveFrom,
1014
1154
  return null;
1015
1155
  }
1016
1156
  };
1017
- const entry = tryResolve(path4.join(resolveFrom ?? appDir, "package.json")) ?? tryResolve(absFrom);
1018
- if (!entry || !fs4.existsSync(entry)) return null;
1157
+ const entry = tryResolve(path5.join(resolveFrom ?? appDir, "package.json")) ?? tryResolve(absFrom);
1158
+ if (!entry || !fs5.existsSync(entry)) return null;
1019
1159
  return { file: entry, relNoExt: `_proteus/${source}` };
1020
1160
  }
1021
1161
  if (!source.startsWith(".")) return null;
1022
- const base = path4.resolve(path4.dirname(absFrom), source);
1162
+ const base = path5.resolve(path5.dirname(absFrom), source);
1023
1163
  const JS_EXTS = /* @__PURE__ */ new Set([".ts", ".js", ".mjs", ".cjs"]);
1024
1164
  const variantHit = resolvePlatformVariantWithExts(base, [...JS_EXTS], platform, (p) => {
1025
1165
  try {
1026
- return fs4.statSync(p).isFile();
1166
+ return fs5.statSync(p).isFile();
1027
1167
  } catch {
1028
1168
  return false;
1029
1169
  }
1030
1170
  });
1031
- const candidates = variantHit ? [variantHit] : [base, `${base}.ts`, `${base}.js`, path4.join(base, "index.ts"), path4.join(base, "index.js")];
1171
+ const candidates = variantHit ? [variantHit] : [base, `${base}.ts`, `${base}.js`, path5.join(base, "index.ts"), path5.join(base, "index.js")];
1032
1172
  for (const cand of candidates) {
1033
1173
  if (cand.endsWith(".vue")) continue;
1034
- if (!JS_EXTS.has(path4.extname(cand).toLowerCase())) continue;
1174
+ if (!JS_EXTS.has(path5.extname(cand).toLowerCase())) continue;
1035
1175
  let isFile = false;
1036
1176
  try {
1037
- isFile = fs4.statSync(cand).isFile();
1177
+ isFile = fs5.statSync(cand).isFile();
1038
1178
  } catch {
1039
1179
  isFile = false;
1040
1180
  }
1041
1181
  if (isFile) {
1042
- let relNoExt = path4.relative(appDir, cand).replace(/\\/g, "/").replace(/\.(ts|js)$/, "");
1043
- if (relNoExt.startsWith("../") && frameworkDir && !path4.relative(frameworkDir, cand).startsWith("..")) {
1044
- relNoExt = `proteus/${path4.relative(frameworkDir, cand).replace(/\\/g, "/").replace(/\.(ts|js)$/, "")}`;
1182
+ let relNoExt = path5.relative(appDir, cand).replace(/\\/g, "/").replace(/\.(ts|js)$/, "");
1183
+ if (relNoExt.startsWith("../") && frameworkDir && !path5.relative(frameworkDir, cand).startsWith("..")) {
1184
+ relNoExt = `proteus/${path5.relative(frameworkDir, cand).replace(/\\/g, "/").replace(/\.(ts|js)$/, "")}`;
1045
1185
  }
1046
1186
  return { file: cand, relNoExt };
1047
1187
  }
@@ -1053,10 +1193,10 @@ function rewriteRootToPage(css) {
1053
1193
  }
1054
1194
  function rewriteFrameworkRequires(js, relOutput) {
1055
1195
  if (!js.includes("require('@proteus-vue/")) return js;
1056
- const pageDir = path4.posix.dirname(relOutput);
1196
+ const pageDir = path5.posix.dirname(relOutput);
1057
1197
  return js.replace(/require\('@proteus-vue\/([A-Za-z0-9_-]+)'\)/g, (m, name) => {
1058
1198
  const pkgRel = `_proteus/${name}.js`;
1059
- let rel = path4.posix.relative(pageDir, pkgRel);
1199
+ let rel = path5.posix.relative(pageDir, pkgRel);
1060
1200
  if (!rel.startsWith(".")) rel = `./${rel}`;
1061
1201
  return `require('${rel}')`;
1062
1202
  });
@@ -1105,11 +1245,11 @@ async function loadPresetBuilders(projectRoot, cfg) {
1105
1245
  for (const d of duplicates) console.warn(`[mp-transform] \u8DEF\u7531\u5B57\u6BB5 "${d}" \u5728\u9876\u5C42\u4E0E router \u6BB5\u540C\u65F6\u58F0\u660E\u2014\u2014\u5DF2\u53D6 router.${d}\uFF08#492 \u7EDF\u4E00\u8DEF\u7531\u7BA1\u7406\uFF1A\u5EFA\u8BAE\u5220\u9664\u9876\u5C42\u9057\u7559\u5199\u6CD5\uFF09`);
1106
1246
  for (const [name, modPath] of Object.entries(rc.customRoute.builders)) {
1107
1247
  const abs = resolvePkgPath(projectRoot, modPath);
1108
- if (!fs4.existsSync(abs)) {
1248
+ if (!fs5.existsSync(abs)) {
1109
1249
  console.warn(`[mp-transform] \u9884\u8BBE builder ${name} \u4E0D\u5B58\u5728\uFF1A${modPath}`);
1110
1250
  continue;
1111
1251
  }
1112
- const { code } = await esbuildTransform(fs4.readFileSync(abs, "utf-8"), { loader: "ts", charset: "utf8" });
1252
+ const { code } = await esbuildTransform(fs5.readFileSync(abs, "utf-8"), { loader: "ts", charset: "utf8" });
1113
1253
  const fnName = extractBuilderFnName(code);
1114
1254
  if (!fnName) {
1115
1255
  console.warn(`[mp-transform] \u9884\u8BBE builder ${name} \u672A\u627E\u5230\u51FD\u6570\u58F0\u660E\uFF0C\u5DF2\u8DF3\u8FC7`);
@@ -1120,10 +1260,10 @@ async function loadPresetBuilders(projectRoot, cfg) {
1120
1260
  return presets;
1121
1261
  }
1122
1262
  function walkVueFiles(dir, acc = []) {
1123
- if (!fs4.existsSync(dir)) return acc;
1124
- for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1263
+ if (!fs5.existsSync(dir)) return acc;
1264
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
1125
1265
  if (entry.name.startsWith(".")) continue;
1126
- const full = path4.join(dir, entry.name);
1266
+ const full = path5.join(dir, entry.name);
1127
1267
  if (entry.isDirectory()) walkVueFiles(full, acc);
1128
1268
  else if (entry.name.endsWith(".vue")) acc.push(full);
1129
1269
  }
@@ -1139,19 +1279,26 @@ function collectMpEntries(opts) {
1139
1279
  onSkipWebOnly?.(f);
1140
1280
  continue;
1141
1281
  }
1142
- const relBase = path4.relative(appDir, splitVariant2(f).base).replace(/\\/g, "/");
1282
+ const relBase = path5.relative(appDir, splitVariant2(f).base).replace(/\\/g, "/");
1143
1283
  out.push({ file: f, rel: relBase.replace(/\.vue$/, ""), isComponent });
1144
1284
  }
1145
1285
  };
1146
- pushRel(path4.join(projectRoot, pagesDir), false);
1147
- for (const sp of subPackages) pushRel(path4.join(projectRoot, sp.root), false);
1148
- pushRel(path4.join(appDir, "components"), true);
1286
+ pushRel(path5.join(projectRoot, pagesDir), false);
1287
+ for (const sp of subPackages) pushRel(path5.join(projectRoot, sp.root), false);
1288
+ pushRel(path5.join(appDir, "components"), true);
1289
+ const pageFiles = out.filter((t) => !t.isComponent).map((t) => t.file);
1290
+ const emitAll = (opts.componentEmit ?? "used") === "all";
1291
+ const usedComponents = emitAll ? null : collectUsedFrameworkComponents(pageFiles, componentsDir);
1149
1292
  for (const f of effectiveVariants2(walkVueFiles(componentsDir), platform)) {
1150
1293
  if (webOnlyPages?.has(f)) {
1151
1294
  onSkipWebOnly?.(f);
1152
1295
  continue;
1153
1296
  }
1154
- const relIn = path4.relative(componentsDir, splitVariant2(f).base).replace(/\\/g, "/").replace(/\.vue$/, "");
1297
+ if (usedComponents) {
1298
+ const compName = path5.relative(componentsDir, splitVariant2(f).base).replace(/\\/g, "/").split("/")[0];
1299
+ if (!usedComponents.has(compName)) continue;
1300
+ }
1301
+ const relIn = path5.relative(componentsDir, splitVariant2(f).base).replace(/\\/g, "/").replace(/\.vue$/, "");
1155
1302
  out.push({ file: f, rel: `proteus/${relIn}`, isComponent: true });
1156
1303
  }
1157
1304
  return out;
@@ -1184,13 +1331,13 @@ function mpTransform(opts) {
1184
1331
  projectRoot = resolved.root;
1185
1332
  },
1186
1333
  async buildStart() {
1187
- const appDir = path4.join(projectRoot, path4.dirname(cfg.pagesDir));
1188
- const compileCache = createCompileCache(path4.join(projectRoot, "node_modules", ".cache", "proteus", "compile"));
1189
- const bundleCache = createBundleCache(path4.join(projectRoot, "node_modules", ".cache", "proteus", "bundle"));
1334
+ const appDir = path5.join(projectRoot, path5.dirname(cfg.pagesDir));
1335
+ const compileCache = createCompileCache(path5.join(projectRoot, "node_modules", ".cache", "proteus", "compile"));
1336
+ const bundleCache = createBundleCache(path5.join(projectRoot, "node_modules", ".cache", "proteus", "bundle"));
1190
1337
  const webOnlyPages = /* @__PURE__ */ new Set();
1191
1338
  const detectWebOnly = (file) => {
1192
1339
  try {
1193
- const src = fs4.readFileSync(file, "utf-8");
1340
+ const src = fs5.readFileSync(file, "utf-8");
1194
1341
  const m = src.match(/<route>\s*([\s\S]*?)<\/route>/);
1195
1342
  if (!m) return;
1196
1343
  if (/"?webOnly"?\s*:\s*true/.test(m[1])) {
@@ -1210,10 +1357,10 @@ function mpTransform(opts) {
1210
1357
  } catch {
1211
1358
  }
1212
1359
  };
1213
- for (const pagesRoot of [path4.join(projectRoot, cfg.pagesDir), ...(effectiveSubPackages ?? []).map((sp) => path4.join(projectRoot, sp.root))]) {
1360
+ for (const pagesRoot of [path5.join(projectRoot, cfg.pagesDir), ...(effectiveSubPackages ?? []).map((sp) => path5.join(projectRoot, sp.root))]) {
1214
1361
  for (const f of walkVueFiles(pagesRoot)) detectWebOnly(f);
1215
1362
  }
1216
- const frameworkComponents = opts.componentsDir ? path4.resolve(projectRoot, opts.componentsDir) : resolveComponentsRoot(projectRoot);
1363
+ const frameworkComponents = opts.componentsDir ? path5.resolve(projectRoot, opts.componentsDir) : resolveComponentsRoot(projectRoot);
1217
1364
  const files = collectMpEntries({
1218
1365
  projectRoot,
1219
1366
  appDir,
@@ -1221,20 +1368,22 @@ function mpTransform(opts) {
1221
1368
  subPackages: effectiveSubPackages ?? [],
1222
1369
  componentsDir: frameworkComponents,
1223
1370
  webOnlyPages,
1224
- onSkipWebOnly: (f) => console.log(`[mp-transform] \u8DF3\u8FC7 webOnly \u9875\u9762\uFF1A${path4.relative(projectRoot, f).replace(/\\/g, "/")}`)
1371
+ onSkipWebOnly: (f) => console.log(`[mp-transform] \u8DF3\u8FC7 webOnly \u9875\u9762\uFF1A${path5.relative(projectRoot, f).replace(/\\/g, "/")}`),
1372
+ // ★框架组件按引用输出(2026-09-18);PROTEUS_COMPONENTS_EMIT=all 回退全量(非常规用法逃生舱)
1373
+ componentEmit: process.env.PROTEUS_COMPONENTS_EMIT === "all" ? "all" : "used"
1225
1374
  });
1226
1375
  const appUsesStore = files.some((f) => {
1227
1376
  try {
1228
- const s = fs4.readFileSync(f.file, "utf-8");
1377
+ const s = fs5.readFileSync(f.file, "utf-8");
1229
1378
  const script = s.includes("<script") ? s.match(/<script[^>]*>([\s\S]*?)<\/script>/i)?.[1] ?? "" : s;
1230
1379
  return /\buse[A-Z]\w*Store\s*\(/.test(script);
1231
1380
  } catch {
1232
1381
  return false;
1233
1382
  }
1234
1383
  });
1235
- const mpEntry = path4.join(appDir, "main.mp.ts");
1236
- if (fs4.existsSync(mpEntry)) {
1237
- const src = fs4.readFileSync(mpEntry, "utf-8");
1384
+ const mpEntry = path5.join(appDir, "main.mp.ts");
1385
+ if (fs5.existsSync(mpEntry)) {
1386
+ const src = fs5.readFileSync(mpEntry, "utf-8");
1238
1387
  const { code } = await esbuildTransform(src, { loader: "ts", charset: "utf8" });
1239
1388
  const presets = filterOverriddenPresets(code, await loadPresetBuilders(projectRoot, cfg));
1240
1389
  const piniaInstall = appUsesStore ? " // \u2605\u9875\u9762\u4F7F\u7528 store \u2192 \u5B89\u88C5\u5E76\u6FC0\u6D3B Pinia\uFF08\u5C0F\u7A0B\u5E8F\u65E0 createApp\uFF0C\u5FC5\u987B setActivePinia\uFF09\n var __proteusPiniaMod = require('./_proteus/runtime.js')\n if (__proteusPiniaMod && __proteusPiniaMod.createMpPinia) __proteusPiniaMod.createMpPinia()" : " // \uFF08\u672A\u68C0\u6D4B\u5230 store \u4F7F\u7528\u2014\u2014\u8DF3\u8FC7 Pinia \u5B89\u88C5\uFF09";
@@ -1243,19 +1392,19 @@ function mpTransform(opts) {
1243
1392
  console.log(`[mp-transform] app.js \u5DF2\u76F4\u51FA\uFF08${isDebug ? "debug" : "\u6B63\u5F0F"}\uFF09\uFF0C\u5185\u7F6E\u9884\u8BBE\uFF1A${presets.map((p) => p.name).join("/") || "\u65E0"}${appUsesStore ? "\uFF0CPinia \u5DF2\u5B89\u88C5" : ""}`);
1244
1393
  }
1245
1394
  {
1246
- const explicit = cfg.globalStyle ? path4.resolve(projectRoot, cfg.globalStyle) : void 0;
1395
+ const explicit = cfg.globalStyle ? path5.resolve(projectRoot, cfg.globalStyle) : void 0;
1247
1396
  const candidates = [
1248
1397
  explicit,
1249
- path4.join(appDir, "app.wxss"),
1250
- path4.join(projectRoot, "app.wxss")
1398
+ path5.join(appDir, "app.wxss"),
1399
+ path5.join(projectRoot, "app.wxss")
1251
1400
  ].filter((p) => Boolean(p));
1252
- const globalStylePath = candidates.find((p) => fs4.existsSync(p));
1401
+ const globalStylePath = candidates.find((p) => fs5.existsSync(p));
1253
1402
  if (globalStylePath) {
1254
- const raw = fs4.readFileSync(globalStylePath, "utf-8");
1403
+ const raw = fs5.readFileSync(globalStylePath, "utf-8");
1255
1404
  const normalized = rewriteRootToPage(raw);
1256
1405
  const wxss = transformStyleToWxss(normalized, { px2rpx: cfg.style?.px2rpx ?? true, rpxRatio: cfg.style?.rpxRatio ?? 2, rules: cfg.rules });
1257
1406
  this.emitFile({ type: "asset", fileName: "app.wxss", source: wxss });
1258
- console.log(`[mp-transform] app.wxss \u5DF2\u4EA7\u51FA\uFF08${path4.relative(projectRoot, globalStylePath).replace(/\\/g, "/")}\u2014\u2014\u5168\u5C40\u8BBE\u8BA1 token/\u91CD\u7F6E\uFF0C:root\u2192page\uFF09`);
1407
+ console.log(`[mp-transform] app.wxss \u5DF2\u4EA7\u51FA\uFF08${path5.relative(projectRoot, globalStylePath).replace(/\\/g, "/")}\u2014\u2014\u5168\u5C40\u8BBE\u8BA1 token/\u91CD\u7F6E\uFF0C:root\u2192page\uFF09`);
1259
1408
  }
1260
1409
  }
1261
1410
  const moduleImportsByFile = /* @__PURE__ */ new Map();
@@ -1263,7 +1412,7 @@ function mpTransform(opts) {
1263
1412
  const sharedRelNoExt = /* @__PURE__ */ new Map();
1264
1413
  const resolveShared = (absFrom, source) => resolveSharedModule(appDir, absFrom, source, frameworkComponents, projectRoot, "mp");
1265
1414
  const scanImports = (absFile) => {
1266
- const src = fs4.readFileSync(absFile, "utf-8");
1415
+ const src = fs5.readFileSync(absFile, "utf-8");
1267
1416
  const script = src.includes("<script") ? src.match(/<script[^>]*>([\s\S]*?)<\/script>/i)?.[1] ?? "" : src;
1268
1417
  return scanSourceImports(script);
1269
1418
  };
@@ -1327,21 +1476,21 @@ function mpTransform(opts) {
1327
1476
  name: "proteus-pkg-require-path",
1328
1477
  setup(b) {
1329
1478
  const mapExternal = (target) => {
1330
- const dir = path4.posix.dirname(relNoExt);
1331
- let rel = path4.posix.relative(dir, target);
1479
+ const dir = path5.posix.dirname(relNoExt);
1480
+ let rel = path5.posix.relative(dir, target);
1332
1481
  if (!rel.startsWith(".")) rel = `./${rel}`;
1333
1482
  return { path: rel, external: true };
1334
1483
  };
1335
1484
  b.onResolve({ filter: /^@proteus-vue\// }, (args) => mapExternal(`_proteus/${args.path.replace("@proteus-vue/", "")}.js`));
1336
1485
  b.onResolve({ filter: new RegExp(`^(${VENDOR_SINGLETONS.join("|")})$`) }, (args) => mapExternal(`_proteus/${args.path}.js`));
1337
1486
  b.onLoad({ filter: /\.(md|txt|json)$/ }, (args) => ({
1338
- contents: `export default ${JSON.stringify(fs4.readFileSync(args.path, "utf-8"))}`,
1487
+ contents: `export default ${JSON.stringify(fs5.readFileSync(args.path, "utf-8"))}`,
1339
1488
  loader: "js"
1340
1489
  }));
1341
1490
  b.onLoad({ filter: /\.(png|jpe?g|gif|webp|svg|ico|woff2?|ttf|eot|mp3|mp4|wav|zip)$/ }, (args) => ({
1342
1491
  errors: [
1343
1492
  {
1344
- text: `MP \u4EA7\u7269\u4E0D\u652F\u6301\u4E8C\u8FDB\u5236\u8D44\u6E90 import\uFF1A${path4.relative(projectRoot, args.path)}\u2014\u2014\u8BF7\u6539\u7528\u7F51\u7EDC URL \u6216 base64 \u5185\u8054`
1493
+ text: `MP \u4EA7\u7269\u4E0D\u652F\u6301\u4E8C\u8FDB\u5236\u8D44\u6E90 import\uFF1A${path5.relative(projectRoot, args.path)}\u2014\u2014\u8BF7\u6539\u7528\u7F51\u7EDC URL \u6216 base64 \u5185\u8054`
1345
1494
  }
1346
1495
  ]
1347
1496
  }));
@@ -1400,7 +1549,7 @@ function mpTransform(opts) {
1400
1549
  const inputFiles = Object.keys(build.metafile.inputs);
1401
1550
  const inputs = inputFiles.map((f) => {
1402
1551
  try {
1403
- const st = fs4.statSync(f);
1552
+ const st = fs5.statSync(f);
1404
1553
  return { file: f, mtimeMs: st.mtimeMs, size: st.size };
1405
1554
  } catch {
1406
1555
  return null;
@@ -1415,18 +1564,18 @@ function mpTransform(opts) {
1415
1564
  for (const [file, list] of moduleImportsByFile) {
1416
1565
  const entry = files.find((f) => f.file === file);
1417
1566
  if (!entry) continue;
1418
- const pageDir = path4.posix.dirname(entry.rel);
1567
+ const pageDir = path5.posix.dirname(entry.rel);
1419
1568
  for (const item of list) {
1420
1569
  const shared = resolveShared(file, item.source);
1421
1570
  if (!shared) continue;
1422
1571
  const sharedRel = `${shared.relNoExt}.js`;
1423
- let rel = path4.posix.relative(pageDir, sharedRel);
1572
+ let rel = path5.posix.relative(pageDir, sharedRel);
1424
1573
  if (!rel.startsWith(".")) rel = `./${rel}`;
1425
1574
  item.requirePath = rel;
1426
1575
  }
1427
1576
  }
1428
1577
  for (const { file, rel, isComponent } of files) {
1429
- const source = fs4.readFileSync(file, "utf-8");
1578
+ const source = fs5.readFileSync(file, "utf-8");
1430
1579
  if (rustCompiler) {
1431
1580
  const v = verifyDualCompilerEquivalence(source, { rustBin: rustCliBin, filename: file });
1432
1581
  if (v.status === "ok") {
@@ -1546,21 +1695,21 @@ function mpTransform(opts) {
1546
1695
  console.log(`[mp-transform] ${rel} \u2192 wxml/js/wxss \u5DF2\u8F93\u51FA`);
1547
1696
  }
1548
1697
  {
1549
- const publicDir = path4.join(projectRoot, "public");
1550
- if (fs4.existsSync(publicDir)) {
1698
+ const publicDir = path5.join(projectRoot, "public");
1699
+ if (fs5.existsSync(publicDir)) {
1551
1700
  const rels = [];
1552
1701
  const walk = (dir) => {
1553
- for (const e of fs4.readdirSync(dir, { withFileTypes: true })) {
1702
+ for (const e of fs5.readdirSync(dir, { withFileTypes: true })) {
1554
1703
  if (e.name.startsWith(".")) continue;
1555
- const full = path4.join(dir, e.name);
1704
+ const full = path5.join(dir, e.name);
1556
1705
  if (e.isDirectory()) walk(full);
1557
- else rels.push(path4.relative(publicDir, full).replace(/\\/g, "/"));
1706
+ else rels.push(path5.relative(publicDir, full).replace(/\\/g, "/"));
1558
1707
  }
1559
1708
  };
1560
1709
  walk(publicDir);
1561
1710
  let assetN = 0;
1562
1711
  for (const { from, to } of mapPublicAssetVariants(rels, "mp")) {
1563
- this.emitFile({ type: "asset", fileName: to, source: fs4.readFileSync(path4.join(publicDir, from)) });
1712
+ this.emitFile({ type: "asset", fileName: to, source: fs5.readFileSync(path5.join(publicDir, from)) });
1564
1713
  assetN++;
1565
1714
  }
1566
1715
  if (assetN) console.log(`[mp-transform] public \u9759\u6001\u8D44\u6E90 \u2192 ${assetN} \u4E2A\uFF08\u5E73\u53F0\u53D8\u4F53\u5DF2\u6309 mp \u89E3\u6790\uFF09`);
@@ -1592,8 +1741,8 @@ function mpTransform(opts) {
1592
1741
  }
1593
1742
 
1594
1743
  // src/devtools-plugin.ts
1595
- import fs5 from "node:fs";
1596
- import path5 from "node:path";
1744
+ import fs6 from "node:fs";
1745
+ import path6 from "node:path";
1597
1746
  import { createRequire as createRequire4 } from "node:module";
1598
1747
  import { WebSocketServer } from "ws";
1599
1748
 
@@ -1664,7 +1813,7 @@ function isOriginAllowed(origin, allowFrom) {
1664
1813
  return allowFrom.indexOf(origin) >= 0;
1665
1814
  }
1666
1815
  function resolveDevtoolsDir() {
1667
- return path5.dirname(require_.resolve("@proteus-vue/devtools/package.json"));
1816
+ return path6.dirname(require_.resolve("@proteus-vue/devtools/package.json"));
1668
1817
  }
1669
1818
  function createPanelPageHandler(devtoolsDir) {
1670
1819
  return (req, res) => {
@@ -1673,19 +1822,19 @@ function createPanelPageHandler(devtoolsDir) {
1673
1822
  if (pathname === base || pathname === base + "/") {
1674
1823
  const host = req.headers?.host ?? "localhost";
1675
1824
  const proto = req.headers?.["x-forwarded-proto"] === "https" ? "wss" : "ws";
1676
- const html = fs5.readFileSync(path5.join(devtoolsDir, "panel.html"), "utf8").replace(/__PROTEUS_DEFAULT_WS__/g, `'${proto}://${host}/proteus-panel'`).replace("./style.css", base + "/style.css").replace("./dist/panel.js", base + "/panel.js");
1825
+ const html = fs6.readFileSync(path6.join(devtoolsDir, "panel.html"), "utf8").replace(/__PROTEUS_DEFAULT_WS__/g, `'${proto}://${host}/proteus-panel'`).replace("./style.css", base + "/style.css").replace("./dist/panel.js", base + "/panel.js");
1677
1826
  res.setHeader("content-type", "text/html; charset=utf-8");
1678
1827
  res.end(html);
1679
1828
  return true;
1680
1829
  }
1681
1830
  if (pathname === base + "/style.css") {
1682
1831
  res.setHeader("content-type", "text/css; charset=utf-8");
1683
- res.end(fs5.readFileSync(path5.join(devtoolsDir, "style.css")));
1832
+ res.end(fs6.readFileSync(path6.join(devtoolsDir, "style.css")));
1684
1833
  return true;
1685
1834
  }
1686
1835
  if (pathname === base + "/panel.js") {
1687
1836
  res.setHeader("content-type", "application/javascript; charset=utf-8");
1688
- res.end(fs5.readFileSync(path5.join(devtoolsDir, "dist", "panel.js")));
1837
+ res.end(fs6.readFileSync(path6.join(devtoolsDir, "dist", "panel.js")));
1689
1838
  return true;
1690
1839
  }
1691
1840
  return false;
@@ -1759,8 +1908,8 @@ function devtoolsRelayPlugin(opts = {}) {
1759
1908
  }
1760
1909
 
1761
1910
  // src/vite-config.ts
1762
- import path6 from "node:path";
1763
- import fs6 from "node:fs";
1911
+ import path7 from "node:path";
1912
+ import fs7 from "node:fs";
1764
1913
  import { pathToFileURL } from "node:url";
1765
1914
  import { createRequire as createRequire5 } from "node:module";
1766
1915
  import {
@@ -1794,25 +1943,25 @@ function platformVariantPlugin(root, platform) {
1794
1943
  const bare = qIdx >= 0 ? id.slice(0, qIdx) : id;
1795
1944
  if (!bare) return null;
1796
1945
  let base;
1797
- if (bare.startsWith(".")) base = path6.resolve(path6.dirname(importer), bare);
1798
- else if (bare.startsWith("@/")) base = path6.resolve(root, "src", bare.slice(2));
1946
+ if (bare.startsWith(".")) base = path7.resolve(path7.dirname(importer), bare);
1947
+ else if (bare.startsWith("@/")) base = path7.resolve(root, "src", bare.slice(2));
1799
1948
  else return null;
1800
- const hasExt = path6.extname(base) !== "";
1949
+ const hasExt = path7.extname(base) !== "";
1801
1950
  if (hasExt) {
1802
- const r2 = resolvePlatformVariant2(base, platform, fs6.existsSync);
1951
+ const r2 = resolvePlatformVariant2(base, platform, fs7.existsSync);
1803
1952
  return r2 && r2 !== base ? r2 + query : null;
1804
1953
  }
1805
- const r = resolvePlatformVariantWithExts2(base, CODE_EXTS, platform, fs6.existsSync);
1954
+ const r = resolvePlatformVariantWithExts2(base, CODE_EXTS, platform, fs7.existsSync);
1806
1955
  return r ? r + query : null;
1807
1956
  }
1808
1957
  };
1809
1958
  }
1810
1959
  function hasPublicVariants(publicDir) {
1811
- if (!fs6.existsSync(publicDir)) return false;
1960
+ if (!fs7.existsSync(publicDir)) return false;
1812
1961
  const walk = (dir) => {
1813
- for (const e of fs6.readdirSync(dir, { withFileTypes: true })) {
1962
+ for (const e of fs7.readdirSync(dir, { withFileTypes: true })) {
1814
1963
  if (e.name.startsWith(".")) continue;
1815
- const full = path6.join(dir, e.name);
1964
+ const full = path7.join(dir, e.name);
1816
1965
  if (e.isDirectory()) {
1817
1966
  if (walk(full)) return true;
1818
1967
  } else if (splitVariant3(e.name).platform !== void 0) {
@@ -1828,20 +1977,20 @@ function platformPublicAssetsPlugin(root, platform) {
1828
1977
  name: "proteus-platform-public-assets",
1829
1978
  apply: "build",
1830
1979
  generateBundle() {
1831
- const publicDir = path6.join(root, "public");
1832
- if (!fs6.existsSync(publicDir)) return;
1980
+ const publicDir = path7.join(root, "public");
1981
+ if (!fs7.existsSync(publicDir)) return;
1833
1982
  const rels = [];
1834
1983
  const walk = (dir) => {
1835
- for (const e of fs6.readdirSync(dir, { withFileTypes: true })) {
1984
+ for (const e of fs7.readdirSync(dir, { withFileTypes: true })) {
1836
1985
  if (e.name.startsWith(".")) continue;
1837
- const full = path6.join(dir, e.name);
1986
+ const full = path7.join(dir, e.name);
1838
1987
  if (e.isDirectory()) walk(full);
1839
- else rels.push(path6.relative(publicDir, full).replace(/\\/g, "/"));
1988
+ else rels.push(path7.relative(publicDir, full).replace(/\\/g, "/"));
1840
1989
  }
1841
1990
  };
1842
1991
  walk(publicDir);
1843
1992
  for (const { from, to } of mapPublicAssetVariants2(rels, platform)) {
1844
- this.emitFile({ type: "asset", fileName: to, source: fs6.readFileSync(path6.join(publicDir, from)) });
1993
+ this.emitFile({ type: "asset", fileName: to, source: fs7.readFileSync(path7.join(publicDir, from)) });
1845
1994
  }
1846
1995
  }
1847
1996
  };
@@ -1869,7 +2018,7 @@ function virtualMpEntryPlugin() {
1869
2018
  };
1870
2019
  }
1871
2020
  async function importFromRoot(root, spec) {
1872
- const req = createRequire5(path6.join(root, "package.json"));
2021
+ const req = createRequire5(path7.join(root, "package.json"));
1873
2022
  const resolved = req.resolve(spec);
1874
2023
  return import(pathToFileURL(resolved).href);
1875
2024
  }
@@ -1906,15 +2055,15 @@ async function resolveProteusViteConfig(ctx, config) {
1906
2055
  // ★平台变体·静态资源 Web 通道(第 3 层):public/ 含平台变体(logo.web.png)时,
1907
2056
  // 关掉 Vite 默认逐字拷贝(会把他端变体也拷进产物),改由 platformPublicAssetsPlugin 按 web 解析;
1908
2057
  // 无变体时保持默认(零侵入,避免改变既有工程行为)。
1909
- publicDir: hasPublicVariants(path6.join(root, "public")) ? false : void 0,
2058
+ publicDir: hasPublicVariants(path7.join(root, "public")) ? false : void 0,
1910
2059
  resolve: {
1911
- alias: [{ find: "@", replacement: path6.join(root, "src") }]
2060
+ alias: [{ find: "@", replacement: path7.join(root, "src") }]
1912
2061
  },
1913
2062
  build: {
1914
2063
  target: "es2018",
1915
2064
  cssCodeSplit: false,
1916
2065
  minify: isMp ? false : void 0,
1917
- outDir: path6.join(root, "dist", platform),
2066
+ outDir: path7.join(root, "dist", platform),
1918
2067
  emptyOutDir: !isMp,
1919
2068
  rollupOptions: isMp ? { input: "proteus:mp-entry", output: { entryFileNames: "mp-entry.js" } } : void 0
1920
2069
  }
package/dist/plugin.d.ts CHANGED
@@ -119,6 +119,8 @@ export declare function collectMpEntries(opts: {
119
119
  onSkipWebOnly?: (file: string) => void;
120
120
  /** ★平台变体(2026-09-13):按该平台解析 `foo.mp.vue`/`foo.web.vue`(缺省 mp) */
121
121
  platform?: VariantPlatform;
122
+ /** ★框架组件输出策略(2026-09-18):'used'(缺省,按引用闭包输出)/ 'all'(全量,逃生舱) */
123
+ componentEmit?: 'used' | 'all';
122
124
  }): Array<{
123
125
  file: string;
124
126
  rel: string;
@@ -0,0 +1,28 @@
1
+ /** 抽取 `<template>` 块正文(跳过注释 / script / style / 嵌套 template 深度) */
2
+ export declare function extractTemplateBody(src: string): string;
3
+ /**
4
+ * 扫描模板正文中出现的全部标签名(kebab 化;跳过注释块)。
5
+ * PascalCase(`<PSafe>`)→ kebab(`p-safe`)——★旧正则只匹配小写开头,
6
+ * 导致 `<PSafe>` 完全跳过扫描(既不注册也不告警 → 组件静默不渲染,showcase 踩坑)。
7
+ */
8
+ export declare function extractTags(tpl: string): Set<string>;
9
+ /**
10
+ * ★编译器**产出**的组件标签(源码模板中不存在,由 compiler lowering 生成)。
11
+ * 必须计入引用集合,否则按需输出会把这些组件漏掉 → 运行时不渲染(真 bug)。
12
+ *
13
+ * 与 gen-routes 的 collectComponents 同源规则(两处必须一致):
14
+ * 源码含**形状变化动画**的 SVG(cx/r/d/stroke-dashoffset 等)→ 编译器 lowering 为 `<p-svg-canvas>`。
15
+ */
16
+ export declare function collectCompilerEmittedTags(templateBody: string): Set<string>;
17
+ /**
18
+ * 计算页面**实际引用**的框架组件目录集合(含组件间传递依赖闭包)。
19
+ *
20
+ * 用途:MP 产物只输出用到的组件(而非全量 76 个 = 607 KB)——对每个真实应用都是可观瘦身。
21
+ * ★诚实边界:仅识别**静态模板标签**。运行时动态拼标签不受支持(MP 本就无 `<component :is>`,
22
+ * 编译器已对 `<component :is>` 显式告警)——若确有非常规用法,可用 `components.emit: 'all'` 关闭本优化。
23
+ *
24
+ * @param pageFiles 页面源文件绝对路径(主包 + 分包,**须已排除 webOnly 页面**)
25
+ * @param componentsDir 框架组件根目录
26
+ * @returns 组件目录名集合(如 `p-view`);空集表示「未引用任何框架组件」
27
+ */
28
+ export declare function collectUsedFrameworkComponents(pageFiles: readonly string[], componentsDir: string): Set<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteus-vue/plugin-vite",
3
- "version": "0.2.0-beta.2",
3
+ "version": "0.2.0-beta.4",
4
4
  "description": "Proteus Vite 插件(mp-weixin 编译管线适配层)+ gen-routes 路由表生成器",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -19,14 +19,14 @@
19
19
  "README.md"
20
20
  ],
21
21
  "dependencies": {
22
- "@proteus-vue/compiler": "0.3.0-beta.2",
23
- "@proteus-vue/module": "0.1.0",
24
- "@proteus-vue/router": "0.2.0-beta.2",
25
- "@proteus-vue/types": "0.2.0-beta.1",
22
+ "@proteus-vue/compiler": "0.3.0-beta.3",
23
+ "@proteus-vue/module": "0.1.1-beta.0",
24
+ "@proteus-vue/router": "0.2.0-beta.4",
25
+ "@proteus-vue/types": "0.2.0-beta.2",
26
26
  "esbuild": "^0.28.2",
27
27
  "sass": "^1.103.1",
28
28
  "ws": "^8.0.0",
29
- "@proteus-vue/compiler-backend": "0.1.1-beta.0"
29
+ "@proteus-vue/compiler-backend": "0.1.1-beta.1"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "vite": "^5.0.0"