@open-wxa/wxa-runner-core 0.10.0 → 0.11.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/index.js CHANGED
@@ -33,26 +33,106 @@ __export(src_exports, {
33
33
  AppBuilder: () => AppBuilder,
34
34
  DEFAULT_DEVICE: () => DEFAULT_DEVICE,
35
35
  DevServer: () => DevServer,
36
+ LOCAL_HOST_OF_ANY_ADDRESS: () => LOCAL_HOST_OF_ANY_ADDRESS,
36
37
  MiniProgram: () => MiniProgram,
37
- createWxConfig: () => createWxConfig
38
+ bundleSetup: () => bundleSetup,
39
+ createWxConfig: () => createWxConfig,
40
+ findRuntimeDir: () => findRuntimeDir,
41
+ findSetupFile: () => findSetupFile,
42
+ handleProxy: () => handleProxy,
43
+ listen: () => listen
38
44
  });
39
45
  module.exports = __toCommonJS(src_exports);
40
46
 
41
47
  // src/server/DevServer.ts
42
- var import_crypto4 = require("crypto");
48
+ var import_crypto3 = require("crypto");
43
49
 
44
50
  // src/server/FileStore.ts
45
- var import_crypto = require("crypto");
51
+ var storeSequence = 0;
52
+ var textEncoder = new TextEncoder();
53
+ var textDecoder = new TextDecoder("utf-8", { ignoreBOM: true });
54
+ function normalizeEncoding(encoding) {
55
+ switch (String(encoding).toLowerCase()) {
56
+ case "utf8":
57
+ case "utf-8":
58
+ return "utf8";
59
+ case "utf16le":
60
+ case "utf-16le":
61
+ case "ucs2":
62
+ case "ucs-2":
63
+ return "utf16le";
64
+ case "latin1":
65
+ case "binary":
66
+ return "latin1";
67
+ case "ascii":
68
+ return "ascii";
69
+ case "base64":
70
+ case "base64url":
71
+ return String(encoding).toLowerCase();
72
+ case "hex":
73
+ return "hex";
74
+ default:
75
+ throw new Error(`Unknown encoding: ${encoding}`);
76
+ }
77
+ }
78
+ function encodeText(text, encoding) {
79
+ const format = normalizeEncoding(encoding);
80
+ if (format === "utf8") return textEncoder.encode(text);
81
+ if (format === "base64" || format === "base64url") {
82
+ let base64 = text.split("=", 1)[0].replace(/-/g, "+").replace(/_/g, "/").replace(/[^A-Za-z0-9+/]/g, "");
83
+ if (base64.length % 4 === 1) base64 = base64.slice(0, -1);
84
+ base64 = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
85
+ return Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));
86
+ }
87
+ if (format === "hex") {
88
+ const bytes2 = [];
89
+ for (let index = 0; index + 1 < text.length; index += 2) {
90
+ const pair = text.slice(index, index + 2);
91
+ if (!/^[\da-f]{2}$/i.test(pair)) break;
92
+ bytes2.push(parseInt(pair, 16));
93
+ }
94
+ return new Uint8Array(bytes2);
95
+ }
96
+ const bytes = new Uint8Array(text.length * (format === "utf16le" ? 2 : 1));
97
+ for (let index = 0; index < text.length; index++) {
98
+ const code = text.charCodeAt(index);
99
+ if (format === "utf16le") {
100
+ bytes[index * 2] = code & 255;
101
+ bytes[index * 2 + 1] = code >>> 8;
102
+ } else bytes[index] = code & 255;
103
+ }
104
+ return bytes;
105
+ }
106
+ function decodeText(bytes, encoding) {
107
+ const format = normalizeEncoding(encoding);
108
+ if (format === "utf8") return textDecoder.decode(bytes);
109
+ if (format === "hex") return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
110
+ const chunks = [];
111
+ for (let offset = 0; offset < bytes.length; offset += 32768) {
112
+ const chunk = bytes.subarray(offset, offset + 32768);
113
+ const codes = [];
114
+ for (let index = 0; index < chunk.length; index++) {
115
+ if (format === "utf16le") {
116
+ if (index + 1 < chunk.length) codes.push(chunk[index] | chunk[++index] << 8);
117
+ } else codes.push(format === "ascii" ? chunk[index] & 127 : chunk[index]);
118
+ }
119
+ chunks.push(String.fromCharCode(...codes));
120
+ }
121
+ const text = chunks.join("");
122
+ if (format === "base64") return btoa(text);
123
+ if (format === "base64url") return btoa(text).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
124
+ return text;
125
+ }
46
126
  var FileStore = class {
47
127
  /** @param roots 预先创建的目录(如用户目录)。rmdir 不能删除它们和它们的上级目录:删掉之后再写 USER_DATA_PATH 都会失败 */
48
128
  constructor(roots = []) {
49
- this.etagPrefix = (0, import_crypto.randomUUID)();
129
+ this.etagPrefix = globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${++storeSequence}-${Math.random().toString(36).slice(2)}`;
50
130
  this.version = 0;
51
- this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" }]]);
131
+ this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: new Uint8Array(0), mtime: Date.now(), etag: "" }]]);
52
132
  this.operations = {
53
133
  readFile: ({ filePath, encoding }) => {
54
134
  const data = this.read(filePath);
55
- return encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(encoding) };
135
+ return encoding === void 0 ? { data: decodeText(data, "base64"), binary: true } : { data: decodeText(data, encoding) };
56
136
  },
57
137
  writeFile: (args) => this.writeEncoded(args, false),
58
138
  appendFile: (args) => this.writeEncoded(args, true),
@@ -114,16 +194,21 @@ var FileStore = class {
114
194
  if (!recursive) throw new Error(`no such file or directory ${dirPath}`);
115
195
  this.mkdir(parent, true);
116
196
  }
117
- this.nodes.set(target, { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" });
197
+ this.nodes.set(target, { dir: true, data: new Uint8Array(0), mtime: Date.now(), etag: "" });
118
198
  }
119
199
  write(filePath, input, append = false) {
120
- const data = Buffer.from(input);
200
+ const data = typeof input === "string" ? textEncoder.encode(input) : new Uint8Array(input);
121
201
  const target = this.normalize(filePath);
122
202
  const parent = target.slice(0, target.lastIndexOf("/")) || "/";
123
203
  if (!this.nodes.get(parent)?.dir) throw new Error(`no such file or directory ${filePath}`);
124
204
  const existing = this.nodes.get(target);
125
205
  if (existing?.dir) throw new Error(`illegal operation on a directory ${filePath}`);
126
- const contents = append && existing ? Buffer.concat([existing.data, data]) : data;
206
+ let contents = data;
207
+ if (append && existing) {
208
+ contents = new Uint8Array(existing.data.length + data.length);
209
+ contents.set(existing.data);
210
+ contents.set(data, existing.data.length);
211
+ }
127
212
  this.nodes.set(target, { dir: false, data: contents, mtime: Date.now(), etag: `${this.etagPrefix}-${++this.version}` });
128
213
  }
129
214
  read(filePath) {
@@ -158,7 +243,7 @@ var FileStore = class {
158
243
  return this.operations[operation](args);
159
244
  }
160
245
  writeEncoded(args, append) {
161
- this.write(args.filePath, Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8"), append);
246
+ this.write(args.filePath, encodeText(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8"), append);
162
247
  return {};
163
248
  }
164
249
  existing(filePath) {
@@ -347,6 +432,11 @@ function describeError(project, error) {
347
432
  return error?.message ?? String(error);
348
433
  }
349
434
 
435
+ // src/project/toJs.ts
436
+ function toJs(value) {
437
+ return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
438
+ }
439
+
350
440
  // src/project/wxConfig.ts
351
441
  var DEFAULT_WINDOW = {
352
442
  navigationBarBackgroundColor: "#000000",
@@ -686,9 +776,6 @@ try { require(${toJs(file)}); } catch (error) { console.error(${toJs("[wxa-runne
686
776
  function rewriteSafeAreaInsets(code) {
687
777
  return code.replace(/\b(?:env|constant)\(\s*safe-area-inset-(top|right|bottom|left)\b/g, "var(--wxa-safe-area-inset-$1");
688
778
  }
689
- function toJs(value) {
690
- return JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
691
- }
692
779
  function classifyFile(file) {
693
780
  if (file.endsWith(".json")) return "config";
694
781
  if (file.endsWith(".wxml") || file.endsWith(".wxs")) return "wxml";
@@ -882,155 +969,7 @@ function fromServerPath(prefix, serverPath) {
882
969
  }
883
970
  }
884
971
 
885
- // src/server/plugins.ts
886
- var import_wxa_runner_extractor2 = require("@open-wxa/wxa-runner-extractor");
887
-
888
- // src/server/wechatDev.ts
889
- var import_wxa_runner_extractor = require("@open-wxa/wxa-runner-extractor");
890
- var BASE_URL = "https://servicewechat.com";
891
- var CLIENT_VERSION = "1021810240";
892
- var DEFAULT_TIMEOUT = 1e4;
893
- var ERROR_HINTS = {
894
- // 40001:开发者工具刷新过 ticket,旧的随即作废
895
- 40001: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
896
- 40029: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
897
- 85023: "\uFF08\u5F53\u524D\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u662F\u8BE5\u5C0F\u7A0B\u5E8F\u7684\u5F00\u53D1\u8005\uFF09"
898
- };
899
- function requireUsableTicket(ticket) {
900
- if (!ticket) throw new Error(`\u672A\u627E\u5230\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\uFF0C\u8BF7\u767B\u5F55\u5F00\u53D1\u8005\u5DE5\u5177\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${import_wxa_runner_extractor.TICKET_ENV}`);
901
- if (ticket.expiredAt !== void 0 && ticket.expiredAt <= Date.now()) {
902
- throw new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6253\u5F00\u5F00\u53D1\u8005\u5DE5\u5177\u5237\u65B0\u767B\u5F55\u6001");
903
- }
904
- return ticket.newticket;
905
- }
906
- function explainErrcode(errcode) {
907
- return errcode !== void 0 && ERROR_HINTS[errcode] || "";
908
- }
909
- function requireOk(body, action) {
910
- const errcode = body.baseresponse?.errcode;
911
- if (errcode !== 0) throw new Error(`${action} ${errcode}: ${body.baseresponse?.errmsg || "\u672A\u77E5\u9519\u8BEF"}${explainErrcode(errcode)}`);
912
- return body;
913
- }
914
- var WechatDevClient = class {
915
- /**
916
- * @param findTicket 每次请求都重新取:开发者工具重新登录、切换账号后不用重启服务器
917
- */
918
- constructor(appid, findTicket) {
919
- this.appid = appid;
920
- this.findTicket = findTicket;
921
- }
922
- /**
923
- * POST JSON 到 path(如 wxa-dev-logic/jslogin),带上 appid 和开发者 ticket
924
- *
925
- * @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
926
- */
927
- async post(path7, body, timeout = DEFAULT_TIMEOUT) {
928
- const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
929
- const response = await fetch(`${BASE_URL}/${path7}?${query}`, {
930
- method: "POST",
931
- headers: { "Content-Type": "application/json" },
932
- body: JSON.stringify(body),
933
- signal: AbortSignal.timeout(timeout)
934
- }).catch((error) => {
935
- throw new Error(error.name === "TimeoutError" ? `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u8D85\u65F6\uFF08${timeout / 1e3}s\uFF09` : `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u5931\u8D25\uFF1A${error.cause?.message ?? error.message}`);
936
- });
937
- if (!response.ok) throw new Error(`\u5FAE\u4FE1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
938
- return response;
939
- }
940
- /** 同 post,返回解析后的 JSON */
941
- async postJson(path7, body) {
942
- return (await this.post(path7, body)).json();
943
- }
944
- };
945
-
946
- // src/server/plugins.ts
947
- var DOWNLOAD_TIMEOUT = 3e4;
948
- var PACKAGE_MAGIC = 190;
949
- var PluginStore = class {
950
- /**
951
- * @param wechat 开启 --devtools-login 时才有,没有时不下载插件
952
- */
953
- constructor(wechat, logger) {
954
- this.wechat = wechat;
955
- this.logger = logger;
956
- /** provider@version → 下载结果;失败的不留,下次打开小程序时重试 */
957
- this.downloads = /* @__PURE__ */ new Map();
958
- /**
959
- * 这次打开小程序时决定用哪些插件。同一次打开的逻辑层、业务模块、渲染层等请求都按它处理:
960
- * 各自去下载的话,下载失败时每个请求都会重试,结果还可能前后不一,页面列表和逻辑层对不上
961
- */
962
- this.selection = null;
963
- /** 没开 --devtools-login 的提示每个插件只说一次 */
964
- this.warned = /* @__PURE__ */ new Set();
965
- }
966
- /** 打开小程序时(宿主取 app-info)调用:重新决定用哪些插件,下载过的直接用,上次失败的重试 */
967
- async refresh(build2) {
968
- this.selection = this.select(build2);
969
- return withPlugins(build2, await this.selection);
970
- }
971
- /** 其余请求沿用这次打开时的决定,不触发下载;还没决定过时(如直接请求逻辑层)先决定一次 */
972
- async current(build2) {
973
- this.selection ??= this.select(build2);
974
- return withPlugins(build2, await this.selection);
975
- }
976
- /** app.json 声明的插件;下载不了的跳过,在终端说明原因,小程序自己的页面照常可用 */
977
- async select(build2) {
978
- const declared = build2.wxConfig.plugins || {};
979
- const results = await Promise.all(Object.entries(declared).map(
980
- ([alias, { provider, version }]) => provider && version ? this.load(alias, provider, version) : null
981
- ));
982
- return results.filter((plugin) => !!plugin);
983
- }
984
- async load(alias, provider, version) {
985
- const label = `\u63D2\u4EF6 ${alias}\uFF08${provider}@${version}\uFF09`;
986
- if (!this.wechat) {
987
- this.warnOnce(label, `[wxa-runner] ${label} \u7684\u4EE3\u7801\u8981\u7528\u5F00\u53D1\u8005\u7684\u767B\u5F55\u6001\u5411\u5FAE\u4FE1\u4E0B\u8F7D\uFF0C\u542F\u52A8\u65F6\u52A0 --devtools-login`);
988
- return null;
989
- }
990
- const key = `${provider}@${version}`;
991
- let download = this.downloads.get(key);
992
- if (!download) {
993
- download = this.download(this.wechat, alias, provider, version);
994
- this.downloads.set(key, download);
995
- download.catch(() => this.downloads.delete(key));
996
- }
997
- try {
998
- return await download;
999
- } catch (error) {
1000
- this.logger.warn(`[wxa-runner] ${label} \u4E0B\u8F7D\u5931\u8D25\uFF1A${error.message}`);
1001
- return null;
1002
- }
1003
- }
1004
- /** @throws 没有登录态、连不上、微信拒绝或返回的不是插件代码包时,消息是原因 */
1005
- async download(wechat, alias, provider, version) {
1006
- const response = await wechat.post("wxa-dev/getplugincode", { plugin_id: provider, plugin_version: version }, DOWNLOAD_TIMEOUT);
1007
- const data = Buffer.from(await response.arrayBuffer());
1008
- if (data[0] !== PACKAGE_MAGIC) {
1009
- let body = {};
1010
- try {
1011
- body = JSON.parse(data.toString("utf8"));
1012
- } catch {
1013
- }
1014
- if (body.baseresponse?.errcode) requireOk(body, "\u5FAE\u4FE1\u62D2\u7EDD\u4E0B\u8F7D");
1015
- throw new Error("\u5FAE\u4FE1\u6CA1\u6709\u8FD4\u56DE\u63D2\u4EF6\u4EE3\u7801");
1016
- }
1017
- const files = new Map((0, import_wxa_runner_extractor2.readWxvpkgIndex)(data).map((entry) => [entry.name, Buffer.from((0, import_wxa_runner_extractor2.readWxvpkgFile)(data, entry))]));
1018
- const pluginJson = JSON.parse(files.get("/plugin.json")?.toString("utf8") || "{}");
1019
- return {
1020
- alias,
1021
- provider,
1022
- version,
1023
- files,
1024
- pages: Object.values(pluginJson.pages || {}),
1025
- pageConfigs: pluginJson.page || {}
1026
- };
1027
- }
1028
- warnOnce(key, message) {
1029
- if (this.warned.has(key)) return;
1030
- this.warned.add(key);
1031
- this.logger.warn(message);
1032
- }
1033
- };
972
+ // src/server/pluginCode.ts
1034
973
  function withPlugins(build2, plugins) {
1035
974
  if (!plugins.length) return { ...build2, plugins };
1036
975
  return {
@@ -1175,7 +1114,8 @@ importScripts(${modules.map((url) => toJs(url)).join(", ")});
1175
1114
  }
1176
1115
  function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId, options = {}) {
1177
1116
  const id = Number(webviewId) || 0;
1178
- const page = plugin.files.get(`/${innerPath}.html`)?.toString("utf8") ?? `<script>console.error(${JSON.stringify(`[wxa-runner] \u63D2\u4EF6 ${plugin.alias} \u91CC\u6CA1\u6709\u9875\u9762 ${innerPath}`)})</script>`;
1117
+ const html2 = plugin.files.get(`/${innerPath}.html`);
1118
+ const page = html2 ? new TextDecoder().decode(html2) : `<script>console.error(${JSON.stringify(`[wxa-runner] \u63D2\u4EF6 ${plugin.alias} \u91CC\u6CA1\u6709\u9875\u9762 ${innerPath}`)})</script>`;
1179
1119
  return `<!DOCTYPE html>
1180
1120
  <html>
1181
1121
  <head>
@@ -1218,6 +1158,94 @@ function escapeHtml(text) {
1218
1158
  return text.replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch]);
1219
1159
  }
1220
1160
 
1161
+ // src/server/appRoutes.ts
1162
+ var DEFAULT_DEVICE = {
1163
+ model: "iPhone X",
1164
+ screenWidth: 375,
1165
+ screenHeight: 812,
1166
+ pixelRatio: 3,
1167
+ statusBarHeight: 44,
1168
+ safeAreaBottom: 34,
1169
+ system: "iOS 16.0",
1170
+ platform: "devtools"
1171
+ };
1172
+ var PLUGIN_SCRIPT = /^\/__wxa__\/plugin\/([^/]+)\/([^/]+)\/(appservice\.js|pageframe\.js)$/;
1173
+ function routeApp(source, url) {
1174
+ const { build: build2 } = source;
1175
+ const pathname = safeDecode(url.pathname);
1176
+ if (pathname === null) return null;
1177
+ const query = url.searchParams;
1178
+ switch (pathname) {
1179
+ case "/__wxa__/appservice.html": {
1180
+ const launchPath = (query.get("path") || "").replace(/^\//, "");
1181
+ const launch = build2.pages.includes(launchPath) ? { path: launchPath, query: Object.fromEntries(new URLSearchParams(query.get("query") || "")) } : { path: build2.pages[0], query: {} };
1182
+ return html(appserviceHtml(build2, launch, { hot: source.hot, isolated: source.isolated }));
1183
+ }
1184
+ case "/__wxa__/subcontext.html": {
1185
+ const id = Number(query.get("id"));
1186
+ if (!Number.isInteger(id) || id <= 0) return { status: 400, type: "text", body: "\u5B50\u4E0A\u4E0B\u6587 id \u65E0\u6548" };
1187
+ return html(subcontextHtml(id, query.get("empty") === "1"));
1188
+ }
1189
+ case "/__wxa__/worker.js":
1190
+ return js(workerScript(build2, source.baseLib));
1191
+ case "/__wxa__/service-bootstrap.js":
1192
+ return js(build2.serviceBootstrap);
1193
+ case "/__wxa__/service-templates.js":
1194
+ return js(build2.serviceTemplates);
1195
+ case "/__wxa__/page-templates.js":
1196
+ return js(build2.pageTemplates);
1197
+ case "/__wxa__/page-styles.js":
1198
+ return js(build2.pageStyles);
1199
+ case "/__wxa__/page-frame.js":
1200
+ return js(build2.pageFrame);
1201
+ }
1202
+ const webview = /^\/__wxa__\/webview\/(.+)\.html$/.exec(pathname);
1203
+ if (webview) {
1204
+ const pagePath = webview[1];
1205
+ if (!build2.pages.includes(pagePath)) return null;
1206
+ const webviewId = Number(query.get("id"));
1207
+ const pluginPage = findPluginPage(build2, pagePath);
1208
+ return html(pluginPage ? pluginWebviewHtml(build2, pluginPage.plugin, pluginPage.innerPath, pagePath, webviewId, { scripts: source.webviewScripts }) : webviewHtml(build2, pagePath, webviewId, { hot: source.hot, scripts: source.webviewScripts }));
1209
+ }
1210
+ const pluginScript = PLUGIN_SCRIPT.exec(pathname);
1211
+ if (pluginScript) {
1212
+ const [, provider, version, file] = pluginScript;
1213
+ const code = build2.plugins.find((plugin) => plugin.provider === provider && plugin.version === version)?.files.get(`/${file}`);
1214
+ return code ? { status: 200, type: "js", body: code } : null;
1215
+ }
1216
+ const pluginFile = /^\/__plugin__\/([^/]+)(\/.+)$/.exec(pathname);
1217
+ if (pluginFile) {
1218
+ const file = build2.plugins.find((plugin) => plugin.provider === pluginFile[1])?.files.get(pluginFile[2]);
1219
+ return file ? { status: 200, type: extname(pluginFile[2]) || "application/octet-stream", body: file } : null;
1220
+ }
1221
+ const module2 = /^\/__wxa__\/module\/(.+\.js)$/.exec(pathname);
1222
+ if (module2) {
1223
+ const id = module2[1];
1224
+ if (!build2.modules.includes(id) && !build2.workerModules.includes(id)) {
1225
+ return { status: 404, type: "js", body: `console.error(${JSON.stringify(`[wxa-runner] \u6A21\u5757 ${id} \u4E0D\u5B58\u5728`)})` };
1226
+ }
1227
+ return js(source.moduleScript(id));
1228
+ }
1229
+ return null;
1230
+ }
1231
+ function extname(filePath) {
1232
+ const match = /\.[^./]+$/.exec(filePath);
1233
+ return match ? match[0] : "";
1234
+ }
1235
+ function html(body) {
1236
+ return { status: 200, type: "html", body };
1237
+ }
1238
+ function js(body) {
1239
+ return { status: 200, type: "js", body };
1240
+ }
1241
+ function safeDecode(pathname) {
1242
+ try {
1243
+ return decodeURIComponent(pathname);
1244
+ } catch {
1245
+ return null;
1246
+ }
1247
+ }
1248
+
1221
1249
  // src/server/ignoreList.ts
1222
1250
  var STRIDE = 100;
1223
1251
  var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -1296,48 +1324,64 @@ function ignoreConsoleHook(sourceMap) {
1296
1324
  // src/server/proxy.ts
1297
1325
  var import_node_stream = require("stream");
1298
1326
  var import_promises = require("stream/promises");
1327
+
1328
+ // src/server/proxyFetch.ts
1299
1329
  var DROPPED_REQUEST_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding", "keep-alive", "upgrade", "expect"]);
1300
1330
  var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "content-length", "transfer-encoding", "connection", "set-cookie"]);
1301
- async function handleProxy(req, res) {
1302
- const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
1303
- if (!target) {
1304
- sendProxyError(res, 400, `invalid url "${req.originalUrl}"`);
1305
- return;
1306
- }
1307
- const controller = new AbortController();
1308
- const cancel = () => controller.abort();
1309
- res.once("close", cancel);
1331
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
1332
+ async function proxyFetch(request, options = {}) {
1333
+ const url = new URL(request.url);
1334
+ const path7 = url.pathname + url.search;
1335
+ const target = fromServerPath(PROXY_PREFIX, path7);
1336
+ if (!target) return proxyError(400, `invalid url "${path7}"`);
1310
1337
  try {
1338
+ let current = new URL(target);
1339
+ if (options.isAllowed && !options.isAllowed(current)) return forbiddenHost(current, 0);
1340
+ const headers = new Headers(
1341
+ forwardedRequestHeaders(request.headers.get("x-wxa-headers") ?? void 0, request.headers.get("content-type") ?? "")
1342
+ );
1343
+ let method = request.method;
1344
+ let body = request.body && !["GET", "HEAD"].includes(method) ? await request.arrayBuffer() : void 0;
1345
+ const send = options.fetch ?? fetch;
1311
1346
  let upstream;
1312
- try {
1313
- upstream = await fetch(target, {
1314
- method: req.method,
1315
- headers: forwardedRequestHeaders(req.get("x-wxa-headers"), req.get("content-type")),
1316
- body: requestBody(req),
1317
- redirect: "follow",
1318
- signal: controller.signal
1347
+ for (let hop = 0; ; hop++) {
1348
+ upstream = await send(current.href, {
1349
+ method,
1350
+ headers,
1351
+ body,
1352
+ redirect: options.isAllowed ? "manual" : "follow",
1353
+ signal: request.signal
1319
1354
  });
1320
- } catch (error) {
1321
- if (res.destroyed || controller.signal.aborted) return;
1322
- sendProxyError(res, 502, fetchErrorMessage(error));
1323
- return;
1355
+ const location = upstream.headers.get("location");
1356
+ if (!options.isAllowed || !REDIRECT_STATUSES.has(upstream.status) || location === null) break;
1357
+ await upstream.body?.cancel();
1358
+ const next = new URL(location, current);
1359
+ if (hop >= 20) return proxyError(403, `host "${next.hostname}": redirect hop ${hop + 1} exceeds limit 20`);
1360
+ if (!["http:", "https:"].includes(next.protocol) || !options.isAllowed(next)) return forbiddenHost(next, hop + 1);
1361
+ if (upstream.status === 303 && method !== "GET" && method !== "HEAD" || (upstream.status === 301 || upstream.status === 302) && method === "POST") {
1362
+ method = "GET";
1363
+ body = void 0;
1364
+ for (const name of ["content-type", "content-encoding", "content-language", "content-location"]) headers.delete(name);
1365
+ }
1366
+ if (next.origin !== current.origin) {
1367
+ for (const name of ["authorization", "proxy-authorization", "cookie"]) headers.delete(name);
1368
+ }
1369
+ current = next;
1324
1370
  }
1325
- await relayResponse(upstream, res);
1371
+ return relayResponse(upstream);
1326
1372
  } catch (error) {
1327
- if (!res.destroyed) res.destroy(error);
1328
- } finally {
1329
- res.off("close", cancel);
1373
+ return proxyError(502, fetchErrorMessage(error));
1330
1374
  }
1331
1375
  }
1332
- function requestBody(req) {
1333
- const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
1334
- return hasBody ? req.body : void 0;
1376
+ function forbiddenHost(url, hop) {
1377
+ return proxyError(403, `host "${url.hostname}" is not in the allowlist (redirect hop ${hop})`);
1335
1378
  }
1336
- function fetchErrorMessage(error) {
1337
- return error?.cause?.message || error.message;
1379
+ function proxyError(status, message) {
1380
+ return new Response(message, { status, headers: { "x-wxa-proxy-error": "1", "content-type": "text/html; charset=utf-8" } });
1338
1381
  }
1339
- function sendProxyError(res, status, message) {
1340
- res.status(status).set("x-wxa-proxy-error", "1").send(message);
1382
+ function fetchErrorMessage(error) {
1383
+ const detail = error;
1384
+ return detail?.cause?.message || detail?.message || String(error);
1341
1385
  }
1342
1386
  function forwardedRequestHeaders(encoded, browserContentType = "") {
1343
1387
  const multipart = browserContentType.startsWith("multipart/form-data");
@@ -1352,27 +1396,61 @@ function forwardedRequestHeaders(encoded, browserContentType = "") {
1352
1396
  }
1353
1397
  function parseHeaders(encoded = "{}") {
1354
1398
  try {
1355
- return JSON.parse(encoded);
1399
+ const parsed = JSON.parse(encoded);
1400
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1356
1401
  } catch {
1357
1402
  return {};
1358
1403
  }
1359
1404
  }
1360
- async function relayResponse(upstream, res) {
1405
+ function relayResponse(upstream) {
1361
1406
  const responseHeaders = {};
1407
+ const headers = new Headers();
1362
1408
  upstream.headers.forEach((value, key) => {
1363
1409
  responseHeaders[key] = value;
1364
- if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
1410
+ if (!DROPPED_RESPONSE_HEADERS.has(key)) headers.set(key, value);
1365
1411
  });
1366
- res.status(upstream.status);
1367
- res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
1412
+ headers.set("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
1368
1413
  const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
1369
- res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
1370
- res.flushHeaders();
1371
- if (!upstream.body) {
1372
- res.end();
1373
- return;
1414
+ headers.set("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
1415
+ return new Response(upstream.body, { status: upstream.status, statusText: upstream.statusText, headers });
1416
+ }
1417
+
1418
+ // src/server/proxy.ts
1419
+ async function handleProxy(req, res) {
1420
+ const controller = new AbortController();
1421
+ const cancel = () => controller.abort();
1422
+ res.once("close", cancel);
1423
+ try {
1424
+ const headers = new Headers();
1425
+ for (const name of ["x-wxa-headers", "content-type"]) {
1426
+ const value = req.get(name);
1427
+ if (value !== void 0) headers.set(name, value);
1428
+ }
1429
+ const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
1430
+ const request = new globalThis.Request(`http://localhost${req.originalUrl}`, {
1431
+ method: req.method,
1432
+ headers,
1433
+ body: hasBody ? req.body : void 0,
1434
+ signal: controller.signal
1435
+ });
1436
+ const response = await proxyFetch(request);
1437
+ if (res.destroyed || controller.signal.aborted) {
1438
+ await response.body?.cancel();
1439
+ return;
1440
+ }
1441
+ response.headers.forEach((value, name) => res.setHeader(name, value));
1442
+ res.status(response.status);
1443
+ res.flushHeaders();
1444
+ if (!response.body) {
1445
+ res.end();
1446
+ return;
1447
+ }
1448
+ await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(response.body), res);
1449
+ } catch (error) {
1450
+ if (!res.destroyed) res.destroy(error);
1451
+ } finally {
1452
+ res.off("close", cancel);
1374
1453
  }
1375
- await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(upstream.body), res);
1376
1454
  }
1377
1455
 
1378
1456
  // src/server/hmr.ts
@@ -1391,18 +1469,18 @@ function planHotUpdate(files, units) {
1391
1469
 
1392
1470
  // src/server/developerTicket.ts
1393
1471
  var import_child_process = require("child_process");
1394
- var import_crypto2 = require("crypto");
1472
+ var import_crypto = require("crypto");
1395
1473
  var import_promises2 = require("timers/promises");
1396
1474
  var import_ws = require("ws");
1397
- var import_wxa_runner_extractor3 = require("@open-wxa/wxa-runner-extractor");
1475
+ var import_wxa_runner_extractor = require("@open-wxa/wxa-runner-extractor");
1398
1476
  var AUTO_PORT = 9420;
1399
1477
  var CLI_TIMEOUT = 6e4;
1400
1478
  var AUTOMATOR_TIMEOUT = 5e3;
1401
1479
  var WINDOW_TIMEOUT = 1e4;
1402
1480
  var WINDOW_RETRY_INTERVAL = 500;
1403
1481
  function developerTicketSource(projectPath, logger) {
1404
- const cli = (0, import_wxa_runner_extractor3.findElectronDevtoolsCli)();
1405
- if (!cli) return async () => (0, import_wxa_runner_extractor3.findDeveloperTicket)();
1482
+ const cli = (0, import_wxa_runner_extractor.findElectronDevtoolsCli)();
1483
+ if (!cli) return async () => (0, import_wxa_runner_extractor.findDeveloperTicket)();
1406
1484
  let automatorPort = null;
1407
1485
  const startOnce = () => {
1408
1486
  if (!automatorPort) {
@@ -1418,7 +1496,7 @@ function developerTicketSource(projectPath, logger) {
1418
1496
  return automatorPort;
1419
1497
  };
1420
1498
  return async () => {
1421
- if (process.env[import_wxa_runner_extractor3.TICKET_ENV]) return { newticket: process.env[import_wxa_runner_extractor3.TICKET_ENV] };
1499
+ if (process.env[import_wxa_runner_extractor.TICKET_ENV]) return { newticket: process.env[import_wxa_runner_extractor.TICKET_ENV] };
1422
1500
  const port = startOnce();
1423
1501
  try {
1424
1502
  return await requestTicketOnceWindowReady(await port, projectPath);
@@ -1483,7 +1561,7 @@ function cliErrorMessage(stdout) {
1483
1561
  }
1484
1562
  function requestTicket(port) {
1485
1563
  return new Promise((resolve, reject) => {
1486
- const id = `wxa-runner-${(0, import_crypto2.randomUUID)()}`;
1564
+ const id = `wxa-runner-${(0, import_crypto.randomUUID)()}`;
1487
1565
  const socket = new import_ws.WebSocket(`ws://127.0.0.1:${port}`);
1488
1566
  const settle = (finish) => {
1489
1567
  clearTimeout(timer);
@@ -1523,6 +1601,156 @@ function explainAutomatorError(message) {
1523
1601
  return new Error(`\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u8FD4\u56DE\u9519\u8BEF\uFF1A${message}`);
1524
1602
  }
1525
1603
 
1604
+ // src/server/plugins.ts
1605
+ var import_wxa_runner_extractor3 = require("@open-wxa/wxa-runner-extractor");
1606
+
1607
+ // src/server/wechatDev.ts
1608
+ var import_wxa_runner_extractor2 = require("@open-wxa/wxa-runner-extractor");
1609
+ var BASE_URL = "https://servicewechat.com";
1610
+ var CLIENT_VERSION = "1021810240";
1611
+ var DEFAULT_TIMEOUT = 1e4;
1612
+ var ERROR_HINTS = {
1613
+ // 40001:开发者工具刷新过 ticket,旧的随即作废
1614
+ 40001: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
1615
+ 40029: "\uFF08\u767B\u5F55\u6001\u5DF2\u5931\u6548\uFF0C\u8BF7\u5728\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u91CC\u91CD\u65B0\u767B\u5F55\uFF09",
1616
+ 85023: "\uFF08\u5F53\u524D\u5FAE\u4FE1\u8D26\u53F7\u4E0D\u662F\u8BE5\u5C0F\u7A0B\u5E8F\u7684\u5F00\u53D1\u8005\uFF09"
1617
+ };
1618
+ function requireUsableTicket(ticket) {
1619
+ if (!ticket) throw new Error(`\u672A\u627E\u5230\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\uFF0C\u8BF7\u767B\u5F55\u5F00\u53D1\u8005\u5DE5\u5177\uFF0C\u6216\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF ${import_wxa_runner_extractor2.TICKET_ENV}`);
1620
+ if (ticket.expiredAt !== void 0 && ticket.expiredAt <= Date.now()) {
1621
+ throw new Error("\u5FAE\u4FE1\u5F00\u53D1\u8005\u5DE5\u5177\u7684\u767B\u5F55\u6001\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u6253\u5F00\u5F00\u53D1\u8005\u5DE5\u5177\u5237\u65B0\u767B\u5F55\u6001");
1622
+ }
1623
+ return ticket.newticket;
1624
+ }
1625
+ function explainErrcode(errcode) {
1626
+ return errcode !== void 0 && ERROR_HINTS[errcode] || "";
1627
+ }
1628
+ function requireOk(body, action) {
1629
+ const errcode = body.baseresponse?.errcode;
1630
+ if (errcode !== 0) throw new Error(`${action} ${errcode}: ${body.baseresponse?.errmsg || "\u672A\u77E5\u9519\u8BEF"}${explainErrcode(errcode)}`);
1631
+ return body;
1632
+ }
1633
+ var WechatDevClient = class {
1634
+ /**
1635
+ * @param findTicket 每次请求都重新取:开发者工具重新登录、切换账号后不用重启服务器
1636
+ */
1637
+ constructor(appid, findTicket) {
1638
+ this.appid = appid;
1639
+ this.findTicket = findTicket;
1640
+ }
1641
+ /**
1642
+ * POST JSON 到 path(如 wxa-dev-logic/jslogin),带上 appid 和开发者 ticket
1643
+ *
1644
+ * @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
1645
+ */
1646
+ async post(path7, body, timeout = DEFAULT_TIMEOUT) {
1647
+ const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
1648
+ const response = await fetch(`${BASE_URL}/${path7}?${query}`, {
1649
+ method: "POST",
1650
+ headers: { "Content-Type": "application/json" },
1651
+ body: JSON.stringify(body),
1652
+ signal: AbortSignal.timeout(timeout)
1653
+ }).catch((error) => {
1654
+ throw new Error(error.name === "TimeoutError" ? `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u8D85\u65F6\uFF08${timeout / 1e3}s\uFF09` : `\u8BF7\u6C42\u5FAE\u4FE1\u670D\u52A1\u5668\u5931\u8D25\uFF1A${error.cause?.message ?? error.message}`);
1655
+ });
1656
+ if (!response.ok) throw new Error(`\u5FAE\u4FE1\u670D\u52A1\u5668\u8FD4\u56DE HTTP ${response.status}`);
1657
+ return response;
1658
+ }
1659
+ /** 同 post,返回解析后的 JSON */
1660
+ async postJson(path7, body) {
1661
+ return (await this.post(path7, body)).json();
1662
+ }
1663
+ };
1664
+
1665
+ // src/server/plugins.ts
1666
+ var DOWNLOAD_TIMEOUT = 3e4;
1667
+ var PACKAGE_MAGIC = 190;
1668
+ var PluginStore = class {
1669
+ /**
1670
+ * @param wechat 开启 --devtools-login 时才有,没有时不下载插件
1671
+ */
1672
+ constructor(wechat, logger) {
1673
+ this.wechat = wechat;
1674
+ this.logger = logger;
1675
+ /** provider@version → 下载结果;失败的不留,下次打开小程序时重试 */
1676
+ this.downloads = /* @__PURE__ */ new Map();
1677
+ /**
1678
+ * 这次打开小程序时决定用哪些插件。同一次打开的逻辑层、业务模块、渲染层等请求都按它处理:
1679
+ * 各自去下载的话,下载失败时每个请求都会重试,结果还可能前后不一,页面列表和逻辑层对不上
1680
+ */
1681
+ this.selection = null;
1682
+ /** 没开 --devtools-login 的提示每个插件只说一次 */
1683
+ this.warned = /* @__PURE__ */ new Set();
1684
+ }
1685
+ /** 打开小程序时(宿主取 app-info)调用:重新决定用哪些插件,下载过的直接用,上次失败的重试 */
1686
+ async refresh(build2) {
1687
+ this.selection = this.select(build2);
1688
+ return withPlugins(build2, await this.selection);
1689
+ }
1690
+ /** 其余请求沿用这次打开时的决定,不触发下载;还没决定过时(如直接请求逻辑层)先决定一次 */
1691
+ async current(build2) {
1692
+ this.selection ??= this.select(build2);
1693
+ return withPlugins(build2, await this.selection);
1694
+ }
1695
+ /** app.json 声明的插件;下载不了的跳过,在终端说明原因,小程序自己的页面照常可用 */
1696
+ async select(build2) {
1697
+ const declared = build2.wxConfig.plugins || {};
1698
+ const results = await Promise.all(Object.entries(declared).map(
1699
+ ([alias, { provider, version }]) => provider && version ? this.load(alias, provider, version) : null
1700
+ ));
1701
+ return results.filter((plugin) => !!plugin);
1702
+ }
1703
+ async load(alias, provider, version) {
1704
+ const label = `\u63D2\u4EF6 ${alias}\uFF08${provider}@${version}\uFF09`;
1705
+ if (!this.wechat) {
1706
+ this.warnOnce(label, `[wxa-runner] ${label} \u7684\u4EE3\u7801\u8981\u7528\u5F00\u53D1\u8005\u7684\u767B\u5F55\u6001\u5411\u5FAE\u4FE1\u4E0B\u8F7D\uFF0C\u542F\u52A8\u65F6\u52A0 --devtools-login`);
1707
+ return null;
1708
+ }
1709
+ const key = `${provider}@${version}`;
1710
+ let download = this.downloads.get(key);
1711
+ if (!download) {
1712
+ download = this.download(this.wechat, alias, provider, version);
1713
+ this.downloads.set(key, download);
1714
+ download.catch(() => this.downloads.delete(key));
1715
+ }
1716
+ try {
1717
+ return await download;
1718
+ } catch (error) {
1719
+ this.logger.warn(`[wxa-runner] ${label} \u4E0B\u8F7D\u5931\u8D25\uFF1A${error.message}`);
1720
+ return null;
1721
+ }
1722
+ }
1723
+ /** @throws 没有登录态、连不上、微信拒绝或返回的不是插件代码包时,消息是原因 */
1724
+ async download(wechat, alias, provider, version) {
1725
+ const response = await wechat.post("wxa-dev/getplugincode", { plugin_id: provider, plugin_version: version }, DOWNLOAD_TIMEOUT);
1726
+ const data = Buffer.from(await response.arrayBuffer());
1727
+ if (data[0] !== PACKAGE_MAGIC) {
1728
+ let body = {};
1729
+ try {
1730
+ body = JSON.parse(data.toString("utf8"));
1731
+ } catch {
1732
+ }
1733
+ if (body.baseresponse?.errcode) requireOk(body, "\u5FAE\u4FE1\u62D2\u7EDD\u4E0B\u8F7D");
1734
+ throw new Error("\u5FAE\u4FE1\u6CA1\u6709\u8FD4\u56DE\u63D2\u4EF6\u4EE3\u7801");
1735
+ }
1736
+ const files = new Map((0, import_wxa_runner_extractor3.readWxvpkgIndex)(data).map((entry) => [entry.name, Buffer.from((0, import_wxa_runner_extractor3.readWxvpkgFile)(data, entry))]));
1737
+ const pluginJson = JSON.parse(files.get("/plugin.json")?.toString("utf8") || "{}");
1738
+ return {
1739
+ alias,
1740
+ provider,
1741
+ version,
1742
+ files,
1743
+ pages: Object.values(pluginJson.pages || {}),
1744
+ pageConfigs: pluginJson.page || {}
1745
+ };
1746
+ }
1747
+ warnOnce(key, message) {
1748
+ if (this.warned.has(key)) return;
1749
+ this.warned.add(key);
1750
+ this.logger.warn(message);
1751
+ }
1752
+ };
1753
+
1526
1754
  // src/server/devtoolsLogin.ts
1527
1755
  var import_express = __toESM(require("express"));
1528
1756
  var import_wxa_runner_extractor4 = require("@open-wxa/wxa-runner-extractor");
@@ -1589,8 +1817,13 @@ function respond(action) {
1589
1817
  var import_fs4 = __toESM(require("fs"));
1590
1818
  var import_path4 = __toESM(require("path"));
1591
1819
  var import_esbuild2 = require("esbuild");
1592
- var SETUP_FILE_NAMES = ["wxa.setup.ts", "wxa.setup.js", "wxa.setup.mjs"];
1820
+
1821
+ // src/runtime/setupModule.ts
1593
1822
  var SETUP_MODULE = "@open-wxa/wxa-runner-core/setup";
1823
+ var SETUP_MODULE_SOURCE = "export function defineSetup(setup) { return setup; }";
1824
+
1825
+ // src/server/setup.ts
1826
+ var SETUP_FILE_NAMES = ["wxa.setup.ts", "wxa.setup.js", "wxa.setup.mjs"];
1594
1827
  var ENV_PREFIX = "WXA_";
1595
1828
  function findSetupFile(projectPath) {
1596
1829
  for (const name of SETUP_FILE_NAMES) {
@@ -1625,8 +1858,7 @@ function builtinSetupModule() {
1625
1858
  setup(pluginBuild) {
1626
1859
  pluginBuild.onResolve({ filter: new RegExp(`^${SETUP_MODULE}$`) }, () => ({ path: SETUP_MODULE, namespace: "wxa-setup" }));
1627
1860
  pluginBuild.onLoad({ filter: /.*/, namespace: "wxa-setup" }, () => ({
1628
- // 与 src/setup.ts 的 defineSetup 一致:只为类型提示,运行时原样返回
1629
- contents: "export function defineSetup(setup) { return setup; }",
1861
+ contents: SETUP_MODULE_SOURCE,
1630
1862
  loader: "js"
1631
1863
  }));
1632
1864
  }
@@ -1634,7 +1866,7 @@ function builtinSetupModule() {
1634
1866
  }
1635
1867
 
1636
1868
  // src/server/watcher.ts
1637
- var import_crypto3 = require("crypto");
1869
+ var import_crypto2 = require("crypto");
1638
1870
  var import_fs5 = __toESM(require("fs"));
1639
1871
  var import_path5 = __toESM(require("path"));
1640
1872
  var import_chokidar = __toESM(require("chokidar"));
@@ -1678,7 +1910,7 @@ function updateFingerprint(fingerprints, file, event) {
1678
1910
  if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
1679
1911
  let next;
1680
1912
  try {
1681
- next = (0, import_crypto3.createHash)("sha1").update(import_fs5.default.readFileSync(file)).digest("hex");
1913
+ next = (0, import_crypto2.createHash)("sha1").update(import_fs5.default.readFileSync(file)).digest("hex");
1682
1914
  } catch {
1683
1915
  return false;
1684
1916
  }
@@ -1711,16 +1943,7 @@ function applyExtensions(base, extensions, context) {
1711
1943
 
1712
1944
  // src/server/DevServer.ts
1713
1945
  var RESERVED_PREFIXES = ["/__wxa__", "/__baselib__", "/__plugin__"];
1714
- var DEFAULT_DEVICE = {
1715
- model: "iPhone X",
1716
- screenWidth: 375,
1717
- screenHeight: 812,
1718
- pixelRatio: 3,
1719
- statusBarHeight: 44,
1720
- safeAreaBottom: 34,
1721
- system: "iOS 16.0",
1722
- platform: "devtools"
1723
- };
1946
+ var APP_ROUTES = /^\/(?:__wxa__\/(?:appservice\.html|subcontext\.html|worker\.js|service-|page-|webview\/.+\.html$|plugin\/|module\/)|__plugin__\/)/;
1724
1947
  var DevServer = class {
1725
1948
  constructor(options) {
1726
1949
  this.options = options;
@@ -1856,63 +2079,11 @@ ${error.message}`);
1856
2079
  res.status(500).type("text").send(error.message);
1857
2080
  }
1858
2081
  });
1859
- app.get("/__wxa__/appservice.html", this.withBuild((build2, req, res) => {
1860
- const launchPath = String(req.query.path || "").replace(/^\//, "");
1861
- const launch = build2.pages.includes(launchPath) ? { path: launchPath, query: Object.fromEntries(new URLSearchParams(String(req.query.query || ""))) } : { path: build2.pages[0], query: {} };
1862
- res.type("html").send(appserviceHtml(build2, launch, { hot: this.hotEnabled, isolated: (0, import_wxa_runner_extractor5.usesIsolatedContext)(this.baseLib.version) }));
1863
- }));
1864
- app.get("/__wxa__/subcontext.html", (req, res) => {
1865
- const id = Number(req.query.id);
1866
- if (!Number.isInteger(id) || id <= 0) {
1867
- res.status(400).type("text").send("\u5B50\u4E0A\u4E0B\u6587 id \u65E0\u6548");
1868
- return;
1869
- }
1870
- res.type("html").send(subcontextHtml(id, req.query.empty === "1"));
1871
- });
1872
- app.get(/^\/__wxa__\/webview\/(.+)\.html$/, this.withBuild((build2, req, res, next) => {
1873
- const pagePath = decodeURIComponent(req.params[0]);
1874
- if (!build2.pages.includes(pagePath)) return next();
1875
- const pluginPage = findPluginPage(build2, pagePath);
1876
- const scripts = this.webviewScripts;
1877
- res.type("html").send(pluginPage ? pluginWebviewHtml(build2, pluginPage.plugin, pluginPage.innerPath, pagePath, Number(req.query.id), { scripts }) : webviewHtml(build2, pagePath, Number(req.query.id), { hot: this.hotEnabled, scripts }));
1878
- }));
1879
- app.get("/__wxa__/plugin/:provider/:version/:file(appservice.js|pageframe.js)", this.withBuild((build2, req, res, next) => {
1880
- const { provider, version, file } = req.params;
1881
- const code = build2.plugins.find((plugin) => plugin.provider === provider && plugin.version === version)?.files.get(`/${file}`);
1882
- if (!code) return next();
1883
- res.type("js").send(code);
1884
- }));
1885
- app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
1886
- const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
1887
- if (!file) return next();
1888
- res.type(import_path6.default.extname(req.params[1]) || "application/octet-stream").send(file);
1889
- }));
1890
- app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
1891
- const id = decodeURIComponent(req.params[0]);
1892
- if (!build2.modules.includes(id) && !build2.workerModules.includes(id)) {
1893
- res.status(404).type("js").send(`console.error(${JSON.stringify(`[wxa-runner] \u6A21\u5757 ${id} \u4E0D\u5B58\u5728`)})`);
1894
- return;
1895
- }
1896
- res.type("js").send(this.builder.getModuleScript(id));
1897
- }));
1898
- app.get("/__wxa__/worker.js", this.withBuild((build2, _req, res) => {
1899
- const hasWorker = import_fs6.default.existsSync(import_path6.default.join(this.baseLib.dir, "WAWorker.js"));
1900
- res.type("js").send(workerScript(build2, { version: this.baseLib.version, hasWorker }));
1901
- }));
1902
- app.get("/__wxa__/service-bootstrap.js", this.withBuild((build2, _req, res) => {
1903
- res.type("js").send(build2.serviceBootstrap);
1904
- }));
1905
- app.get("/__wxa__/service-templates.js", this.withBuild((build2, _req, res) => {
1906
- res.type("js").send(build2.serviceTemplates);
1907
- }));
1908
- app.get("/__wxa__/page-templates.js", this.withBuild((build2, _req, res) => {
1909
- res.type("js").send(build2.pageTemplates);
1910
- }));
1911
- app.get("/__wxa__/page-styles.js", this.withBuild((build2, _req, res) => {
1912
- res.type("js").send(build2.pageStyles);
1913
- }));
1914
- app.get("/__wxa__/page-frame.js", this.withBuild((build2, _req, res) => {
1915
- res.type("js").send(build2.pageFrame);
2082
+ app.get(APP_ROUTES, this.withBuild((build2, req, res, next) => {
2083
+ const response = routeApp(this.appSource(build2), new URL(req.originalUrl, "http://localhost"));
2084
+ if (!response) return next();
2085
+ res.status(response.status).type(response.type);
2086
+ res.send(typeof response.body === "string" ? response.body : Buffer.from(response.body.buffer, response.body.byteOffset, response.body.byteLength));
1916
2087
  }));
1917
2088
  app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
1918
2089
  const file = import_path6.default.join(this.runtimeDir(), "frame.js.map");
@@ -1975,7 +2146,7 @@ ${error.message}`);
1975
2146
  res.status(400).type("text").send(`\u975E\u6CD5\u7684\u4F1A\u8BDD\u6807\u8BC6 ${req.params.session}`);
1976
2147
  return;
1977
2148
  }
1978
- const filePath = `/__wxa__/tmp/${req.params.session}/${(0, import_crypto4.randomUUID)()}.${type}`;
2149
+ const filePath = `/__wxa__/tmp/${req.params.session}/${(0, import_crypto3.randomUUID)()}.${type}`;
1979
2150
  files.write(filePath, Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0));
1980
2151
  res.json({ url: `${req.protocol}://${req.get("host")}${filePath}` });
1981
2152
  });
@@ -1995,7 +2166,7 @@ ${error.message}`);
1995
2166
  res.status(404).end();
1996
2167
  return;
1997
2168
  }
1998
- const data = node.data;
2169
+ const data = Buffer.from(node.data.buffer, node.data.byteOffset, node.data.byteLength);
1999
2170
  res.type(import_path6.default.extname(req.params[0]));
2000
2171
  res.setHeader("ETag", `"${node.etag}"`);
2001
2172
  res.setHeader("Accept-Ranges", "bytes");
@@ -2046,6 +2217,16 @@ ${error.message}`);
2046
2217
  return this.plugins.current(await this.builder.build());
2047
2218
  }
2048
2219
  /** 需要构建结果的路由;构建失败时返回错误文本 */
2220
+ appSource(build2) {
2221
+ return {
2222
+ build: build2,
2223
+ moduleScript: (id) => this.builder.getModuleScript(id),
2224
+ baseLib: { version: this.baseLib.version, hasWorker: import_fs6.default.existsSync(import_path6.default.join(this.baseLib.dir, "WAWorker.js")) },
2225
+ isolated: (0, import_wxa_runner_extractor5.usesIsolatedContext)(this.baseLib.version),
2226
+ hot: this.hotEnabled,
2227
+ webviewScripts: this.webviewScripts
2228
+ };
2229
+ }
2049
2230
  withBuild(handler) {
2050
2231
  return async (req, res, next) => {
2051
2232
  res.setHeader("Cache-Control", "no-store");
@@ -2064,17 +2245,7 @@ ${error.message}`);
2064
2245
  };
2065
2246
  }
2066
2247
  runtimeDir() {
2067
- const candidates = [
2068
- this.options.runtimeDir,
2069
- import_path6.default.join(__dirname, "runtime"),
2070
- import_path6.default.join(__dirname, "../runtime"),
2071
- import_path6.default.join(__dirname, "../../dist/runtime")
2072
- ].filter((dir) => !!dir);
2073
- const found = candidates.find((dir) => import_fs6.default.existsSync(import_path6.default.join(dir, "host.js")));
2074
- if (!found) {
2075
- throw new Error("\u627E\u4E0D\u5230\u6D4F\u89C8\u5668\u7AEF\u8FD0\u884C\u65F6\uFF08host.js\uFF09\uFF0C\u8BF7\u5148\u6267\u884C pnpm build");
2076
- }
2077
- return found;
2248
+ return findRuntimeDir(this.options.runtimeDir);
2078
2249
  }
2079
2250
  /** 只在监听文件变化时打开基础库的热更新开关,跑测试(watch: false)时运行环境与真机一致 */
2080
2251
  get hotEnabled() {
@@ -2162,12 +2333,31 @@ function resolveSetupFile(option, projectPath) {
2162
2333
  if (!import_fs6.default.existsSync(file)) throw new Error(`\u627E\u4E0D\u5230 setup \u6587\u4EF6\uFF1A${file}`);
2163
2334
  return file;
2164
2335
  }
2336
+ function findRuntimeDir(explicit) {
2337
+ const candidates = [
2338
+ explicit,
2339
+ import_path6.default.join(__dirname, "runtime"),
2340
+ import_path6.default.join(__dirname, "../runtime"),
2341
+ import_path6.default.join(__dirname, "../../dist/runtime")
2342
+ ].filter((dir) => !!dir);
2343
+ const found = candidates.find((dir) => import_fs6.default.existsSync(import_path6.default.join(dir, "host.js")));
2344
+ if (!found) {
2345
+ throw new Error("\u627E\u4E0D\u5230\u6D4F\u89C8\u5668\u7AEF\u8FD0\u884C\u65F6\uFF08host.js\uFF09\uFF0C\u8BF7\u5148\u6267\u884C pnpm build");
2346
+ }
2347
+ return found;
2348
+ }
2165
2349
  // Annotate the CommonJS export names for ESM import in node:
2166
2350
  0 && (module.exports = {
2167
2351
  AppBuilder,
2168
2352
  DEFAULT_DEVICE,
2169
2353
  DevServer,
2354
+ LOCAL_HOST_OF_ANY_ADDRESS,
2170
2355
  MiniProgram,
2171
- createWxConfig
2356
+ bundleSetup,
2357
+ createWxConfig,
2358
+ findRuntimeDir,
2359
+ findSetupFile,
2360
+ handleProxy,
2361
+ listen
2172
2362
  });
2173
2363
  //# sourceMappingURL=index.js.map