@open-wxa/wxa-runner-core 0.8.2 → 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.js
CHANGED
|
@@ -164,11 +164,11 @@ var FileStore = class {
|
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
// src/server/DevServer.ts
|
|
167
|
-
var
|
|
167
|
+
var import_fs5 = __toESM(require("fs"));
|
|
168
168
|
var import_http = __toESM(require("http"));
|
|
169
169
|
var import_net = __toESM(require("net"));
|
|
170
|
-
var
|
|
171
|
-
var
|
|
170
|
+
var import_os2 = __toESM(require("os"));
|
|
171
|
+
var import_path5 = __toESM(require("path"));
|
|
172
172
|
var import_express2 = __toESM(require("express"));
|
|
173
173
|
var import_ws2 = require("ws");
|
|
174
174
|
var import_wxa_runner_extractor5 = require("@open-wxa/wxa-runner-extractor");
|
|
@@ -233,6 +233,50 @@ function createWxConfig(input) {
|
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
// src/project/WxmlMirror.ts
|
|
237
|
+
var import_fs = __toESM(require("fs"));
|
|
238
|
+
var import_os = __toESM(require("os"));
|
|
239
|
+
var import_path = __toESM(require("path"));
|
|
240
|
+
var WxmlMirror = class {
|
|
241
|
+
constructor(transform) {
|
|
242
|
+
this.transform = transform;
|
|
243
|
+
this.dir = null;
|
|
244
|
+
/** 已写入的文件:源内容(判断要不要重新转换)与写入的内容 */
|
|
245
|
+
this.files = /* @__PURE__ */ new Map();
|
|
246
|
+
}
|
|
247
|
+
/** 镜像目录;还没同步过时为 null */
|
|
248
|
+
get directory() {
|
|
249
|
+
return this.dir;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 让镜像目录与项目里的 wxml / wxs 一致,返回镜像目录。
|
|
253
|
+
* 只重写源内容变化的文件;项目里已删除的文件同时从镜像目录删除,被引用时 wcc 照常报错
|
|
254
|
+
*/
|
|
255
|
+
sync(project, relPaths) {
|
|
256
|
+
const dir = this.dir ??= import_fs.default.mkdtempSync(import_path.default.join(import_os.default.tmpdir(), "wxa-runner-wxml-"));
|
|
257
|
+
const current = new Set(relPaths);
|
|
258
|
+
for (const rel of [...this.files.keys()]) {
|
|
259
|
+
if (current.has(rel)) continue;
|
|
260
|
+
import_fs.default.rmSync(import_path.default.join(dir, rel), { force: true });
|
|
261
|
+
this.files.delete(rel);
|
|
262
|
+
}
|
|
263
|
+
for (const rel of relPaths) {
|
|
264
|
+
const source = project.readText(rel);
|
|
265
|
+
if (this.files.get(rel) === source) continue;
|
|
266
|
+
const target = import_path.default.join(dir, rel);
|
|
267
|
+
import_fs.default.mkdirSync(import_path.default.dirname(target), { recursive: true });
|
|
268
|
+
import_fs.default.writeFileSync(target, rel.endsWith(".wxml") ? this.transform(source, rel) : source);
|
|
269
|
+
this.files.set(rel, source);
|
|
270
|
+
}
|
|
271
|
+
return dir;
|
|
272
|
+
}
|
|
273
|
+
dispose() {
|
|
274
|
+
if (this.dir) import_fs.default.rmSync(this.dir, { recursive: true, force: true });
|
|
275
|
+
this.dir = null;
|
|
276
|
+
this.files.clear();
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
236
280
|
// src/project/AppBuilder.ts
|
|
237
281
|
var MODULE_PARAMS = [
|
|
238
282
|
"require",
|
|
@@ -259,7 +303,7 @@ var MODULE_PARAMS = [
|
|
|
259
303
|
].join(", ");
|
|
260
304
|
var ESM_SYNTAX = /(?:^|[;}])\s*(?:import\s*[{*'"]|import\s+[\w$]|export\s*[{*]|export\s+(?:default|const|let|var|function|class|async)\b)/m;
|
|
261
305
|
var AppBuilder = class {
|
|
262
|
-
constructor(project) {
|
|
306
|
+
constructor(project, options = {}) {
|
|
263
307
|
this.project = project;
|
|
264
308
|
this.version = 0;
|
|
265
309
|
/** invalidate 标记过的变化次数;每次构建记下开始时覆盖到第几次 */
|
|
@@ -269,6 +313,15 @@ var AppBuilder = class {
|
|
|
269
313
|
this.dirty = /* @__PURE__ */ new Set(["config", "wxml", "wxss", "js"]);
|
|
270
314
|
this.wxmlCache = null;
|
|
271
315
|
this.wxssCache = null;
|
|
316
|
+
this.wxmlMirror = options.wxmlTransform ? new WxmlMirror(options.wxmlTransform) : null;
|
|
317
|
+
}
|
|
318
|
+
/** wxmlTransform 的镜像目录;没有设置转换或还没编译过时为 null */
|
|
319
|
+
get wxmlMirrorDir() {
|
|
320
|
+
return this.wxmlMirror?.directory ?? null;
|
|
321
|
+
}
|
|
322
|
+
/** 删除 wxmlTransform 的镜像目录,开发服务器关闭时调用 */
|
|
323
|
+
dispose() {
|
|
324
|
+
this.wxmlMirror?.dispose();
|
|
272
325
|
}
|
|
273
326
|
/** 根据变化的文件标记需要重新构建的部分;返回 true 表示需要刷新 */
|
|
274
327
|
invalidate(changedFiles = []) {
|
|
@@ -393,7 +446,7 @@ ${pageStyles}`,
|
|
|
393
446
|
const json = unitJson.get(id);
|
|
394
447
|
return { path: id, tags: Object.keys({ ...json.usingComponents, ...json.componentGenerics }) };
|
|
395
448
|
});
|
|
396
|
-
const cwd = this.project.root;
|
|
449
|
+
const cwd = this.wxmlMirror ? this.wxmlMirror.sync(this.project, files.map((file) => file.slice(2))) : this.project.root;
|
|
397
450
|
const [full, cut] = await Promise.all([
|
|
398
451
|
(0, import_wxa_runner_compiler.compileWxml)({ cwd, files, components, cut: false }),
|
|
399
452
|
(0, import_wxa_runner_compiler.compileWxml)({ cwd, files, components, cut: true })
|
|
@@ -460,22 +513,22 @@ function classifyFile(file) {
|
|
|
460
513
|
}
|
|
461
514
|
|
|
462
515
|
// src/project/MiniProgram.ts
|
|
463
|
-
var
|
|
464
|
-
var
|
|
516
|
+
var import_fs2 = __toESM(require("fs"));
|
|
517
|
+
var import_path2 = __toESM(require("path"));
|
|
465
518
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".idea", ".vscode"]);
|
|
466
519
|
var PROJECT_FILES = /* @__PURE__ */ new Set(["project.config.json", "project.private.config.json", "sitemap.json", "app.json", "wxa.setup.js"]);
|
|
467
520
|
var MiniProgram = class {
|
|
468
521
|
constructor(projectPath) {
|
|
469
522
|
this.projectPath = projectPath;
|
|
470
|
-
const projectConfig = readJsonFile(
|
|
471
|
-
this.root =
|
|
523
|
+
const projectConfig = readJsonFile(import_path2.default.join(projectPath, "project.config.json")) || {};
|
|
524
|
+
this.root = import_path2.default.resolve(projectPath, projectConfig.miniprogramRoot || "");
|
|
472
525
|
this.appid = projectConfig.appid || "touristappid";
|
|
473
|
-
if (!
|
|
526
|
+
if (!import_fs2.default.existsSync(import_path2.default.join(this.root, "app.json"))) {
|
|
474
527
|
throw new Error(`${this.root} \u4E0B\u6CA1\u6709 app.json\uFF0C\u4E0D\u662F\u6709\u6548\u7684\u5C0F\u7A0B\u5E8F\u76EE\u5F55`);
|
|
475
528
|
}
|
|
476
529
|
}
|
|
477
530
|
readAppJson() {
|
|
478
|
-
const app = readJsonFile(
|
|
531
|
+
const app = readJsonFile(import_path2.default.join(this.root, "app.json"));
|
|
479
532
|
if (!app || !Array.isArray(app.pages) || app.pages.length === 0) {
|
|
480
533
|
throw new Error("app.json \u7F3A\u5C11 pages \u914D\u7F6E");
|
|
481
534
|
}
|
|
@@ -487,14 +540,14 @@ var MiniProgram = class {
|
|
|
487
540
|
/** Worker 代码所在目录,不带首尾的 /;没有配置 workers 时为 null */
|
|
488
541
|
workersDir(app) {
|
|
489
542
|
const dir = typeof app.workers === "string" ? app.workers : app.workers?.path;
|
|
490
|
-
return dir ?
|
|
543
|
+
return dir ? import_path2.default.posix.normalize(dir).replace(/^\/+|\/+$/g, "") : null;
|
|
491
544
|
}
|
|
492
545
|
/** 主包页面 + 分包页面(带 root 前缀) */
|
|
493
546
|
allPages(app) {
|
|
494
547
|
const pages = [...app.pages];
|
|
495
548
|
for (const pkg of this.subPackages(app)) {
|
|
496
549
|
for (const page of pkg.pages) {
|
|
497
|
-
const full =
|
|
550
|
+
const full = import_path2.default.posix.join(pkg.root, page);
|
|
498
551
|
if (!pages.includes(full)) pages.push(full);
|
|
499
552
|
}
|
|
500
553
|
}
|
|
@@ -506,7 +559,7 @@ var MiniProgram = class {
|
|
|
506
559
|
* 所以能解析到的引用都换成组件的绝对路径
|
|
507
560
|
*/
|
|
508
561
|
readUnitJson(id, app) {
|
|
509
|
-
const json = { ...readJsonFile(
|
|
562
|
+
const json = { ...readJsonFile(import_path2.default.join(this.root, id + ".json")) };
|
|
510
563
|
const resolve = (fromId, ref) => {
|
|
511
564
|
const resolved = this.resolveComponent(fromId, ref);
|
|
512
565
|
return resolved ? "/" + resolved : null;
|
|
@@ -527,7 +580,7 @@ var MiniProgram = class {
|
|
|
527
580
|
if (resolved || ref.startsWith("/") || /^plugin(-private)?:\/\//.test(ref)) {
|
|
528
581
|
json.usingComponents[tag] = resolved ?? ref;
|
|
529
582
|
} else {
|
|
530
|
-
const relative =
|
|
583
|
+
const relative = import_path2.default.posix.relative(import_path2.default.posix.dirname(id), ref);
|
|
531
584
|
json.usingComponents[tag] = relative.startsWith(".") ? relative : "./" + relative;
|
|
532
585
|
}
|
|
533
586
|
}
|
|
@@ -540,25 +593,25 @@ var MiniProgram = class {
|
|
|
540
593
|
*/
|
|
541
594
|
resolveComponent(fromId, ref) {
|
|
542
595
|
if (!ref || ref.startsWith("plugin://") || ref.startsWith("plugin-private://")) return null;
|
|
543
|
-
const fromDir =
|
|
596
|
+
const fromDir = import_path2.default.posix.dirname(fromId);
|
|
544
597
|
const candidates = [];
|
|
545
598
|
if (ref.startsWith("/")) {
|
|
546
599
|
candidates.push(ref.slice(1));
|
|
547
600
|
} else {
|
|
548
|
-
candidates.push(
|
|
601
|
+
candidates.push(import_path2.default.posix.join(fromDir, ref));
|
|
549
602
|
if (!ref.startsWith(".")) {
|
|
550
603
|
let dir = fromDir;
|
|
551
604
|
for (; ; ) {
|
|
552
|
-
candidates.push(
|
|
605
|
+
candidates.push(import_path2.default.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
|
|
553
606
|
if (dir === "." || dir === "") break;
|
|
554
|
-
dir =
|
|
607
|
+
dir = import_path2.default.posix.dirname(dir);
|
|
555
608
|
}
|
|
556
609
|
}
|
|
557
610
|
}
|
|
558
611
|
for (const candidate of candidates) {
|
|
559
|
-
const normalized =
|
|
560
|
-
for (const id of [normalized,
|
|
561
|
-
if (
|
|
612
|
+
const normalized = import_path2.default.posix.normalize(candidate);
|
|
613
|
+
for (const id of [normalized, import_path2.default.posix.join(normalized, "index")]) {
|
|
614
|
+
if (import_fs2.default.existsSync(import_path2.default.join(this.root, id + ".json"))) return id;
|
|
562
615
|
}
|
|
563
616
|
}
|
|
564
617
|
return null;
|
|
@@ -590,11 +643,11 @@ var MiniProgram = class {
|
|
|
590
643
|
listFiles(extensions) {
|
|
591
644
|
const result = [];
|
|
592
645
|
const walk = (dir, rel) => {
|
|
593
|
-
for (const entry of
|
|
646
|
+
for (const entry of import_fs2.default.readdirSync(dir, { withFileTypes: true })) {
|
|
594
647
|
if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
|
|
595
648
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
596
649
|
if (entry.isDirectory()) {
|
|
597
|
-
walk(
|
|
650
|
+
walk(import_path2.default.join(dir, entry.name), childRel);
|
|
598
651
|
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
599
652
|
if (!rel && PROJECT_FILES.has(entry.name)) continue;
|
|
600
653
|
result.push(childRel);
|
|
@@ -605,18 +658,18 @@ var MiniProgram = class {
|
|
|
605
658
|
return result.sort();
|
|
606
659
|
}
|
|
607
660
|
exists(relPath) {
|
|
608
|
-
return
|
|
661
|
+
return import_fs2.default.existsSync(import_path2.default.join(this.root, relPath));
|
|
609
662
|
}
|
|
610
663
|
readText(relPath) {
|
|
611
|
-
return
|
|
664
|
+
return import_fs2.default.readFileSync(import_path2.default.join(this.root, relPath), "utf8");
|
|
612
665
|
}
|
|
613
666
|
isInIndependentSubPackage(id, app) {
|
|
614
667
|
return this.subPackages(app).some((pkg) => pkg.independent && id.startsWith(pkg.root.replace(/\/?$/, "/")));
|
|
615
668
|
}
|
|
616
669
|
};
|
|
617
670
|
function readJsonFile(file) {
|
|
618
|
-
if (!
|
|
619
|
-
const text =
|
|
671
|
+
if (!import_fs2.default.existsSync(file)) return null;
|
|
672
|
+
const text = import_fs2.default.readFileSync(file, "utf8");
|
|
620
673
|
try {
|
|
621
674
|
return JSON.parse(text);
|
|
622
675
|
} catch (error) {
|
|
@@ -682,9 +735,9 @@ var WechatDevClient = class {
|
|
|
682
735
|
*
|
|
683
736
|
* @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
|
|
684
737
|
*/
|
|
685
|
-
async post(
|
|
738
|
+
async post(path6, body, timeout = DEFAULT_TIMEOUT) {
|
|
686
739
|
const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
|
|
687
|
-
const response = await fetch(`${BASE_URL}/${
|
|
740
|
+
const response = await fetch(`${BASE_URL}/${path6}?${query}`, {
|
|
688
741
|
method: "POST",
|
|
689
742
|
headers: { "Content-Type": "application/json" },
|
|
690
743
|
body: JSON.stringify(body),
|
|
@@ -696,8 +749,8 @@ var WechatDevClient = class {
|
|
|
696
749
|
return response;
|
|
697
750
|
}
|
|
698
751
|
/** 同 post,返回解析后的 JSON */
|
|
699
|
-
async postJson(
|
|
700
|
-
return (await this.post(
|
|
752
|
+
async postJson(path6, body) {
|
|
753
|
+
return (await this.post(path6, body)).json();
|
|
701
754
|
}
|
|
702
755
|
};
|
|
703
756
|
|
|
@@ -893,12 +946,12 @@ function webviewHtml(build2, pagePath, webviewId, options) {
|
|
|
893
946
|
return `<!DOCTYPE html>
|
|
894
947
|
<html>
|
|
895
948
|
<head>
|
|
896
|
-
${webviewHead(build2, pagePath, id)}
|
|
949
|
+
${webviewHead(build2, pagePath, id, options.scripts)}
|
|
897
950
|
${enableHotUpdate(options.hot)}<script src="/__wxa__/page-frame.js?v=${build2.version}"></script>
|
|
898
951
|
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
899
952
|
</head>
|
|
900
953
|
<body>
|
|
901
|
-
<script>
|
|
954
|
+
${scriptTags(options.scripts, "body")}<script>
|
|
902
955
|
(function () {
|
|
903
956
|
var path = ${toJs(pagePath)};
|
|
904
957
|
if (typeof __wxAppCode__[path + '.wxss'] === 'function') __wxAppCode__[path + '.wxss']();
|
|
@@ -931,18 +984,18 @@ self.console = __wxaConsole;
|
|
|
931
984
|
importScripts(${modules.map((url) => toJs(url)).join(", ")});
|
|
932
985
|
`;
|
|
933
986
|
}
|
|
934
|
-
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId) {
|
|
987
|
+
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId, options = {}) {
|
|
935
988
|
const id = Number(webviewId) || 0;
|
|
936
989
|
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>`;
|
|
937
990
|
return `<!DOCTYPE html>
|
|
938
991
|
<html>
|
|
939
992
|
<head>
|
|
940
|
-
${webviewHead(build2, pagePath, id)}
|
|
993
|
+
${webviewHead(build2, pagePath, id, options.scripts)}
|
|
941
994
|
<script src="${pluginScriptUrl(plugin, "pageframe.js")}"></script>
|
|
942
995
|
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
943
996
|
</head>
|
|
944
997
|
<body>
|
|
945
|
-
${page}
|
|
998
|
+
${scriptTags(options.scripts, "body")}${page}
|
|
946
999
|
<script>parent.__wxaHost.frameLoaded('webview', ${id});</script>
|
|
947
1000
|
</body>
|
|
948
1001
|
</html>`;
|
|
@@ -950,7 +1003,7 @@ ${page}
|
|
|
950
1003
|
function pageWindow(build2, pagePath) {
|
|
951
1004
|
return { ...build2.wxConfig.global?.window, ...build2.wxConfig.page?.[pagePath + ".html"]?.window };
|
|
952
1005
|
}
|
|
953
|
-
function webviewHead(build2, pagePath, id) {
|
|
1006
|
+
function webviewHead(build2, pagePath, id, scripts) {
|
|
954
1007
|
const wxConfig = { ...build2.wxConfig, window: pageWindow(build2, pagePath) };
|
|
955
1008
|
return `<meta charset="utf-8">
|
|
956
1009
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
@@ -965,7 +1018,12 @@ function webviewHead(build2, pagePath, id) {
|
|
|
965
1018
|
<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>
|
|
966
1019
|
<script>var __wxConfig = ${toJs(wxConfig)};</script>
|
|
967
1020
|
<script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"></script>
|
|
968
|
-
<script src="/__baselib__/WAWebview.js"></script>`;
|
|
1021
|
+
${scriptTags(scripts, "head")}<script src="/__baselib__/WAWebview.js"></script>`;
|
|
1022
|
+
}
|
|
1023
|
+
function scriptTags(scripts, position) {
|
|
1024
|
+
return (scripts ?? []).filter((script) => (script.position ?? "body") === position).map((script) => script.src !== void 0 ? `<script src="${escapeHtml(script.src)}"></script>
|
|
1025
|
+
` : `<script>${script.content.replace(/<\/script/gi, "<\\/script")}</script>
|
|
1026
|
+
`).join("");
|
|
969
1027
|
}
|
|
970
1028
|
function escapeHtml(text) {
|
|
971
1029
|
return text.replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
@@ -1329,16 +1387,16 @@ function respond(action) {
|
|
|
1329
1387
|
}
|
|
1330
1388
|
|
|
1331
1389
|
// src/server/setup.ts
|
|
1332
|
-
var
|
|
1333
|
-
var
|
|
1390
|
+
var import_fs3 = __toESM(require("fs"));
|
|
1391
|
+
var import_path3 = __toESM(require("path"));
|
|
1334
1392
|
var import_esbuild2 = require("esbuild");
|
|
1335
1393
|
var SETUP_FILE_NAMES = ["wxa.setup.ts", "wxa.setup.js", "wxa.setup.mjs"];
|
|
1336
1394
|
var SETUP_MODULE = "@open-wxa/wxa-runner-core/setup";
|
|
1337
1395
|
var ENV_PREFIX = "WXA_";
|
|
1338
1396
|
function findSetupFile(projectPath) {
|
|
1339
1397
|
for (const name of SETUP_FILE_NAMES) {
|
|
1340
|
-
const file =
|
|
1341
|
-
if (
|
|
1398
|
+
const file = import_path3.default.join(projectPath, name);
|
|
1399
|
+
if (import_fs3.default.existsSync(file)) return file;
|
|
1342
1400
|
}
|
|
1343
1401
|
return null;
|
|
1344
1402
|
}
|
|
@@ -1350,7 +1408,7 @@ function collectSetupEnv(env) {
|
|
|
1350
1408
|
async function bundleSetup(file) {
|
|
1351
1409
|
const result = await (0, import_esbuild2.build)({
|
|
1352
1410
|
entryPoints: [file],
|
|
1353
|
-
absWorkingDir:
|
|
1411
|
+
absWorkingDir: import_path3.default.dirname(file),
|
|
1354
1412
|
bundle: true,
|
|
1355
1413
|
format: "esm",
|
|
1356
1414
|
platform: "browser",
|
|
@@ -1378,8 +1436,8 @@ function builtinSetupModule() {
|
|
|
1378
1436
|
|
|
1379
1437
|
// src/server/watcher.ts
|
|
1380
1438
|
var import_crypto3 = require("crypto");
|
|
1381
|
-
var
|
|
1382
|
-
var
|
|
1439
|
+
var import_fs4 = __toESM(require("fs"));
|
|
1440
|
+
var import_path4 = __toESM(require("path"));
|
|
1383
1441
|
var import_chokidar = __toESM(require("chokidar"));
|
|
1384
1442
|
var DEBOUNCE = 120;
|
|
1385
1443
|
var IGNORED = /(^|[/\\])(node_modules|\.git)([/\\]|$)/;
|
|
@@ -1407,7 +1465,7 @@ function watchProject(root, paths, onChange) {
|
|
|
1407
1465
|
const unchanged = updateFingerprint(fingerprints, file, event);
|
|
1408
1466
|
if (!scanned || unchanged) return;
|
|
1409
1467
|
if (event !== "change") structural = true;
|
|
1410
|
-
changed.add(
|
|
1468
|
+
changed.add(import_path4.default.relative(root, file).split(import_path4.default.sep).join("/"));
|
|
1411
1469
|
if (timer) clearTimeout(timer);
|
|
1412
1470
|
timer = setTimeout(flush, DEBOUNCE);
|
|
1413
1471
|
});
|
|
@@ -1421,7 +1479,7 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1421
1479
|
if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
|
|
1422
1480
|
let next;
|
|
1423
1481
|
try {
|
|
1424
|
-
next = (0, import_crypto3.createHash)("sha1").update(
|
|
1482
|
+
next = (0, import_crypto3.createHash)("sha1").update(import_fs4.default.readFileSync(file)).digest("hex");
|
|
1425
1483
|
} catch {
|
|
1426
1484
|
return false;
|
|
1427
1485
|
}
|
|
@@ -1430,7 +1488,30 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1430
1488
|
return previous === next;
|
|
1431
1489
|
}
|
|
1432
1490
|
|
|
1491
|
+
// src/server/extensions.ts
|
|
1492
|
+
function applyExtensions(base, extensions, context) {
|
|
1493
|
+
const webviewScripts = [...base.webviewScripts ?? []];
|
|
1494
|
+
const middlewares = [...base.middlewares ?? []];
|
|
1495
|
+
const transforms = base.wxmlTransform ? [base.wxmlTransform] : [];
|
|
1496
|
+
const entries = [];
|
|
1497
|
+
for (const extension of extensions) {
|
|
1498
|
+
let setup;
|
|
1499
|
+
try {
|
|
1500
|
+
setup = extension.setup(context);
|
|
1501
|
+
} catch (error) {
|
|
1502
|
+
throw new Error(`\u6269\u5C55 ${extension.name} \u521D\u59CB\u5316\u5931\u8D25\uFF1A${error.message}`);
|
|
1503
|
+
}
|
|
1504
|
+
webviewScripts.push(...setup.webviewScripts ?? []);
|
|
1505
|
+
middlewares.push(...setup.middlewares ?? []);
|
|
1506
|
+
if (setup.wxmlTransform) transforms.push(setup.wxmlTransform);
|
|
1507
|
+
if (setup.entry) entries.push({ name: extension.name, ...setup.entry });
|
|
1508
|
+
}
|
|
1509
|
+
const wxmlTransform = transforms.length === 0 ? void 0 : (code, file) => transforms.reduce((result, transform) => transform(result, file), code);
|
|
1510
|
+
return { webviewScripts, middlewares, wxmlTransform, entries };
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1433
1513
|
// src/server/DevServer.ts
|
|
1514
|
+
var RESERVED_PREFIXES = ["/__wxa__", "/__baselib__", "/__plugin__"];
|
|
1434
1515
|
var DEFAULT_DEVICE = {
|
|
1435
1516
|
model: "iPhone X",
|
|
1436
1517
|
screenWidth: 375,
|
|
@@ -1452,7 +1533,12 @@ var DevServer = class {
|
|
|
1452
1533
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1453
1534
|
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1454
1535
|
this.logger = options.logger || console;
|
|
1455
|
-
this.project = new MiniProgram(
|
|
1536
|
+
this.project = new MiniProgram(import_path5.default.resolve(options.projectPath));
|
|
1537
|
+
const extended = applyExtensions(options, options.extensions ?? [], { projectPath: this.project.projectPath, root: this.project.root });
|
|
1538
|
+
extended.middlewares.forEach(({ path: mountPath }) => assertMountPath(mountPath));
|
|
1539
|
+
this.webviewScripts = extended.webviewScripts;
|
|
1540
|
+
this.middlewares = extended.middlewares;
|
|
1541
|
+
this.extensionEntries = extended.entries;
|
|
1456
1542
|
this.baseLib = (0, import_wxa_runner_extractor5.resolveBaseLib)({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
1457
1543
|
if (!this.baseLib.compatible) {
|
|
1458
1544
|
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`);
|
|
@@ -1464,7 +1550,7 @@ var DevServer = class {
|
|
|
1464
1550
|
this.ticketSource = this.devtoolsLogin ? developerTicketSource(this.project.projectPath, this.logger) : null;
|
|
1465
1551
|
this.plugins = new PluginStore(this.ticketSource && new WechatDevClient(this.project.appid, this.ticketSource), this.logger);
|
|
1466
1552
|
this.device = { ...DEFAULT_DEVICE, ...options.device };
|
|
1467
|
-
this.builder = new AppBuilder(this.project);
|
|
1553
|
+
this.builder = new AppBuilder(this.project, { wxmlTransform: extended.wxmlTransform });
|
|
1468
1554
|
this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);
|
|
1469
1555
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1470
1556
|
this.app = this.createApp();
|
|
@@ -1477,7 +1563,7 @@ var DevServer = class {
|
|
|
1477
1563
|
get networkUrls() {
|
|
1478
1564
|
if (!this.lanAccess) return [];
|
|
1479
1565
|
const { port } = new URL(this.address);
|
|
1480
|
-
return Object.values(
|
|
1566
|
+
return Object.values(import_os2.default.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1481
1567
|
}
|
|
1482
1568
|
async start() {
|
|
1483
1569
|
try {
|
|
@@ -1520,11 +1606,15 @@ ${error.message}`);
|
|
|
1520
1606
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1521
1607
|
}
|
|
1522
1608
|
this.fileSessions.clear();
|
|
1609
|
+
this.builder.dispose();
|
|
1523
1610
|
}
|
|
1524
1611
|
createApp() {
|
|
1525
1612
|
const app = (0, import_express2.default)();
|
|
1526
1613
|
const root = this.project.root;
|
|
1527
1614
|
const noStore = (res) => res.setHeader("Cache-Control", "no-store");
|
|
1615
|
+
for (const { path: mountPath, handler } of this.middlewares) {
|
|
1616
|
+
app.use(mountPath, (req, res, next) => handler(req, res, next));
|
|
1617
|
+
}
|
|
1528
1618
|
app.get("/", (_req, res) => {
|
|
1529
1619
|
noStore(res);
|
|
1530
1620
|
res.type("html").send(hostHtml());
|
|
@@ -1579,7 +1669,8 @@ ${error.message}`);
|
|
|
1579
1669
|
const pagePath = decodeURIComponent(req.params[0]);
|
|
1580
1670
|
if (!build2.pages.includes(pagePath)) return next();
|
|
1581
1671
|
const pluginPage = findPluginPage(build2, pagePath);
|
|
1582
|
-
|
|
1672
|
+
const scripts = this.webviewScripts;
|
|
1673
|
+
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 }));
|
|
1583
1674
|
}));
|
|
1584
1675
|
app.get("/__wxa__/plugin/:provider/:version/:file(appservice.js|pageframe.js)", this.withBuild((build2, req, res, next) => {
|
|
1585
1676
|
const { provider, version, file } = req.params;
|
|
@@ -1590,7 +1681,7 @@ ${error.message}`);
|
|
|
1590
1681
|
app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
|
|
1591
1682
|
const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
|
|
1592
1683
|
if (!file) return next();
|
|
1593
|
-
res.type(
|
|
1684
|
+
res.type(import_path5.default.extname(req.params[1]) || "application/octet-stream").send(file);
|
|
1594
1685
|
}));
|
|
1595
1686
|
app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
|
|
1596
1687
|
const id = decodeURIComponent(req.params[0]);
|
|
@@ -1601,7 +1692,7 @@ ${error.message}`);
|
|
|
1601
1692
|
res.type("js").send(this.builder.getModuleScript(id));
|
|
1602
1693
|
}));
|
|
1603
1694
|
app.get("/__wxa__/worker.js", this.withBuild((build2, _req, res) => {
|
|
1604
|
-
const hasWorker =
|
|
1695
|
+
const hasWorker = import_fs5.default.existsSync(import_path5.default.join(this.baseLib.dir, "WAWorker.js"));
|
|
1605
1696
|
res.type("js").send(workerScript(build2, { version: this.baseLib.version, hasWorker }));
|
|
1606
1697
|
}));
|
|
1607
1698
|
app.get("/__wxa__/service-bootstrap.js", this.withBuild((build2, _req, res) => {
|
|
@@ -1620,28 +1711,28 @@ ${error.message}`);
|
|
|
1620
1711
|
res.type("js").send(build2.pageFrame);
|
|
1621
1712
|
}));
|
|
1622
1713
|
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1623
|
-
const file =
|
|
1624
|
-
if (!
|
|
1625
|
-
res.type("json").send(ignoreConsoleHook(
|
|
1714
|
+
const file = import_path5.default.join(this.runtimeDir(), "frame.js.map");
|
|
1715
|
+
if (!import_fs5.default.existsSync(file)) return next();
|
|
1716
|
+
res.type("json").send(ignoreConsoleHook(import_fs5.default.readFileSync(file, "utf8")));
|
|
1626
1717
|
});
|
|
1627
1718
|
app.use("/__wxa__/runtime", import_express2.default.static(this.runtimeDir(), {
|
|
1628
1719
|
cacheControl: false,
|
|
1629
1720
|
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1630
1721
|
setHeaders: (res, file) => {
|
|
1631
|
-
if (
|
|
1722
|
+
if (import_path5.default.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1632
1723
|
}
|
|
1633
1724
|
}));
|
|
1634
1725
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1635
1726
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1636
|
-
const file =
|
|
1637
|
-
if (!file.startsWith(this.baseLib.dir +
|
|
1638
|
-
if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(
|
|
1727
|
+
const file = import_path5.default.resolve(this.baseLib.dir, req.params[0]);
|
|
1728
|
+
if (!file.startsWith(this.baseLib.dir + import_path5.default.sep) || !import_fs5.default.existsSync(file)) return next();
|
|
1729
|
+
if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(import_fs5.default.readFileSync(file, "utf8"), `${import_path5.default.basename(file)}?original`));
|
|
1639
1730
|
res.type("json").send(sourceMaps.get(file));
|
|
1640
1731
|
});
|
|
1641
1732
|
app.use("/__baselib__", import_express2.default.static(this.baseLib.dir, {
|
|
1642
1733
|
maxAge: "1d",
|
|
1643
1734
|
setHeaders: (res, file) => {
|
|
1644
|
-
if (file.endsWith(".js")) res.setHeader("SourceMap", `${
|
|
1735
|
+
if (file.endsWith(".js")) res.setHeader("SourceMap", `${import_path5.default.basename(file)}.map`);
|
|
1645
1736
|
}
|
|
1646
1737
|
}));
|
|
1647
1738
|
app.use(PROXY_PREFIX, import_express2.default.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
@@ -1701,7 +1792,7 @@ ${error.message}`);
|
|
|
1701
1792
|
return;
|
|
1702
1793
|
}
|
|
1703
1794
|
const data = node.data;
|
|
1704
|
-
res.type(
|
|
1795
|
+
res.type(import_path5.default.extname(req.params[0]));
|
|
1705
1796
|
res.setHeader("ETag", `"${node.etag}"`);
|
|
1706
1797
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1707
1798
|
const ranges = req.range(data.length);
|
|
@@ -1771,11 +1862,11 @@ ${error.message}`);
|
|
|
1771
1862
|
runtimeDir() {
|
|
1772
1863
|
const candidates = [
|
|
1773
1864
|
this.options.runtimeDir,
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1865
|
+
import_path5.default.join(__dirname, "runtime"),
|
|
1866
|
+
import_path5.default.join(__dirname, "../runtime"),
|
|
1867
|
+
import_path5.default.join(__dirname, "../../dist/runtime")
|
|
1777
1868
|
].filter((dir) => !!dir);
|
|
1778
|
-
const found = candidates.find((dir) =>
|
|
1869
|
+
const found = candidates.find((dir) => import_fs5.default.existsSync(import_path5.default.join(dir, "host.js")));
|
|
1779
1870
|
if (!found) {
|
|
1780
1871
|
throw new Error("\u627E\u4E0D\u5230\u6D4F\u89C8\u5668\u7AEF\u8FD0\u884C\u65F6\uFF08host.js\uFF09\uFF0C\u8BF7\u5148\u6267\u884C pnpm build");
|
|
1781
1872
|
}
|
|
@@ -1788,7 +1879,7 @@ ${error.message}`);
|
|
|
1788
1879
|
startWatching() {
|
|
1789
1880
|
let lastBuildFailed = false;
|
|
1790
1881
|
const watched = [this.project.root];
|
|
1791
|
-
if (this.setupFile &&
|
|
1882
|
+
if (this.setupFile && import_path5.default.relative(this.project.root, this.setupFile).startsWith("..")) watched.push(this.setupFile);
|
|
1792
1883
|
this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {
|
|
1793
1884
|
const reloadOnly = structural || lastBuildFailed;
|
|
1794
1885
|
this.builder.invalidate(files);
|
|
@@ -1819,7 +1910,7 @@ var DEFAULT_PORT = 3e3;
|
|
|
1819
1910
|
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1820
1911
|
async function listen(server, port, host, logger) {
|
|
1821
1912
|
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1822
|
-
const taken =
|
|
1913
|
+
const taken = await takenOnLocalAddresses(candidate) || !await bind(server, candidate, host);
|
|
1823
1914
|
if (!taken) {
|
|
1824
1915
|
const actual = server.address().port;
|
|
1825
1916
|
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
@@ -1838,8 +1929,8 @@ function bind(server, port, host) {
|
|
|
1838
1929
|
});
|
|
1839
1930
|
});
|
|
1840
1931
|
}
|
|
1841
|
-
async function
|
|
1842
|
-
for (const address of ["127.0.0.1", "::1"]) {
|
|
1932
|
+
async function takenOnLocalAddresses(port) {
|
|
1933
|
+
for (const address of ["127.0.0.1", "::1", "0.0.0.0", "::"]) {
|
|
1843
1934
|
const probe = import_net.default.createServer();
|
|
1844
1935
|
const free = await bind(probe, port, address).catch(() => true);
|
|
1845
1936
|
if (!free) return true;
|
|
@@ -1847,11 +1938,21 @@ async function takenOnLoopback(port) {
|
|
|
1847
1938
|
}
|
|
1848
1939
|
return false;
|
|
1849
1940
|
}
|
|
1941
|
+
function assertMountPath(mountPath) {
|
|
1942
|
+
const literal = typeof mountPath === "string" && /^(\/[\w.~-]+)+$/.test(mountPath);
|
|
1943
|
+
const lower = literal ? mountPath.toLowerCase() : "";
|
|
1944
|
+
const reserved = RESERVED_PREFIXES.some((prefix) => lower === prefix || lower.startsWith(prefix + "/"));
|
|
1945
|
+
if (!literal || reserved) {
|
|
1946
|
+
throw new Error(
|
|
1947
|
+
`\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`
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1850
1951
|
function resolveSetupFile(option, projectPath) {
|
|
1851
1952
|
if (option === false) return null;
|
|
1852
1953
|
if (!option) return findSetupFile(projectPath);
|
|
1853
|
-
const file =
|
|
1854
|
-
if (!
|
|
1954
|
+
const file = import_path5.default.resolve(option);
|
|
1955
|
+
if (!import_fs5.default.existsSync(file)) throw new Error(`\u627E\u4E0D\u5230 setup \u6587\u4EF6\uFF1A${file}`);
|
|
1855
1956
|
return file;
|
|
1856
1957
|
}
|
|
1857
1958
|
// Annotate the CommonJS export names for ESM import in node:
|