@open-wxa/wxa-runner-core 0.10.1 → 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.d.mts +46 -6
- package/dist/index.d.ts +46 -6
- package/dist/index.js +487 -297
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +476 -292
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/host.js +5 -6
- package/dist/runtime/host.js.map +1 -1
- package/dist/runtime/sw.js +24 -20
- package/dist/runtime/sw.js.map +1 -1
- package/dist/{setup-Bac8XdhR.d.mts → setup-DhDDVlit.d.mts} +4 -0
- package/dist/{setup-Bac8XdhR.d.ts → setup-DhDDVlit.d.ts} +4 -0
- package/dist/setup.d.mts +1 -1
- package/dist/setup.d.ts +1 -1
- package/dist/test-api.d.mts +2 -2
- package/dist/test-api.d.ts +2 -2
- package/dist/{testApi-DHuEW5Cl.d.mts → testApi-BbK_1vhj.d.mts} +1 -1
- package/dist/{testApi-BOZSXSnV.d.ts → testApi-DbTnccYV.d.ts} +1 -1
- package/dist/web.d.mts +268 -0
- package/dist/web.d.ts +268 -0
- package/dist/web.js +700 -0
- package/dist/web.js.map +1 -0
- package/dist/web.mjs +677 -0
- package/dist/web.mjs.map +1 -0
- package/package.json +11 -3
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
|
-
|
|
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
|
|
48
|
+
var import_crypto3 = require("crypto");
|
|
43
49
|
|
|
44
50
|
// src/server/FileStore.ts
|
|
45
|
-
var
|
|
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 = (
|
|
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:
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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/
|
|
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
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[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
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
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
|
-
|
|
1313
|
-
upstream = await
|
|
1314
|
-
method
|
|
1315
|
-
headers
|
|
1316
|
-
body
|
|
1317
|
-
redirect: "follow",
|
|
1318
|
-
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
|
-
|
|
1321
|
-
if (
|
|
1322
|
-
|
|
1323
|
-
|
|
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
|
-
|
|
1371
|
+
return relayResponse(upstream);
|
|
1326
1372
|
} catch (error) {
|
|
1327
|
-
|
|
1328
|
-
} finally {
|
|
1329
|
-
res.off("close", cancel);
|
|
1373
|
+
return proxyError(502, fetchErrorMessage(error));
|
|
1330
1374
|
}
|
|
1331
1375
|
}
|
|
1332
|
-
function
|
|
1333
|
-
|
|
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
|
|
1337
|
-
return
|
|
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
|
|
1340
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
1410
|
+
if (!DROPPED_RESPONSE_HEADERS.has(key)) headers.set(key, value);
|
|
1365
1411
|
});
|
|
1366
|
-
|
|
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
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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
|
|
1472
|
+
var import_crypto = require("crypto");
|
|
1395
1473
|
var import_promises2 = require("timers/promises");
|
|
1396
1474
|
var import_ws = require("ws");
|
|
1397
|
-
var
|
|
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,
|
|
1405
|
-
if (!cli) return async () => (0,
|
|
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[
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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(
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
res.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
2356
|
+
bundleSetup,
|
|
2357
|
+
createWxConfig,
|
|
2358
|
+
findRuntimeDir,
|
|
2359
|
+
findSetupFile,
|
|
2360
|
+
handleProxy,
|
|
2361
|
+
listen
|
|
2172
2362
|
});
|
|
2173
2363
|
//# sourceMappingURL=index.js.map
|