@open-wxa/wxa-runner-core 0.8.1 → 0.9.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 +86 -2
- package/dist/index.d.ts +86 -2
- package/dist/index.js +174 -73
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +174 -73
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -128,11 +128,11 @@ var FileStore = class {
|
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
// src/server/DevServer.ts
|
|
131
|
-
import
|
|
131
|
+
import fs5 from "fs";
|
|
132
132
|
import http from "http";
|
|
133
133
|
import net from "net";
|
|
134
|
-
import
|
|
135
|
-
import
|
|
134
|
+
import os2 from "os";
|
|
135
|
+
import path5 from "path";
|
|
136
136
|
import express2 from "express";
|
|
137
137
|
import { WebSocket as WebSocket2, WebSocketServer } from "ws";
|
|
138
138
|
import { readRegionData, resolveBaseLib, usesIsolatedContext } from "@open-wxa/wxa-runner-extractor";
|
|
@@ -197,6 +197,50 @@ function createWxConfig(input) {
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
// src/project/WxmlMirror.ts
|
|
201
|
+
import fs from "fs";
|
|
202
|
+
import os from "os";
|
|
203
|
+
import path from "path";
|
|
204
|
+
var WxmlMirror = class {
|
|
205
|
+
constructor(transform) {
|
|
206
|
+
this.transform = transform;
|
|
207
|
+
this.dir = null;
|
|
208
|
+
/** 已写入的文件:源内容(判断要不要重新转换)与写入的内容 */
|
|
209
|
+
this.files = /* @__PURE__ */ new Map();
|
|
210
|
+
}
|
|
211
|
+
/** 镜像目录;还没同步过时为 null */
|
|
212
|
+
get directory() {
|
|
213
|
+
return this.dir;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 让镜像目录与项目里的 wxml / wxs 一致,返回镜像目录。
|
|
217
|
+
* 只重写源内容变化的文件;项目里已删除的文件同时从镜像目录删除,被引用时 wcc 照常报错
|
|
218
|
+
*/
|
|
219
|
+
sync(project, relPaths) {
|
|
220
|
+
const dir = this.dir ??= fs.mkdtempSync(path.join(os.tmpdir(), "wxa-runner-wxml-"));
|
|
221
|
+
const current = new Set(relPaths);
|
|
222
|
+
for (const rel of [...this.files.keys()]) {
|
|
223
|
+
if (current.has(rel)) continue;
|
|
224
|
+
fs.rmSync(path.join(dir, rel), { force: true });
|
|
225
|
+
this.files.delete(rel);
|
|
226
|
+
}
|
|
227
|
+
for (const rel of relPaths) {
|
|
228
|
+
const source = project.readText(rel);
|
|
229
|
+
if (this.files.get(rel) === source) continue;
|
|
230
|
+
const target = path.join(dir, rel);
|
|
231
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
232
|
+
fs.writeFileSync(target, rel.endsWith(".wxml") ? this.transform(source, rel) : source);
|
|
233
|
+
this.files.set(rel, source);
|
|
234
|
+
}
|
|
235
|
+
return dir;
|
|
236
|
+
}
|
|
237
|
+
dispose() {
|
|
238
|
+
if (this.dir) fs.rmSync(this.dir, { recursive: true, force: true });
|
|
239
|
+
this.dir = null;
|
|
240
|
+
this.files.clear();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
200
244
|
// src/project/AppBuilder.ts
|
|
201
245
|
var MODULE_PARAMS = [
|
|
202
246
|
"require",
|
|
@@ -223,7 +267,7 @@ var MODULE_PARAMS = [
|
|
|
223
267
|
].join(", ");
|
|
224
268
|
var ESM_SYNTAX = /(?:^|[;}])\s*(?:import\s*[{*'"]|import\s+[\w$]|export\s*[{*]|export\s+(?:default|const|let|var|function|class|async)\b)/m;
|
|
225
269
|
var AppBuilder = class {
|
|
226
|
-
constructor(project) {
|
|
270
|
+
constructor(project, options = {}) {
|
|
227
271
|
this.project = project;
|
|
228
272
|
this.version = 0;
|
|
229
273
|
/** invalidate 标记过的变化次数;每次构建记下开始时覆盖到第几次 */
|
|
@@ -233,6 +277,15 @@ var AppBuilder = class {
|
|
|
233
277
|
this.dirty = /* @__PURE__ */ new Set(["config", "wxml", "wxss", "js"]);
|
|
234
278
|
this.wxmlCache = null;
|
|
235
279
|
this.wxssCache = null;
|
|
280
|
+
this.wxmlMirror = options.wxmlTransform ? new WxmlMirror(options.wxmlTransform) : null;
|
|
281
|
+
}
|
|
282
|
+
/** wxmlTransform 的镜像目录;没有设置转换或还没编译过时为 null */
|
|
283
|
+
get wxmlMirrorDir() {
|
|
284
|
+
return this.wxmlMirror?.directory ?? null;
|
|
285
|
+
}
|
|
286
|
+
/** 删除 wxmlTransform 的镜像目录,开发服务器关闭时调用 */
|
|
287
|
+
dispose() {
|
|
288
|
+
this.wxmlMirror?.dispose();
|
|
236
289
|
}
|
|
237
290
|
/** 根据变化的文件标记需要重新构建的部分;返回 true 表示需要刷新 */
|
|
238
291
|
invalidate(changedFiles = []) {
|
|
@@ -357,7 +410,7 @@ ${pageStyles}`,
|
|
|
357
410
|
const json = unitJson.get(id);
|
|
358
411
|
return { path: id, tags: Object.keys({ ...json.usingComponents, ...json.componentGenerics }) };
|
|
359
412
|
});
|
|
360
|
-
const cwd = this.project.root;
|
|
413
|
+
const cwd = this.wxmlMirror ? this.wxmlMirror.sync(this.project, files.map((file) => file.slice(2))) : this.project.root;
|
|
361
414
|
const [full, cut] = await Promise.all([
|
|
362
415
|
compileWxml({ cwd, files, components, cut: false }),
|
|
363
416
|
compileWxml({ cwd, files, components, cut: true })
|
|
@@ -424,22 +477,22 @@ function classifyFile(file) {
|
|
|
424
477
|
}
|
|
425
478
|
|
|
426
479
|
// src/project/MiniProgram.ts
|
|
427
|
-
import
|
|
428
|
-
import
|
|
480
|
+
import fs2 from "fs";
|
|
481
|
+
import path2 from "path";
|
|
429
482
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".idea", ".vscode"]);
|
|
430
483
|
var PROJECT_FILES = /* @__PURE__ */ new Set(["project.config.json", "project.private.config.json", "sitemap.json", "app.json", "wxa.setup.js"]);
|
|
431
484
|
var MiniProgram = class {
|
|
432
485
|
constructor(projectPath) {
|
|
433
486
|
this.projectPath = projectPath;
|
|
434
|
-
const projectConfig = readJsonFile(
|
|
435
|
-
this.root =
|
|
487
|
+
const projectConfig = readJsonFile(path2.join(projectPath, "project.config.json")) || {};
|
|
488
|
+
this.root = path2.resolve(projectPath, projectConfig.miniprogramRoot || "");
|
|
436
489
|
this.appid = projectConfig.appid || "touristappid";
|
|
437
|
-
if (!
|
|
490
|
+
if (!fs2.existsSync(path2.join(this.root, "app.json"))) {
|
|
438
491
|
throw new Error(`${this.root} \u4E0B\u6CA1\u6709 app.json\uFF0C\u4E0D\u662F\u6709\u6548\u7684\u5C0F\u7A0B\u5E8F\u76EE\u5F55`);
|
|
439
492
|
}
|
|
440
493
|
}
|
|
441
494
|
readAppJson() {
|
|
442
|
-
const app = readJsonFile(
|
|
495
|
+
const app = readJsonFile(path2.join(this.root, "app.json"));
|
|
443
496
|
if (!app || !Array.isArray(app.pages) || app.pages.length === 0) {
|
|
444
497
|
throw new Error("app.json \u7F3A\u5C11 pages \u914D\u7F6E");
|
|
445
498
|
}
|
|
@@ -451,14 +504,14 @@ var MiniProgram = class {
|
|
|
451
504
|
/** Worker 代码所在目录,不带首尾的 /;没有配置 workers 时为 null */
|
|
452
505
|
workersDir(app) {
|
|
453
506
|
const dir = typeof app.workers === "string" ? app.workers : app.workers?.path;
|
|
454
|
-
return dir ?
|
|
507
|
+
return dir ? path2.posix.normalize(dir).replace(/^\/+|\/+$/g, "") : null;
|
|
455
508
|
}
|
|
456
509
|
/** 主包页面 + 分包页面(带 root 前缀) */
|
|
457
510
|
allPages(app) {
|
|
458
511
|
const pages = [...app.pages];
|
|
459
512
|
for (const pkg of this.subPackages(app)) {
|
|
460
513
|
for (const page of pkg.pages) {
|
|
461
|
-
const full =
|
|
514
|
+
const full = path2.posix.join(pkg.root, page);
|
|
462
515
|
if (!pages.includes(full)) pages.push(full);
|
|
463
516
|
}
|
|
464
517
|
}
|
|
@@ -470,7 +523,7 @@ var MiniProgram = class {
|
|
|
470
523
|
* 所以能解析到的引用都换成组件的绝对路径
|
|
471
524
|
*/
|
|
472
525
|
readUnitJson(id, app) {
|
|
473
|
-
const json = { ...readJsonFile(
|
|
526
|
+
const json = { ...readJsonFile(path2.join(this.root, id + ".json")) };
|
|
474
527
|
const resolve = (fromId, ref) => {
|
|
475
528
|
const resolved = this.resolveComponent(fromId, ref);
|
|
476
529
|
return resolved ? "/" + resolved : null;
|
|
@@ -491,7 +544,7 @@ var MiniProgram = class {
|
|
|
491
544
|
if (resolved || ref.startsWith("/") || /^plugin(-private)?:\/\//.test(ref)) {
|
|
492
545
|
json.usingComponents[tag] = resolved ?? ref;
|
|
493
546
|
} else {
|
|
494
|
-
const relative =
|
|
547
|
+
const relative = path2.posix.relative(path2.posix.dirname(id), ref);
|
|
495
548
|
json.usingComponents[tag] = relative.startsWith(".") ? relative : "./" + relative;
|
|
496
549
|
}
|
|
497
550
|
}
|
|
@@ -504,25 +557,25 @@ var MiniProgram = class {
|
|
|
504
557
|
*/
|
|
505
558
|
resolveComponent(fromId, ref) {
|
|
506
559
|
if (!ref || ref.startsWith("plugin://") || ref.startsWith("plugin-private://")) return null;
|
|
507
|
-
const fromDir =
|
|
560
|
+
const fromDir = path2.posix.dirname(fromId);
|
|
508
561
|
const candidates = [];
|
|
509
562
|
if (ref.startsWith("/")) {
|
|
510
563
|
candidates.push(ref.slice(1));
|
|
511
564
|
} else {
|
|
512
|
-
candidates.push(
|
|
565
|
+
candidates.push(path2.posix.join(fromDir, ref));
|
|
513
566
|
if (!ref.startsWith(".")) {
|
|
514
567
|
let dir = fromDir;
|
|
515
568
|
for (; ; ) {
|
|
516
|
-
candidates.push(
|
|
569
|
+
candidates.push(path2.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
|
|
517
570
|
if (dir === "." || dir === "") break;
|
|
518
|
-
dir =
|
|
571
|
+
dir = path2.posix.dirname(dir);
|
|
519
572
|
}
|
|
520
573
|
}
|
|
521
574
|
}
|
|
522
575
|
for (const candidate of candidates) {
|
|
523
|
-
const normalized =
|
|
524
|
-
for (const id of [normalized,
|
|
525
|
-
if (
|
|
576
|
+
const normalized = path2.posix.normalize(candidate);
|
|
577
|
+
for (const id of [normalized, path2.posix.join(normalized, "index")]) {
|
|
578
|
+
if (fs2.existsSync(path2.join(this.root, id + ".json"))) return id;
|
|
526
579
|
}
|
|
527
580
|
}
|
|
528
581
|
return null;
|
|
@@ -554,11 +607,11 @@ var MiniProgram = class {
|
|
|
554
607
|
listFiles(extensions) {
|
|
555
608
|
const result = [];
|
|
556
609
|
const walk = (dir, rel) => {
|
|
557
|
-
for (const entry of
|
|
610
|
+
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
558
611
|
if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
|
|
559
612
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
560
613
|
if (entry.isDirectory()) {
|
|
561
|
-
walk(
|
|
614
|
+
walk(path2.join(dir, entry.name), childRel);
|
|
562
615
|
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
563
616
|
if (!rel && PROJECT_FILES.has(entry.name)) continue;
|
|
564
617
|
result.push(childRel);
|
|
@@ -569,18 +622,18 @@ var MiniProgram = class {
|
|
|
569
622
|
return result.sort();
|
|
570
623
|
}
|
|
571
624
|
exists(relPath) {
|
|
572
|
-
return
|
|
625
|
+
return fs2.existsSync(path2.join(this.root, relPath));
|
|
573
626
|
}
|
|
574
627
|
readText(relPath) {
|
|
575
|
-
return
|
|
628
|
+
return fs2.readFileSync(path2.join(this.root, relPath), "utf8");
|
|
576
629
|
}
|
|
577
630
|
isInIndependentSubPackage(id, app) {
|
|
578
631
|
return this.subPackages(app).some((pkg) => pkg.independent && id.startsWith(pkg.root.replace(/\/?$/, "/")));
|
|
579
632
|
}
|
|
580
633
|
};
|
|
581
634
|
function readJsonFile(file) {
|
|
582
|
-
if (!
|
|
583
|
-
const text =
|
|
635
|
+
if (!fs2.existsSync(file)) return null;
|
|
636
|
+
const text = fs2.readFileSync(file, "utf8");
|
|
584
637
|
try {
|
|
585
638
|
return JSON.parse(text);
|
|
586
639
|
} catch (error) {
|
|
@@ -646,9 +699,9 @@ var WechatDevClient = class {
|
|
|
646
699
|
*
|
|
647
700
|
* @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
|
|
648
701
|
*/
|
|
649
|
-
async post(
|
|
702
|
+
async post(path6, body, timeout = DEFAULT_TIMEOUT) {
|
|
650
703
|
const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
|
|
651
|
-
const response = await fetch(`${BASE_URL}/${
|
|
704
|
+
const response = await fetch(`${BASE_URL}/${path6}?${query}`, {
|
|
652
705
|
method: "POST",
|
|
653
706
|
headers: { "Content-Type": "application/json" },
|
|
654
707
|
body: JSON.stringify(body),
|
|
@@ -660,8 +713,8 @@ var WechatDevClient = class {
|
|
|
660
713
|
return response;
|
|
661
714
|
}
|
|
662
715
|
/** 同 post,返回解析后的 JSON */
|
|
663
|
-
async postJson(
|
|
664
|
-
return (await this.post(
|
|
716
|
+
async postJson(path6, body) {
|
|
717
|
+
return (await this.post(path6, body)).json();
|
|
665
718
|
}
|
|
666
719
|
};
|
|
667
720
|
|
|
@@ -857,12 +910,12 @@ function webviewHtml(build2, pagePath, webviewId, options) {
|
|
|
857
910
|
return `<!DOCTYPE html>
|
|
858
911
|
<html>
|
|
859
912
|
<head>
|
|
860
|
-
${webviewHead(build2, pagePath, id)}
|
|
913
|
+
${webviewHead(build2, pagePath, id, options.scripts)}
|
|
861
914
|
${enableHotUpdate(options.hot)}<script src="/__wxa__/page-frame.js?v=${build2.version}"></script>
|
|
862
915
|
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
863
916
|
</head>
|
|
864
917
|
<body>
|
|
865
|
-
<script>
|
|
918
|
+
${scriptTags(options.scripts, "body")}<script>
|
|
866
919
|
(function () {
|
|
867
920
|
var path = ${toJs(pagePath)};
|
|
868
921
|
if (typeof __wxAppCode__[path + '.wxss'] === 'function') __wxAppCode__[path + '.wxss']();
|
|
@@ -895,18 +948,18 @@ self.console = __wxaConsole;
|
|
|
895
948
|
importScripts(${modules.map((url) => toJs(url)).join(", ")});
|
|
896
949
|
`;
|
|
897
950
|
}
|
|
898
|
-
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId) {
|
|
951
|
+
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId, options = {}) {
|
|
899
952
|
const id = Number(webviewId) || 0;
|
|
900
953
|
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>`;
|
|
901
954
|
return `<!DOCTYPE html>
|
|
902
955
|
<html>
|
|
903
956
|
<head>
|
|
904
|
-
${webviewHead(build2, pagePath, id)}
|
|
957
|
+
${webviewHead(build2, pagePath, id, options.scripts)}
|
|
905
958
|
<script src="${pluginScriptUrl(plugin, "pageframe.js")}"></script>
|
|
906
959
|
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
907
960
|
</head>
|
|
908
961
|
<body>
|
|
909
|
-
${page}
|
|
962
|
+
${scriptTags(options.scripts, "body")}${page}
|
|
910
963
|
<script>parent.__wxaHost.frameLoaded('webview', ${id});</script>
|
|
911
964
|
</body>
|
|
912
965
|
</html>`;
|
|
@@ -914,7 +967,7 @@ ${page}
|
|
|
914
967
|
function pageWindow(build2, pagePath) {
|
|
915
968
|
return { ...build2.wxConfig.global?.window, ...build2.wxConfig.page?.[pagePath + ".html"]?.window };
|
|
916
969
|
}
|
|
917
|
-
function webviewHead(build2, pagePath, id) {
|
|
970
|
+
function webviewHead(build2, pagePath, id, scripts) {
|
|
918
971
|
const wxConfig = { ...build2.wxConfig, window: pageWindow(build2, pagePath) };
|
|
919
972
|
return `<meta charset="utf-8">
|
|
920
973
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
@@ -929,7 +982,12 @@ function webviewHead(build2, pagePath, id) {
|
|
|
929
982
|
<style>.wxa-service-canvas{position:absolute!important;left:0!important;top:0!important;width:100%!important;height:100%!important;visibility:inherit!important;pointer-events:none}</style>
|
|
930
983
|
<script>var __wxConfig = ${toJs(wxConfig)};</script>
|
|
931
984
|
<script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"></script>
|
|
932
|
-
<script src="/__baselib__/WAWebview.js"></script>`;
|
|
985
|
+
${scriptTags(scripts, "head")}<script src="/__baselib__/WAWebview.js"></script>`;
|
|
986
|
+
}
|
|
987
|
+
function scriptTags(scripts, position) {
|
|
988
|
+
return (scripts ?? []).filter((script) => (script.position ?? "body") === position).map((script) => script.src !== void 0 ? `<script src="${escapeHtml(script.src)}"></script>
|
|
989
|
+
` : `<script>${script.content.replace(/<\/script/gi, "<\\/script")}</script>
|
|
990
|
+
`).join("");
|
|
933
991
|
}
|
|
934
992
|
function escapeHtml(text) {
|
|
935
993
|
return text.replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
@@ -1293,16 +1351,16 @@ function respond(action) {
|
|
|
1293
1351
|
}
|
|
1294
1352
|
|
|
1295
1353
|
// src/server/setup.ts
|
|
1296
|
-
import
|
|
1297
|
-
import
|
|
1354
|
+
import fs3 from "fs";
|
|
1355
|
+
import path3 from "path";
|
|
1298
1356
|
import { build } from "esbuild";
|
|
1299
1357
|
var SETUP_FILE_NAMES = ["wxa.setup.ts", "wxa.setup.js", "wxa.setup.mjs"];
|
|
1300
1358
|
var SETUP_MODULE = "@open-wxa/wxa-runner-core/setup";
|
|
1301
1359
|
var ENV_PREFIX = "WXA_";
|
|
1302
1360
|
function findSetupFile(projectPath) {
|
|
1303
1361
|
for (const name of SETUP_FILE_NAMES) {
|
|
1304
|
-
const file =
|
|
1305
|
-
if (
|
|
1362
|
+
const file = path3.join(projectPath, name);
|
|
1363
|
+
if (fs3.existsSync(file)) return file;
|
|
1306
1364
|
}
|
|
1307
1365
|
return null;
|
|
1308
1366
|
}
|
|
@@ -1314,7 +1372,7 @@ function collectSetupEnv(env) {
|
|
|
1314
1372
|
async function bundleSetup(file) {
|
|
1315
1373
|
const result = await build({
|
|
1316
1374
|
entryPoints: [file],
|
|
1317
|
-
absWorkingDir:
|
|
1375
|
+
absWorkingDir: path3.dirname(file),
|
|
1318
1376
|
bundle: true,
|
|
1319
1377
|
format: "esm",
|
|
1320
1378
|
platform: "browser",
|
|
@@ -1342,8 +1400,8 @@ function builtinSetupModule() {
|
|
|
1342
1400
|
|
|
1343
1401
|
// src/server/watcher.ts
|
|
1344
1402
|
import { createHash } from "crypto";
|
|
1345
|
-
import
|
|
1346
|
-
import
|
|
1403
|
+
import fs4 from "fs";
|
|
1404
|
+
import path4 from "path";
|
|
1347
1405
|
import chokidar from "chokidar";
|
|
1348
1406
|
var DEBOUNCE = 120;
|
|
1349
1407
|
var IGNORED = /(^|[/\\])(node_modules|\.git)([/\\]|$)/;
|
|
@@ -1371,7 +1429,7 @@ function watchProject(root, paths, onChange) {
|
|
|
1371
1429
|
const unchanged = updateFingerprint(fingerprints, file, event);
|
|
1372
1430
|
if (!scanned || unchanged) return;
|
|
1373
1431
|
if (event !== "change") structural = true;
|
|
1374
|
-
changed.add(
|
|
1432
|
+
changed.add(path4.relative(root, file).split(path4.sep).join("/"));
|
|
1375
1433
|
if (timer) clearTimeout(timer);
|
|
1376
1434
|
timer = setTimeout(flush, DEBOUNCE);
|
|
1377
1435
|
});
|
|
@@ -1385,7 +1443,7 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1385
1443
|
if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
|
|
1386
1444
|
let next;
|
|
1387
1445
|
try {
|
|
1388
|
-
next = createHash("sha1").update(
|
|
1446
|
+
next = createHash("sha1").update(fs4.readFileSync(file)).digest("hex");
|
|
1389
1447
|
} catch {
|
|
1390
1448
|
return false;
|
|
1391
1449
|
}
|
|
@@ -1394,7 +1452,30 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1394
1452
|
return previous === next;
|
|
1395
1453
|
}
|
|
1396
1454
|
|
|
1455
|
+
// src/server/extensions.ts
|
|
1456
|
+
function applyExtensions(base, extensions, context) {
|
|
1457
|
+
const webviewScripts = [...base.webviewScripts ?? []];
|
|
1458
|
+
const middlewares = [...base.middlewares ?? []];
|
|
1459
|
+
const transforms = base.wxmlTransform ? [base.wxmlTransform] : [];
|
|
1460
|
+
const entries = [];
|
|
1461
|
+
for (const extension of extensions) {
|
|
1462
|
+
let setup;
|
|
1463
|
+
try {
|
|
1464
|
+
setup = extension.setup(context);
|
|
1465
|
+
} catch (error) {
|
|
1466
|
+
throw new Error(`\u6269\u5C55 ${extension.name} \u521D\u59CB\u5316\u5931\u8D25\uFF1A${error.message}`);
|
|
1467
|
+
}
|
|
1468
|
+
webviewScripts.push(...setup.webviewScripts ?? []);
|
|
1469
|
+
middlewares.push(...setup.middlewares ?? []);
|
|
1470
|
+
if (setup.wxmlTransform) transforms.push(setup.wxmlTransform);
|
|
1471
|
+
if (setup.entry) entries.push({ name: extension.name, ...setup.entry });
|
|
1472
|
+
}
|
|
1473
|
+
const wxmlTransform = transforms.length === 0 ? void 0 : (code, file) => transforms.reduce((result, transform) => transform(result, file), code);
|
|
1474
|
+
return { webviewScripts, middlewares, wxmlTransform, entries };
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1397
1477
|
// src/server/DevServer.ts
|
|
1478
|
+
var RESERVED_PREFIXES = ["/__wxa__", "/__baselib__", "/__plugin__"];
|
|
1398
1479
|
var DEFAULT_DEVICE = {
|
|
1399
1480
|
model: "iPhone X",
|
|
1400
1481
|
screenWidth: 375,
|
|
@@ -1416,7 +1497,12 @@ var DevServer = class {
|
|
|
1416
1497
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1417
1498
|
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1418
1499
|
this.logger = options.logger || console;
|
|
1419
|
-
this.project = new MiniProgram(
|
|
1500
|
+
this.project = new MiniProgram(path5.resolve(options.projectPath));
|
|
1501
|
+
const extended = applyExtensions(options, options.extensions ?? [], { projectPath: this.project.projectPath, root: this.project.root });
|
|
1502
|
+
extended.middlewares.forEach(({ path: mountPath }) => assertMountPath(mountPath));
|
|
1503
|
+
this.webviewScripts = extended.webviewScripts;
|
|
1504
|
+
this.middlewares = extended.middlewares;
|
|
1505
|
+
this.extensionEntries = extended.entries;
|
|
1420
1506
|
this.baseLib = resolveBaseLib({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
1421
1507
|
if (!this.baseLib.compatible) {
|
|
1422
1508
|
this.logger.warn(`[wxa-runner] \u57FA\u7840\u5E93 ${this.baseLib.version} \u9AD8\u4E8E\u5DF2\u9A8C\u8BC1\u7684\u7248\u672C\u8303\u56F4\uFF0C\u53EF\u80FD\u65E0\u6CD5\u6B63\u5E38\u8FD0\u884C`);
|
|
@@ -1428,7 +1514,7 @@ var DevServer = class {
|
|
|
1428
1514
|
this.ticketSource = this.devtoolsLogin ? developerTicketSource(this.project.projectPath, this.logger) : null;
|
|
1429
1515
|
this.plugins = new PluginStore(this.ticketSource && new WechatDevClient(this.project.appid, this.ticketSource), this.logger);
|
|
1430
1516
|
this.device = { ...DEFAULT_DEVICE, ...options.device };
|
|
1431
|
-
this.builder = new AppBuilder(this.project);
|
|
1517
|
+
this.builder = new AppBuilder(this.project, { wxmlTransform: extended.wxmlTransform });
|
|
1432
1518
|
this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);
|
|
1433
1519
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1434
1520
|
this.app = this.createApp();
|
|
@@ -1441,7 +1527,7 @@ var DevServer = class {
|
|
|
1441
1527
|
get networkUrls() {
|
|
1442
1528
|
if (!this.lanAccess) return [];
|
|
1443
1529
|
const { port } = new URL(this.address);
|
|
1444
|
-
return Object.values(
|
|
1530
|
+
return Object.values(os2.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1445
1531
|
}
|
|
1446
1532
|
async start() {
|
|
1447
1533
|
try {
|
|
@@ -1484,11 +1570,15 @@ ${error.message}`);
|
|
|
1484
1570
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1485
1571
|
}
|
|
1486
1572
|
this.fileSessions.clear();
|
|
1573
|
+
this.builder.dispose();
|
|
1487
1574
|
}
|
|
1488
1575
|
createApp() {
|
|
1489
1576
|
const app = express2();
|
|
1490
1577
|
const root = this.project.root;
|
|
1491
1578
|
const noStore = (res) => res.setHeader("Cache-Control", "no-store");
|
|
1579
|
+
for (const { path: mountPath, handler } of this.middlewares) {
|
|
1580
|
+
app.use(mountPath, (req, res, next) => handler(req, res, next));
|
|
1581
|
+
}
|
|
1492
1582
|
app.get("/", (_req, res) => {
|
|
1493
1583
|
noStore(res);
|
|
1494
1584
|
res.type("html").send(hostHtml());
|
|
@@ -1543,7 +1633,8 @@ ${error.message}`);
|
|
|
1543
1633
|
const pagePath = decodeURIComponent(req.params[0]);
|
|
1544
1634
|
if (!build2.pages.includes(pagePath)) return next();
|
|
1545
1635
|
const pluginPage = findPluginPage(build2, pagePath);
|
|
1546
|
-
|
|
1636
|
+
const scripts = this.webviewScripts;
|
|
1637
|
+
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 }));
|
|
1547
1638
|
}));
|
|
1548
1639
|
app.get("/__wxa__/plugin/:provider/:version/:file(appservice.js|pageframe.js)", this.withBuild((build2, req, res, next) => {
|
|
1549
1640
|
const { provider, version, file } = req.params;
|
|
@@ -1554,7 +1645,7 @@ ${error.message}`);
|
|
|
1554
1645
|
app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
|
|
1555
1646
|
const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
|
|
1556
1647
|
if (!file) return next();
|
|
1557
|
-
res.type(
|
|
1648
|
+
res.type(path5.extname(req.params[1]) || "application/octet-stream").send(file);
|
|
1558
1649
|
}));
|
|
1559
1650
|
app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
|
|
1560
1651
|
const id = decodeURIComponent(req.params[0]);
|
|
@@ -1565,7 +1656,7 @@ ${error.message}`);
|
|
|
1565
1656
|
res.type("js").send(this.builder.getModuleScript(id));
|
|
1566
1657
|
}));
|
|
1567
1658
|
app.get("/__wxa__/worker.js", this.withBuild((build2, _req, res) => {
|
|
1568
|
-
const hasWorker =
|
|
1659
|
+
const hasWorker = fs5.existsSync(path5.join(this.baseLib.dir, "WAWorker.js"));
|
|
1569
1660
|
res.type("js").send(workerScript(build2, { version: this.baseLib.version, hasWorker }));
|
|
1570
1661
|
}));
|
|
1571
1662
|
app.get("/__wxa__/service-bootstrap.js", this.withBuild((build2, _req, res) => {
|
|
@@ -1584,28 +1675,28 @@ ${error.message}`);
|
|
|
1584
1675
|
res.type("js").send(build2.pageFrame);
|
|
1585
1676
|
}));
|
|
1586
1677
|
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1587
|
-
const file =
|
|
1588
|
-
if (!
|
|
1589
|
-
res.type("json").send(ignoreConsoleHook(
|
|
1678
|
+
const file = path5.join(this.runtimeDir(), "frame.js.map");
|
|
1679
|
+
if (!fs5.existsSync(file)) return next();
|
|
1680
|
+
res.type("json").send(ignoreConsoleHook(fs5.readFileSync(file, "utf8")));
|
|
1590
1681
|
});
|
|
1591
1682
|
app.use("/__wxa__/runtime", express2.static(this.runtimeDir(), {
|
|
1592
1683
|
cacheControl: false,
|
|
1593
1684
|
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1594
1685
|
setHeaders: (res, file) => {
|
|
1595
|
-
if (
|
|
1686
|
+
if (path5.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1596
1687
|
}
|
|
1597
1688
|
}));
|
|
1598
1689
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1599
1690
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1600
|
-
const file =
|
|
1601
|
-
if (!file.startsWith(this.baseLib.dir +
|
|
1602
|
-
if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(
|
|
1691
|
+
const file = path5.resolve(this.baseLib.dir, req.params[0]);
|
|
1692
|
+
if (!file.startsWith(this.baseLib.dir + path5.sep) || !fs5.existsSync(file)) return next();
|
|
1693
|
+
if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(fs5.readFileSync(file, "utf8"), `${path5.basename(file)}?original`));
|
|
1603
1694
|
res.type("json").send(sourceMaps.get(file));
|
|
1604
1695
|
});
|
|
1605
1696
|
app.use("/__baselib__", express2.static(this.baseLib.dir, {
|
|
1606
1697
|
maxAge: "1d",
|
|
1607
1698
|
setHeaders: (res, file) => {
|
|
1608
|
-
if (file.endsWith(".js")) res.setHeader("SourceMap", `${
|
|
1699
|
+
if (file.endsWith(".js")) res.setHeader("SourceMap", `${path5.basename(file)}.map`);
|
|
1609
1700
|
}
|
|
1610
1701
|
}));
|
|
1611
1702
|
app.use(PROXY_PREFIX, express2.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
@@ -1665,7 +1756,7 @@ ${error.message}`);
|
|
|
1665
1756
|
return;
|
|
1666
1757
|
}
|
|
1667
1758
|
const data = node.data;
|
|
1668
|
-
res.type(
|
|
1759
|
+
res.type(path5.extname(req.params[0]));
|
|
1669
1760
|
res.setHeader("ETag", `"${node.etag}"`);
|
|
1670
1761
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1671
1762
|
const ranges = req.range(data.length);
|
|
@@ -1735,11 +1826,11 @@ ${error.message}`);
|
|
|
1735
1826
|
runtimeDir() {
|
|
1736
1827
|
const candidates = [
|
|
1737
1828
|
this.options.runtimeDir,
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1829
|
+
path5.join(__dirname, "runtime"),
|
|
1830
|
+
path5.join(__dirname, "../runtime"),
|
|
1831
|
+
path5.join(__dirname, "../../dist/runtime")
|
|
1741
1832
|
].filter((dir) => !!dir);
|
|
1742
|
-
const found = candidates.find((dir) =>
|
|
1833
|
+
const found = candidates.find((dir) => fs5.existsSync(path5.join(dir, "host.js")));
|
|
1743
1834
|
if (!found) {
|
|
1744
1835
|
throw new Error("\u627E\u4E0D\u5230\u6D4F\u89C8\u5668\u7AEF\u8FD0\u884C\u65F6\uFF08host.js\uFF09\uFF0C\u8BF7\u5148\u6267\u884C pnpm build");
|
|
1745
1836
|
}
|
|
@@ -1752,7 +1843,7 @@ ${error.message}`);
|
|
|
1752
1843
|
startWatching() {
|
|
1753
1844
|
let lastBuildFailed = false;
|
|
1754
1845
|
const watched = [this.project.root];
|
|
1755
|
-
if (this.setupFile &&
|
|
1846
|
+
if (this.setupFile && path5.relative(this.project.root, this.setupFile).startsWith("..")) watched.push(this.setupFile);
|
|
1756
1847
|
this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {
|
|
1757
1848
|
const reloadOnly = structural || lastBuildFailed;
|
|
1758
1849
|
this.builder.invalidate(files);
|
|
@@ -1783,7 +1874,7 @@ var DEFAULT_PORT = 3e3;
|
|
|
1783
1874
|
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1784
1875
|
async function listen(server, port, host, logger) {
|
|
1785
1876
|
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1786
|
-
const taken =
|
|
1877
|
+
const taken = await takenOnLocalAddresses(candidate) || !await bind(server, candidate, host);
|
|
1787
1878
|
if (!taken) {
|
|
1788
1879
|
const actual = server.address().port;
|
|
1789
1880
|
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
@@ -1802,8 +1893,8 @@ function bind(server, port, host) {
|
|
|
1802
1893
|
});
|
|
1803
1894
|
});
|
|
1804
1895
|
}
|
|
1805
|
-
async function
|
|
1806
|
-
for (const address of ["127.0.0.1", "::1"]) {
|
|
1896
|
+
async function takenOnLocalAddresses(port) {
|
|
1897
|
+
for (const address of ["127.0.0.1", "::1", "0.0.0.0", "::"]) {
|
|
1807
1898
|
const probe = net.createServer();
|
|
1808
1899
|
const free = await bind(probe, port, address).catch(() => true);
|
|
1809
1900
|
if (!free) return true;
|
|
@@ -1811,11 +1902,21 @@ async function takenOnLoopback(port) {
|
|
|
1811
1902
|
}
|
|
1812
1903
|
return false;
|
|
1813
1904
|
}
|
|
1905
|
+
function assertMountPath(mountPath) {
|
|
1906
|
+
const literal = typeof mountPath === "string" && /^(\/[\w.~-]+)+$/.test(mountPath);
|
|
1907
|
+
const lower = literal ? mountPath.toLowerCase() : "";
|
|
1908
|
+
const reserved = RESERVED_PREFIXES.some((prefix) => lower === prefix || lower.startsWith(prefix + "/"));
|
|
1909
|
+
if (!literal || reserved) {
|
|
1910
|
+
throw new Error(
|
|
1911
|
+
`\u4E2D\u95F4\u4EF6\u8DEF\u5F84 ${String(mountPath)} \u4E0D\u53EF\u7528\uFF1A\u8981\u4EE5 / \u5F00\u5934\uFF0C\u53EA\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u548C _ . ~ -\uFF08\u4E0D\u652F\u6301\u901A\u914D\u7B26\u548C\u53C2\u6570\uFF09\uFF0C\u4E0D\u80FD\u662F /\uFF0C\u4E5F\u4E0D\u80FD\u7528 wxa-runner \u4FDD\u7559\u7684\u524D\u7F00 ${RESERVED_PREFIXES.join("\u3001")}\uFF08\u4E0D\u533A\u5206\u5927\u5C0F\u5199\uFF09`
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1814
1915
|
function resolveSetupFile(option, projectPath) {
|
|
1815
1916
|
if (option === false) return null;
|
|
1816
1917
|
if (!option) return findSetupFile(projectPath);
|
|
1817
|
-
const file =
|
|
1818
|
-
if (!
|
|
1918
|
+
const file = path5.resolve(option);
|
|
1919
|
+
if (!fs5.existsSync(file)) throw new Error(`\u627E\u4E0D\u5230 setup \u6587\u4EF6\uFF1A${file}`);
|
|
1819
1920
|
return file;
|
|
1820
1921
|
}
|
|
1821
1922
|
export {
|