@open-wxa/wxa-runner-core 0.9.0 → 0.10.1
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 +34 -2
- package/dist/index.d.ts +34 -2
- package/dist/index.js +373 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +373 -166
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/frame.js +90 -77
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/host.js +81 -48
- package/dist/runtime/host.js.map +1 -1
- package/package.json +6 -3
package/dist/index.mjs
CHANGED
|
@@ -13,6 +13,47 @@ var FileStore = class {
|
|
|
13
13
|
this.etagPrefix = randomUUID();
|
|
14
14
|
this.version = 0;
|
|
15
15
|
this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" }]]);
|
|
16
|
+
this.operations = {
|
|
17
|
+
readFile: ({ filePath, encoding }) => {
|
|
18
|
+
const data = this.read(filePath);
|
|
19
|
+
return encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(encoding) };
|
|
20
|
+
},
|
|
21
|
+
writeFile: (args) => this.writeEncoded(args, false),
|
|
22
|
+
appendFile: (args) => this.writeEncoded(args, true),
|
|
23
|
+
copyFile: ({ srcPath, destPath }) => {
|
|
24
|
+
this.write(destPath, this.read(srcPath));
|
|
25
|
+
return {};
|
|
26
|
+
},
|
|
27
|
+
access: ({ path: path7 }) => {
|
|
28
|
+
this.existing(path7);
|
|
29
|
+
return {};
|
|
30
|
+
},
|
|
31
|
+
stat: ({ path: path7 }) => {
|
|
32
|
+
const node = this.existing(path7);
|
|
33
|
+
const stats = {
|
|
34
|
+
mode: node.dir ? 16877 : 33188,
|
|
35
|
+
size: node.data.length,
|
|
36
|
+
lastAccessedTime: Math.floor(node.mtime / 1e3),
|
|
37
|
+
lastModifiedTime: Math.floor(node.mtime / 1e3)
|
|
38
|
+
};
|
|
39
|
+
return { stats, ...stats };
|
|
40
|
+
},
|
|
41
|
+
getFileInfo: ({ filePath }) => ({ size: this.read(filePath).length }),
|
|
42
|
+
mkdir: ({ dirPath, recursive }) => {
|
|
43
|
+
this.mkdir(dirPath, !!recursive);
|
|
44
|
+
return {};
|
|
45
|
+
},
|
|
46
|
+
readdir: ({ dirPath }) => ({ files: this.readdir(dirPath) }),
|
|
47
|
+
unlink: ({ filePath }) => {
|
|
48
|
+
this.remove(filePath, { dir: false });
|
|
49
|
+
return {};
|
|
50
|
+
},
|
|
51
|
+
rmdir: ({ dirPath, recursive }) => {
|
|
52
|
+
this.assertNotRoot(dirPath);
|
|
53
|
+
this.remove(dirPath, { dir: true, recursive: !!recursive });
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
16
57
|
this.roots = roots.map((root) => this.normalize(root));
|
|
17
58
|
for (const root of this.roots) this.mkdir(root, true);
|
|
18
59
|
}
|
|
@@ -73,66 +114,37 @@ var FileStore = class {
|
|
|
73
114
|
}
|
|
74
115
|
this.nodes.delete(target);
|
|
75
116
|
}
|
|
117
|
+
/** 文件 API 经开发服务器调用的操作,参数和返回值的字段名与基础库一致 */
|
|
76
118
|
execute(operation, args) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!node) throw new Error(`no such file or directory ${args.path}`);
|
|
97
|
-
const stats = {
|
|
98
|
-
mode: node.dir ? 16877 : 33188,
|
|
99
|
-
size: node.data.length,
|
|
100
|
-
lastAccessedTime: Math.floor(node.mtime / 1e3),
|
|
101
|
-
lastModifiedTime: Math.floor(node.mtime / 1e3)
|
|
102
|
-
};
|
|
103
|
-
return { stats, ...stats };
|
|
104
|
-
}
|
|
105
|
-
case "getFileInfo":
|
|
106
|
-
return { size: this.read(args.filePath).length };
|
|
107
|
-
case "mkdir":
|
|
108
|
-
this.mkdir(args.dirPath, !!args.recursive);
|
|
109
|
-
return {};
|
|
110
|
-
case "readdir":
|
|
111
|
-
return { files: this.readdir(args.dirPath) };
|
|
112
|
-
case "unlink":
|
|
113
|
-
this.remove(args.filePath, { dir: false });
|
|
114
|
-
return {};
|
|
115
|
-
case "rmdir": {
|
|
116
|
-
const target = this.normalize(args.dirPath);
|
|
117
|
-
const prefix = target === "/" ? "/" : target + "/";
|
|
118
|
-
if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
|
|
119
|
-
throw new Error(`operation not permitted ${args.dirPath}`);
|
|
120
|
-
}
|
|
121
|
-
this.remove(args.dirPath, { dir: true, recursive: !!args.recursive });
|
|
122
|
-
return {};
|
|
123
|
-
}
|
|
124
|
-
default:
|
|
125
|
-
throw new Error(`unsupported filesystem operation ${operation}`);
|
|
119
|
+
if (!Object.prototype.hasOwnProperty.call(this.operations, operation)) {
|
|
120
|
+
throw new Error(`unsupported filesystem operation ${operation}`);
|
|
121
|
+
}
|
|
122
|
+
return this.operations[operation](args);
|
|
123
|
+
}
|
|
124
|
+
writeEncoded(args, append) {
|
|
125
|
+
this.write(args.filePath, Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8"), append);
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
existing(filePath) {
|
|
129
|
+
const node = this.stat(filePath);
|
|
130
|
+
if (!node) throw new Error(`no such file or directory ${filePath}`);
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
assertNotRoot(dirPath) {
|
|
134
|
+
const target = this.normalize(dirPath);
|
|
135
|
+
const prefix = target === "/" ? "/" : target + "/";
|
|
136
|
+
if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
|
|
137
|
+
throw new Error(`operation not permitted ${dirPath}`);
|
|
126
138
|
}
|
|
127
139
|
}
|
|
128
140
|
};
|
|
129
141
|
|
|
130
142
|
// src/server/DevServer.ts
|
|
131
|
-
import
|
|
143
|
+
import fs6 from "fs";
|
|
132
144
|
import http from "http";
|
|
133
145
|
import net from "net";
|
|
134
|
-
import
|
|
135
|
-
import
|
|
146
|
+
import os3 from "os";
|
|
147
|
+
import path6 from "path";
|
|
136
148
|
import express2 from "express";
|
|
137
149
|
import { WebSocket as WebSocket2, WebSocketServer } from "ws";
|
|
138
150
|
import { readRegionData, resolveBaseLib, usesIsolatedContext } from "@open-wxa/wxa-runner-extractor";
|
|
@@ -141,6 +153,164 @@ import { readRegionData, resolveBaseLib, usesIsolatedContext } from "@open-wxa/w
|
|
|
141
153
|
import { compileWxml, compileWxss } from "@open-wxa/wxa-runner-compiler";
|
|
142
154
|
import { transformSync } from "esbuild";
|
|
143
155
|
|
|
156
|
+
// src/project/StyleMirror.ts
|
|
157
|
+
import fs from "fs";
|
|
158
|
+
import { createRequire } from "module";
|
|
159
|
+
import os from "os";
|
|
160
|
+
import path from "path";
|
|
161
|
+
import { pathToFileURL } from "url";
|
|
162
|
+
var PLUGIN_EXTENSIONS = {
|
|
163
|
+
less: [".less"],
|
|
164
|
+
sass: [".sass", ".scss"]
|
|
165
|
+
};
|
|
166
|
+
var IMPORT_WXSS = /(?:^|\s)?(?:@import)(?:\s*)?(["'])([^"']+.wxss)\1(?:\s*);/g;
|
|
167
|
+
function readStyleSetting(setting) {
|
|
168
|
+
const plugins = [];
|
|
169
|
+
let lessGlobalVars = {};
|
|
170
|
+
for (const item of Array.isArray(setting?.useCompilerPlugins) ? setting.useCompilerPlugins : []) {
|
|
171
|
+
const [name, options] = Array.isArray(item) ? item : [item, void 0];
|
|
172
|
+
if (name !== "less" && name !== "sass" || plugins.includes(name)) continue;
|
|
173
|
+
plugins.push(name);
|
|
174
|
+
if (name === "less" && options?.globalVars) lessGlobalVars = options.globalVars;
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
plugins,
|
|
178
|
+
lessGlobalVars,
|
|
179
|
+
lessCommonFile: setting?.lessSetting?.commonUseFilePath || "app.less",
|
|
180
|
+
sassCommonFile: setting?.sassSetting?.sassCommonUseFilePath || "global.sass",
|
|
181
|
+
scssCommonFile: setting?.sassSetting?.scssCommonUseFilePath || "global.scss"
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function ignoredStyleSources(project, setting) {
|
|
185
|
+
return Object.keys(PLUGIN_EXTENSIONS).filter((plugin) => !setting.plugins.includes(plugin)).map((plugin) => ({ plugin, files: project.listFiles(PLUGIN_EXTENSIONS[plugin]) })).filter(({ files }) => files.length > 0);
|
|
186
|
+
}
|
|
187
|
+
var StyleMirror = class {
|
|
188
|
+
constructor(setting) {
|
|
189
|
+
this.setting = setting;
|
|
190
|
+
this.dir = null;
|
|
191
|
+
/** 已写入的 .wxss 与写入的内容 */
|
|
192
|
+
this.files = /* @__PURE__ */ new Map();
|
|
193
|
+
this.compilers = /* @__PURE__ */ new Map();
|
|
194
|
+
}
|
|
195
|
+
/** 镜像目录;还没同步过时为 null */
|
|
196
|
+
get directory() {
|
|
197
|
+
return this.dir;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 编译全部预处理文件,与原有的 .wxss 一起写进镜像目录。
|
|
201
|
+
* @returns 镜像目录,以及其中全部 .wxss 的相对路径
|
|
202
|
+
*/
|
|
203
|
+
async sync(project) {
|
|
204
|
+
const sources = this.pickSources(project);
|
|
205
|
+
const outputs = await Promise.all(
|
|
206
|
+
[...sources].map(async ([target, source]) => [target, await this.compile(project, source)])
|
|
207
|
+
);
|
|
208
|
+
const dir = this.dir ??= fs.mkdtempSync(path.join(os.tmpdir(), "wxa-runner-wxss-"));
|
|
209
|
+
for (const rel of [...this.files.keys()]) {
|
|
210
|
+
if (sources.has(rel)) continue;
|
|
211
|
+
fs.rmSync(path.join(dir, rel), { force: true });
|
|
212
|
+
this.files.delete(rel);
|
|
213
|
+
}
|
|
214
|
+
for (const [rel, css] of outputs) {
|
|
215
|
+
if (this.files.get(rel) === css) continue;
|
|
216
|
+
const target = path.join(dir, rel);
|
|
217
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
218
|
+
fs.writeFileSync(target, css);
|
|
219
|
+
this.files.set(rel, css);
|
|
220
|
+
}
|
|
221
|
+
return { cwd: dir, files: [...sources.keys()] };
|
|
222
|
+
}
|
|
223
|
+
dispose() {
|
|
224
|
+
if (this.dir) fs.rmSync(this.dir, { recursive: true, force: true });
|
|
225
|
+
this.dir = null;
|
|
226
|
+
this.files.clear();
|
|
227
|
+
}
|
|
228
|
+
/** 每个 .wxss 由哪个源文件生成:键为 .wxss 的相对路径 */
|
|
229
|
+
pickSources(project) {
|
|
230
|
+
const extensions = [".wxss", ...this.setting.plugins.flatMap((plugin) => PLUGIN_EXTENSIONS[plugin])];
|
|
231
|
+
const sources = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const file of project.listFiles(extensions)) {
|
|
233
|
+
const ext = path.extname(file);
|
|
234
|
+
const target = file.slice(0, -ext.length) + ".wxss";
|
|
235
|
+
const current = sources.get(target);
|
|
236
|
+
if (!current || extensions.indexOf(ext) > extensions.indexOf(path.extname(current))) sources.set(target, file);
|
|
237
|
+
}
|
|
238
|
+
return sources;
|
|
239
|
+
}
|
|
240
|
+
async compile(project, file) {
|
|
241
|
+
const ext = path.extname(file);
|
|
242
|
+
if (ext === ".wxss") return project.readText(file);
|
|
243
|
+
const compiler = await this.loadCompiler(ext === ".less" ? "less" : "sass", project);
|
|
244
|
+
try {
|
|
245
|
+
return ext === ".less" ? await this.compileLess(compiler, project, file) : this.compileSass(compiler, project, file);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
throw new Error(`${file} \u7F16\u8BD1\u5931\u8D25\uFF1A${describeError(project, error)}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async compileLess(less, project, file) {
|
|
251
|
+
const common = this.setting.lessCommonFile;
|
|
252
|
+
const prefix = Object.entries(this.setting.lessGlobalVars).map(([name, value]) => `@${name.replace(/^@/, "")}: ${value};`);
|
|
253
|
+
if (file !== common && project.exists(common)) prefix.push(`@import (optional, reference) '${toRoot(file)}/${common}';`);
|
|
254
|
+
let code = prefix.map((statement) => statement + " ").join("") + project.readText(file);
|
|
255
|
+
code = code.replace(IMPORT_WXSS, (statement, _quote, target) => statement.replace(target, `${target}?css`));
|
|
256
|
+
const { css } = await less.render(code, {
|
|
257
|
+
filename: path.join(project.root, file),
|
|
258
|
+
paths: [project.root]
|
|
259
|
+
});
|
|
260
|
+
return css.replace(/(@import\s*["'][^"']+\.wxss)\?css(["'])/g, "$1$2");
|
|
261
|
+
}
|
|
262
|
+
compileSass(sass, project, file) {
|
|
263
|
+
const indented = file.endsWith(".sass");
|
|
264
|
+
const preferred = indented ? this.setting.sassCommonFile : this.setting.scssCommonFile;
|
|
265
|
+
const fallback = indented ? this.setting.scssCommonFile : this.setting.sassCommonFile;
|
|
266
|
+
const common = project.exists(preferred) ? preferred : project.exists(fallback) ? fallback : null;
|
|
267
|
+
let code = project.readText(file);
|
|
268
|
+
if (common && file !== preferred && file !== fallback) {
|
|
269
|
+
code = `@use '${toRoot(file)}/${common}'${indented ? "\n" : "; "}${code}`;
|
|
270
|
+
}
|
|
271
|
+
const imported = /* @__PURE__ */ new Set();
|
|
272
|
+
code = code.replace(IMPORT_WXSS, (statement, _quote, target) => {
|
|
273
|
+
imported.add(target);
|
|
274
|
+
return statement.replace(target, `${target.slice(0, -".wxss".length)}.css`);
|
|
275
|
+
});
|
|
276
|
+
const css = typeof sass.compileString === "function" ? sass.compileString(code, { url: pathToFileURL(path.join(project.root, file)), syntax: indented ? "indented" : "scss", loadPaths: [project.root] }).css : sass.renderSync({ data: code, file: path.join(project.root, file), indentedSyntax: indented, includePaths: [project.root] }).css.toString();
|
|
277
|
+
return css.replace(
|
|
278
|
+
/(@import\s*["'])([^"']+)\.css(["'])/g,
|
|
279
|
+
(statement, head, target, tail) => imported.has(`${target}.wxss`) ? `${head}${target}.wxss${tail}` : statement
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
/** 用到时才加载:先从小程序根目录和项目目录向上查找 node_modules,找不到时用 wxa-runner 自带的 */
|
|
283
|
+
loadCompiler(name, project) {
|
|
284
|
+
let loading = this.compilers.get(name);
|
|
285
|
+
if (!loading) {
|
|
286
|
+
loading = importCompiler(name, project);
|
|
287
|
+
this.compilers.set(name, loading);
|
|
288
|
+
}
|
|
289
|
+
return loading;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
async function importCompiler(name, project) {
|
|
293
|
+
let resolved = null;
|
|
294
|
+
try {
|
|
295
|
+
resolved = createRequire(path.join(project.root, "package.json")).resolve(name, { paths: [project.root, project.projectPath] });
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
const module = resolved ? await import(pathToFileURL(resolved).href) : name === "less" ? await import("less") : await import("sass");
|
|
299
|
+
const entry = name === "less" ? "render" : "renderSync";
|
|
300
|
+
return typeof module[entry] === "function" ? module : module.default;
|
|
301
|
+
}
|
|
302
|
+
function toRoot(file) {
|
|
303
|
+
const dir = path.posix.dirname(file);
|
|
304
|
+
return dir === "." ? "." : dir.split("/").map(() => "..").join("/");
|
|
305
|
+
}
|
|
306
|
+
function describeError(project, error) {
|
|
307
|
+
if (typeof error?.line === "number" && error.filename) {
|
|
308
|
+
const file = path.relative(project.root, error.filename).split(path.sep).join("/");
|
|
309
|
+
return `${file}:${error.line}:${(error.column ?? 0) + 1} ${error.message}`;
|
|
310
|
+
}
|
|
311
|
+
return error?.message ?? String(error);
|
|
312
|
+
}
|
|
313
|
+
|
|
144
314
|
// src/project/wxConfig.ts
|
|
145
315
|
var DEFAULT_WINDOW = {
|
|
146
316
|
navigationBarBackgroundColor: "#000000",
|
|
@@ -198,9 +368,9 @@ function createWxConfig(input) {
|
|
|
198
368
|
}
|
|
199
369
|
|
|
200
370
|
// src/project/WxmlMirror.ts
|
|
201
|
-
import
|
|
202
|
-
import
|
|
203
|
-
import
|
|
371
|
+
import fs2 from "fs";
|
|
372
|
+
import os2 from "os";
|
|
373
|
+
import path2 from "path";
|
|
204
374
|
var WxmlMirror = class {
|
|
205
375
|
constructor(transform) {
|
|
206
376
|
this.transform = transform;
|
|
@@ -217,25 +387,25 @@ var WxmlMirror = class {
|
|
|
217
387
|
* 只重写源内容变化的文件;项目里已删除的文件同时从镜像目录删除,被引用时 wcc 照常报错
|
|
218
388
|
*/
|
|
219
389
|
sync(project, relPaths) {
|
|
220
|
-
const dir = this.dir ??=
|
|
390
|
+
const dir = this.dir ??= fs2.mkdtempSync(path2.join(os2.tmpdir(), "wxa-runner-wxml-"));
|
|
221
391
|
const current = new Set(relPaths);
|
|
222
392
|
for (const rel of [...this.files.keys()]) {
|
|
223
393
|
if (current.has(rel)) continue;
|
|
224
|
-
|
|
394
|
+
fs2.rmSync(path2.join(dir, rel), { force: true });
|
|
225
395
|
this.files.delete(rel);
|
|
226
396
|
}
|
|
227
397
|
for (const rel of relPaths) {
|
|
228
398
|
const source = project.readText(rel);
|
|
229
399
|
if (this.files.get(rel) === source) continue;
|
|
230
|
-
const target =
|
|
231
|
-
|
|
232
|
-
|
|
400
|
+
const target = path2.join(dir, rel);
|
|
401
|
+
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
402
|
+
fs2.writeFileSync(target, rel.endsWith(".wxml") ? this.transform(source, rel) : source);
|
|
233
403
|
this.files.set(rel, source);
|
|
234
404
|
}
|
|
235
405
|
return dir;
|
|
236
406
|
}
|
|
237
407
|
dispose() {
|
|
238
|
-
if (this.dir)
|
|
408
|
+
if (this.dir) fs2.rmSync(this.dir, { recursive: true, force: true });
|
|
239
409
|
this.dir = null;
|
|
240
410
|
this.files.clear();
|
|
241
411
|
}
|
|
@@ -277,15 +447,18 @@ var AppBuilder = class {
|
|
|
277
447
|
this.dirty = /* @__PURE__ */ new Set(["config", "wxml", "wxss", "js"]);
|
|
278
448
|
this.wxmlCache = null;
|
|
279
449
|
this.wxssCache = null;
|
|
450
|
+
/** 开启了 less / sass 编译插件时才有;插件设置变了就换一个 */
|
|
451
|
+
this.styleMirror = null;
|
|
280
452
|
this.wxmlMirror = options.wxmlTransform ? new WxmlMirror(options.wxmlTransform) : null;
|
|
281
453
|
}
|
|
282
454
|
/** wxmlTransform 的镜像目录;没有设置转换或还没编译过时为 null */
|
|
283
455
|
get wxmlMirrorDir() {
|
|
284
456
|
return this.wxmlMirror?.directory ?? null;
|
|
285
457
|
}
|
|
286
|
-
/** 删除 wxmlTransform
|
|
458
|
+
/** 删除 wxmlTransform 和样式预处理的镜像目录,开发服务器关闭时调用 */
|
|
287
459
|
dispose() {
|
|
288
460
|
this.wxmlMirror?.dispose();
|
|
461
|
+
this.styleMirror?.mirror.dispose();
|
|
289
462
|
}
|
|
290
463
|
/** 根据变化的文件标记需要重新构建的部分;返回 true 表示需要刷新 */
|
|
291
464
|
invalidate(changedFiles = []) {
|
|
@@ -419,14 +592,26 @@ ${pageStyles}`,
|
|
|
419
592
|
}
|
|
420
593
|
/** 与开发者工具一致,项目里的 wxss 全部交给 wcsc:被 @import 的文件(如 uni-app 的 common/main.wxss)也要在列表里 */
|
|
421
594
|
async compileStyles(units) {
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
const
|
|
595
|
+
const mirror = this.currentStyleMirror();
|
|
596
|
+
const { cwd, files } = mirror ? await mirror.sync(this.project) : { cwd: this.project.root, files: this.project.listFiles([".wxss"]) };
|
|
597
|
+
const styles = new Set(files);
|
|
598
|
+
const pageFiles = units.filter((id) => styles.has(id + ".wxss")).map((id) => `./${id}.wxss`);
|
|
599
|
+
const commonFiles = files.map((file) => `./${file}`).filter((file) => !pageFiles.includes(file));
|
|
600
|
+
const { comm, pages } = await compileWxss({ cwd, pageFiles, commonFiles });
|
|
425
601
|
return {
|
|
426
602
|
comm: rewriteSafeAreaInsets(comm),
|
|
427
603
|
pages: Object.fromEntries(Object.entries(pages).map(([file, css]) => [file, rewriteSafeAreaInsets(css)]))
|
|
428
604
|
};
|
|
429
605
|
}
|
|
606
|
+
currentStyleMirror() {
|
|
607
|
+
const setting = this.project.styleSetting;
|
|
608
|
+
const key = JSON.stringify(setting);
|
|
609
|
+
if (this.styleMirror?.setting !== key) {
|
|
610
|
+
this.styleMirror?.mirror.dispose();
|
|
611
|
+
this.styleMirror = setting.plugins.length > 0 ? { setting: key, mirror: new StyleMirror(setting) } : null;
|
|
612
|
+
}
|
|
613
|
+
return this.styleMirror?.mirror ?? null;
|
|
614
|
+
}
|
|
430
615
|
/** $gwx 加上各页面、组件的 json / wxml 注册;逻辑层用裁剪版 $gwx,渲染层用完整版 */
|
|
431
616
|
createTemplates(app, units, unitJson, gwx) {
|
|
432
617
|
const lines = [gwx, `__wxAppCode__["app.json"] = ${toJs(app)};`];
|
|
@@ -471,28 +656,32 @@ function toJs(value) {
|
|
|
471
656
|
function classifyFile(file) {
|
|
472
657
|
if (file.endsWith(".json")) return "config";
|
|
473
658
|
if (file.endsWith(".wxml") || file.endsWith(".wxs")) return "wxml";
|
|
474
|
-
if (
|
|
659
|
+
if (/\.(wxss|less|scss|sass|css)$/.test(file)) return "wxss";
|
|
475
660
|
if (file.endsWith(".js")) return "js";
|
|
476
661
|
return null;
|
|
477
662
|
}
|
|
478
663
|
|
|
479
664
|
// src/project/MiniProgram.ts
|
|
480
|
-
import
|
|
481
|
-
import
|
|
665
|
+
import fs3 from "fs";
|
|
666
|
+
import path3 from "path";
|
|
482
667
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".idea", ".vscode"]);
|
|
483
668
|
var PROJECT_FILES = /* @__PURE__ */ new Set(["project.config.json", "project.private.config.json", "sitemap.json", "app.json", "wxa.setup.js"]);
|
|
484
669
|
var MiniProgram = class {
|
|
485
670
|
constructor(projectPath) {
|
|
486
671
|
this.projectPath = projectPath;
|
|
487
|
-
const projectConfig = readJsonFile(
|
|
488
|
-
this.root =
|
|
672
|
+
const projectConfig = readJsonFile(path3.join(projectPath, "project.config.json")) || {};
|
|
673
|
+
this.root = path3.resolve(projectPath, projectConfig.miniprogramRoot || "");
|
|
489
674
|
this.appid = projectConfig.appid || "touristappid";
|
|
490
|
-
if (!
|
|
675
|
+
if (!fs3.existsSync(path3.join(this.root, "app.json"))) {
|
|
491
676
|
throw new Error(`${this.root} \u4E0B\u6CA1\u6709 app.json\uFF0C\u4E0D\u662F\u6709\u6548\u7684\u5C0F\u7A0B\u5E8F\u76EE\u5F55`);
|
|
492
677
|
}
|
|
493
678
|
}
|
|
679
|
+
/** project.config.json 里与 less / sass 编译插件有关的设置;每次重新读取,开发中修改了也能生效 */
|
|
680
|
+
get styleSetting() {
|
|
681
|
+
return readStyleSetting(readJsonFile(path3.join(this.projectPath, "project.config.json"))?.setting);
|
|
682
|
+
}
|
|
494
683
|
readAppJson() {
|
|
495
|
-
const app = readJsonFile(
|
|
684
|
+
const app = readJsonFile(path3.join(this.root, "app.json"));
|
|
496
685
|
if (!app || !Array.isArray(app.pages) || app.pages.length === 0) {
|
|
497
686
|
throw new Error("app.json \u7F3A\u5C11 pages \u914D\u7F6E");
|
|
498
687
|
}
|
|
@@ -504,14 +693,14 @@ var MiniProgram = class {
|
|
|
504
693
|
/** Worker 代码所在目录,不带首尾的 /;没有配置 workers 时为 null */
|
|
505
694
|
workersDir(app) {
|
|
506
695
|
const dir = typeof app.workers === "string" ? app.workers : app.workers?.path;
|
|
507
|
-
return dir ?
|
|
696
|
+
return dir ? path3.posix.normalize(dir).replace(/^\/+|\/+$/g, "") : null;
|
|
508
697
|
}
|
|
509
698
|
/** 主包页面 + 分包页面(带 root 前缀) */
|
|
510
699
|
allPages(app) {
|
|
511
700
|
const pages = [...app.pages];
|
|
512
701
|
for (const pkg of this.subPackages(app)) {
|
|
513
702
|
for (const page of pkg.pages) {
|
|
514
|
-
const full =
|
|
703
|
+
const full = path3.posix.join(pkg.root, page);
|
|
515
704
|
if (!pages.includes(full)) pages.push(full);
|
|
516
705
|
}
|
|
517
706
|
}
|
|
@@ -523,7 +712,7 @@ var MiniProgram = class {
|
|
|
523
712
|
* 所以能解析到的引用都换成组件的绝对路径
|
|
524
713
|
*/
|
|
525
714
|
readUnitJson(id, app) {
|
|
526
|
-
const json = { ...readJsonFile(
|
|
715
|
+
const json = { ...readJsonFile(path3.join(this.root, id + ".json")) };
|
|
527
716
|
const resolve = (fromId, ref) => {
|
|
528
717
|
const resolved = this.resolveComponent(fromId, ref);
|
|
529
718
|
return resolved ? "/" + resolved : null;
|
|
@@ -544,7 +733,7 @@ var MiniProgram = class {
|
|
|
544
733
|
if (resolved || ref.startsWith("/") || /^plugin(-private)?:\/\//.test(ref)) {
|
|
545
734
|
json.usingComponents[tag] = resolved ?? ref;
|
|
546
735
|
} else {
|
|
547
|
-
const relative =
|
|
736
|
+
const relative = path3.posix.relative(path3.posix.dirname(id), ref);
|
|
548
737
|
json.usingComponents[tag] = relative.startsWith(".") ? relative : "./" + relative;
|
|
549
738
|
}
|
|
550
739
|
}
|
|
@@ -557,25 +746,25 @@ var MiniProgram = class {
|
|
|
557
746
|
*/
|
|
558
747
|
resolveComponent(fromId, ref) {
|
|
559
748
|
if (!ref || ref.startsWith("plugin://") || ref.startsWith("plugin-private://")) return null;
|
|
560
|
-
const fromDir =
|
|
749
|
+
const fromDir = path3.posix.dirname(fromId);
|
|
561
750
|
const candidates = [];
|
|
562
751
|
if (ref.startsWith("/")) {
|
|
563
752
|
candidates.push(ref.slice(1));
|
|
564
753
|
} else {
|
|
565
|
-
candidates.push(
|
|
754
|
+
candidates.push(path3.posix.join(fromDir, ref));
|
|
566
755
|
if (!ref.startsWith(".")) {
|
|
567
756
|
let dir = fromDir;
|
|
568
757
|
for (; ; ) {
|
|
569
|
-
candidates.push(
|
|
758
|
+
candidates.push(path3.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
|
|
570
759
|
if (dir === "." || dir === "") break;
|
|
571
|
-
dir =
|
|
760
|
+
dir = path3.posix.dirname(dir);
|
|
572
761
|
}
|
|
573
762
|
}
|
|
574
763
|
}
|
|
575
764
|
for (const candidate of candidates) {
|
|
576
|
-
const normalized =
|
|
577
|
-
for (const id of [normalized,
|
|
578
|
-
if (
|
|
765
|
+
const normalized = path3.posix.normalize(candidate);
|
|
766
|
+
for (const id of [normalized, path3.posix.join(normalized, "index")]) {
|
|
767
|
+
if (fs3.existsSync(path3.join(this.root, id + ".json"))) return id;
|
|
579
768
|
}
|
|
580
769
|
}
|
|
581
770
|
return null;
|
|
@@ -607,11 +796,11 @@ var MiniProgram = class {
|
|
|
607
796
|
listFiles(extensions) {
|
|
608
797
|
const result = [];
|
|
609
798
|
const walk = (dir, rel) => {
|
|
610
|
-
for (const entry of
|
|
799
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
611
800
|
if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
|
|
612
801
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
613
802
|
if (entry.isDirectory()) {
|
|
614
|
-
walk(
|
|
803
|
+
walk(path3.join(dir, entry.name), childRel);
|
|
615
804
|
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
616
805
|
if (!rel && PROJECT_FILES.has(entry.name)) continue;
|
|
617
806
|
result.push(childRel);
|
|
@@ -622,18 +811,18 @@ var MiniProgram = class {
|
|
|
622
811
|
return result.sort();
|
|
623
812
|
}
|
|
624
813
|
exists(relPath) {
|
|
625
|
-
return
|
|
814
|
+
return fs3.existsSync(path3.join(this.root, relPath));
|
|
626
815
|
}
|
|
627
816
|
readText(relPath) {
|
|
628
|
-
return
|
|
817
|
+
return fs3.readFileSync(path3.join(this.root, relPath), "utf8");
|
|
629
818
|
}
|
|
630
819
|
isInIndependentSubPackage(id, app) {
|
|
631
820
|
return this.subPackages(app).some((pkg) => pkg.independent && id.startsWith(pkg.root.replace(/\/?$/, "/")));
|
|
632
821
|
}
|
|
633
822
|
};
|
|
634
823
|
function readJsonFile(file) {
|
|
635
|
-
if (!
|
|
636
|
-
const text =
|
|
824
|
+
if (!fs3.existsSync(file)) return null;
|
|
825
|
+
const text = fs3.readFileSync(file, "utf8");
|
|
637
826
|
try {
|
|
638
827
|
return JSON.parse(text);
|
|
639
828
|
} catch (error) {
|
|
@@ -699,9 +888,9 @@ var WechatDevClient = class {
|
|
|
699
888
|
*
|
|
700
889
|
* @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
|
|
701
890
|
*/
|
|
702
|
-
async post(
|
|
891
|
+
async post(path7, body, timeout = DEFAULT_TIMEOUT) {
|
|
703
892
|
const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
|
|
704
|
-
const response = await fetch(`${BASE_URL}/${
|
|
893
|
+
const response = await fetch(`${BASE_URL}/${path7}?${query}`, {
|
|
705
894
|
method: "POST",
|
|
706
895
|
headers: { "Content-Type": "application/json" },
|
|
707
896
|
body: JSON.stringify(body),
|
|
@@ -713,8 +902,8 @@ var WechatDevClient = class {
|
|
|
713
902
|
return response;
|
|
714
903
|
}
|
|
715
904
|
/** 同 post,返回解析后的 JSON */
|
|
716
|
-
async postJson(
|
|
717
|
-
return (await this.post(
|
|
905
|
+
async postJson(path7, body) {
|
|
906
|
+
return (await this.post(path7, body)).json();
|
|
718
907
|
}
|
|
719
908
|
};
|
|
720
909
|
|
|
@@ -1076,28 +1265,9 @@ var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "con
|
|
|
1076
1265
|
async function handleProxy(req, res) {
|
|
1077
1266
|
const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
|
|
1078
1267
|
if (!target) {
|
|
1079
|
-
res
|
|
1268
|
+
sendProxyError(res, 400, `invalid url "${req.originalUrl}"`);
|
|
1080
1269
|
return;
|
|
1081
1270
|
}
|
|
1082
|
-
let requestHeaders = {};
|
|
1083
|
-
try {
|
|
1084
|
-
requestHeaders = JSON.parse(req.get("x-wxa-headers") || "{}");
|
|
1085
|
-
} catch {
|
|
1086
|
-
}
|
|
1087
|
-
const headers = {};
|
|
1088
|
-
for (const [key, value] of Object.entries(requestHeaders)) {
|
|
1089
|
-
if (!DROPPED_REQUEST_HEADERS.has(key.toLowerCase()) && value !== void 0 && value !== null) {
|
|
1090
|
-
headers[key] = String(value);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
const browserContentType = req.get("content-type") || "";
|
|
1094
|
-
if (browserContentType.startsWith("multipart/form-data")) {
|
|
1095
|
-
for (const key of Object.keys(headers)) {
|
|
1096
|
-
if (key.toLowerCase() === "content-type") delete headers[key];
|
|
1097
|
-
}
|
|
1098
|
-
headers["content-type"] = browserContentType;
|
|
1099
|
-
}
|
|
1100
|
-
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
1101
1271
|
const controller = new AbortController();
|
|
1102
1272
|
const cancel = () => controller.abort();
|
|
1103
1273
|
res.once("close", cancel);
|
|
@@ -1106,48 +1276,77 @@ async function handleProxy(req, res) {
|
|
|
1106
1276
|
try {
|
|
1107
1277
|
upstream = await fetch(target, {
|
|
1108
1278
|
method: req.method,
|
|
1109
|
-
headers,
|
|
1110
|
-
body:
|
|
1279
|
+
headers: forwardedRequestHeaders(req.get("x-wxa-headers"), req.get("content-type")),
|
|
1280
|
+
body: requestBody(req),
|
|
1111
1281
|
redirect: "follow",
|
|
1112
1282
|
signal: controller.signal
|
|
1113
1283
|
});
|
|
1114
1284
|
} catch (error) {
|
|
1115
1285
|
if (res.destroyed || controller.signal.aborted) return;
|
|
1116
|
-
|
|
1117
|
-
res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
|
|
1286
|
+
sendProxyError(res, 502, fetchErrorMessage(error));
|
|
1118
1287
|
return;
|
|
1119
1288
|
}
|
|
1120
|
-
|
|
1121
|
-
upstream.headers.forEach((value, key) => {
|
|
1122
|
-
responseHeaders[key] = value;
|
|
1123
|
-
});
|
|
1124
|
-
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1125
|
-
res.status(upstream.status);
|
|
1126
|
-
upstream.headers.forEach((value, key) => {
|
|
1127
|
-
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
1128
|
-
});
|
|
1129
|
-
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1130
|
-
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1131
|
-
res.flushHeaders();
|
|
1132
|
-
if (!upstream.body) {
|
|
1133
|
-
res.end();
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
await pipeline(Readable.fromWeb(upstream.body), res);
|
|
1289
|
+
await relayResponse(upstream, res);
|
|
1137
1290
|
} catch (error) {
|
|
1138
1291
|
if (!res.destroyed) res.destroy(error);
|
|
1139
1292
|
} finally {
|
|
1140
1293
|
res.off("close", cancel);
|
|
1141
1294
|
}
|
|
1142
1295
|
}
|
|
1296
|
+
function requestBody(req) {
|
|
1297
|
+
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
1298
|
+
return hasBody ? req.body : void 0;
|
|
1299
|
+
}
|
|
1300
|
+
function fetchErrorMessage(error) {
|
|
1301
|
+
return error?.cause?.message || error.message;
|
|
1302
|
+
}
|
|
1303
|
+
function sendProxyError(res, status, message) {
|
|
1304
|
+
res.status(status).set("x-wxa-proxy-error", "1").send(message);
|
|
1305
|
+
}
|
|
1306
|
+
function forwardedRequestHeaders(encoded, browserContentType = "") {
|
|
1307
|
+
const multipart = browserContentType.startsWith("multipart/form-data");
|
|
1308
|
+
const headers = {};
|
|
1309
|
+
for (const [key, value] of Object.entries(parseHeaders(encoded))) {
|
|
1310
|
+
const name = key.toLowerCase();
|
|
1311
|
+
if (DROPPED_REQUEST_HEADERS.has(name) || multipart && name === "content-type" || value === void 0 || value === null) continue;
|
|
1312
|
+
headers[key] = String(value);
|
|
1313
|
+
}
|
|
1314
|
+
if (multipart) headers["content-type"] = browserContentType;
|
|
1315
|
+
return headers;
|
|
1316
|
+
}
|
|
1317
|
+
function parseHeaders(encoded = "{}") {
|
|
1318
|
+
try {
|
|
1319
|
+
return JSON.parse(encoded);
|
|
1320
|
+
} catch {
|
|
1321
|
+
return {};
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async function relayResponse(upstream, res) {
|
|
1325
|
+
const responseHeaders = {};
|
|
1326
|
+
upstream.headers.forEach((value, key) => {
|
|
1327
|
+
responseHeaders[key] = value;
|
|
1328
|
+
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
1329
|
+
});
|
|
1330
|
+
res.status(upstream.status);
|
|
1331
|
+
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1332
|
+
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1333
|
+
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1334
|
+
res.flushHeaders();
|
|
1335
|
+
if (!upstream.body) {
|
|
1336
|
+
res.end();
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
await pipeline(Readable.fromWeb(upstream.body), res);
|
|
1340
|
+
}
|
|
1143
1341
|
|
|
1144
1342
|
// src/server/hmr.ts
|
|
1343
|
+
var STYLE_FILE = /\.(wxss|less|scss|sass|css)$/;
|
|
1145
1344
|
function planHotUpdate(files, units) {
|
|
1146
1345
|
const owners = /* @__PURE__ */ new Set([...units.pages, ...units.components]);
|
|
1147
1346
|
const plan = { templates: [], scripts: [], styles: [] };
|
|
1148
1347
|
for (const file of files) {
|
|
1149
|
-
const id = file.replace(/\.(wxml|js|wxss)$/, "");
|
|
1150
|
-
const target =
|
|
1348
|
+
const id = file.replace(/\.(wxml|js|wxss|less|scss|sass|css)$/, "");
|
|
1349
|
+
const target = STYLE_FILE.test(file) ? plan.styles : !owners.has(id) ? null : file.endsWith(".wxml") ? plan.templates : file.endsWith(".js") ? plan.scripts : null;
|
|
1151
1350
|
if (!target) return null;
|
|
1152
1351
|
if (!target.includes(id)) target.push(id);
|
|
1153
1352
|
}
|
|
@@ -1351,16 +1550,16 @@ function respond(action) {
|
|
|
1351
1550
|
}
|
|
1352
1551
|
|
|
1353
1552
|
// src/server/setup.ts
|
|
1354
|
-
import
|
|
1355
|
-
import
|
|
1553
|
+
import fs4 from "fs";
|
|
1554
|
+
import path4 from "path";
|
|
1356
1555
|
import { build } from "esbuild";
|
|
1357
1556
|
var SETUP_FILE_NAMES = ["wxa.setup.ts", "wxa.setup.js", "wxa.setup.mjs"];
|
|
1358
1557
|
var SETUP_MODULE = "@open-wxa/wxa-runner-core/setup";
|
|
1359
1558
|
var ENV_PREFIX = "WXA_";
|
|
1360
1559
|
function findSetupFile(projectPath) {
|
|
1361
1560
|
for (const name of SETUP_FILE_NAMES) {
|
|
1362
|
-
const file =
|
|
1363
|
-
if (
|
|
1561
|
+
const file = path4.join(projectPath, name);
|
|
1562
|
+
if (fs4.existsSync(file)) return file;
|
|
1364
1563
|
}
|
|
1365
1564
|
return null;
|
|
1366
1565
|
}
|
|
@@ -1372,7 +1571,7 @@ function collectSetupEnv(env) {
|
|
|
1372
1571
|
async function bundleSetup(file) {
|
|
1373
1572
|
const result = await build({
|
|
1374
1573
|
entryPoints: [file],
|
|
1375
|
-
absWorkingDir:
|
|
1574
|
+
absWorkingDir: path4.dirname(file),
|
|
1376
1575
|
bundle: true,
|
|
1377
1576
|
format: "esm",
|
|
1378
1577
|
platform: "browser",
|
|
@@ -1400,12 +1599,12 @@ function builtinSetupModule() {
|
|
|
1400
1599
|
|
|
1401
1600
|
// src/server/watcher.ts
|
|
1402
1601
|
import { createHash } from "crypto";
|
|
1403
|
-
import
|
|
1404
|
-
import
|
|
1602
|
+
import fs5 from "fs";
|
|
1603
|
+
import path5 from "path";
|
|
1405
1604
|
import chokidar from "chokidar";
|
|
1406
1605
|
var DEBOUNCE = 120;
|
|
1407
1606
|
var IGNORED = /(^|[/\\])(node_modules|\.git)([/\\]|$)/;
|
|
1408
|
-
var FINGERPRINTED = /\.(js|mjs|ts|json|wxml|wxss|wxs)$/;
|
|
1607
|
+
var FINGERPRINTED = /\.(js|mjs|ts|json|wxml|wxss|wxs|less|scss|sass|css)$/;
|
|
1409
1608
|
function watchProject(root, paths, onChange) {
|
|
1410
1609
|
let changed = /* @__PURE__ */ new Set();
|
|
1411
1610
|
let structural = false;
|
|
@@ -1429,7 +1628,7 @@ function watchProject(root, paths, onChange) {
|
|
|
1429
1628
|
const unchanged = updateFingerprint(fingerprints, file, event);
|
|
1430
1629
|
if (!scanned || unchanged) return;
|
|
1431
1630
|
if (event !== "change") structural = true;
|
|
1432
|
-
changed.add(
|
|
1631
|
+
changed.add(path5.relative(root, file).split(path5.sep).join("/"));
|
|
1433
1632
|
if (timer) clearTimeout(timer);
|
|
1434
1633
|
timer = setTimeout(flush, DEBOUNCE);
|
|
1435
1634
|
});
|
|
@@ -1443,7 +1642,7 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1443
1642
|
if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
|
|
1444
1643
|
let next;
|
|
1445
1644
|
try {
|
|
1446
|
-
next = createHash("sha1").update(
|
|
1645
|
+
next = createHash("sha1").update(fs5.readFileSync(file)).digest("hex");
|
|
1447
1646
|
} catch {
|
|
1448
1647
|
return false;
|
|
1449
1648
|
}
|
|
@@ -1497,7 +1696,12 @@ var DevServer = class {
|
|
|
1497
1696
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1498
1697
|
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1499
1698
|
this.logger = options.logger || console;
|
|
1500
|
-
this.project = new MiniProgram(
|
|
1699
|
+
this.project = new MiniProgram(path6.resolve(options.projectPath));
|
|
1700
|
+
for (const { plugin, files } of ignoredStyleSources(this.project, this.project.styleSetting)) {
|
|
1701
|
+
this.logger.warn(
|
|
1702
|
+
`[wxa-runner] ${files.length} \u4E2A ${plugin} \u6837\u5F0F\u6587\u4EF6\u4E0D\u4F1A\u751F\u6548\uFF08\u5982 ${files[0]}\uFF09\uFF1Aproject.config.json \u7684 setting.useCompilerPlugins \u6CA1\u6709\u5F00\u542F ${plugin}\uFF0C\u5F00\u53D1\u8005\u5DE5\u5177\u540C\u6837\u4E0D\u7F16\u8BD1\u5B83\u4EEC`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1501
1705
|
const extended = applyExtensions(options, options.extensions ?? [], { projectPath: this.project.projectPath, root: this.project.root });
|
|
1502
1706
|
extended.middlewares.forEach(({ path: mountPath }) => assertMountPath(mountPath));
|
|
1503
1707
|
this.webviewScripts = extended.webviewScripts;
|
|
@@ -1527,7 +1731,7 @@ var DevServer = class {
|
|
|
1527
1731
|
get networkUrls() {
|
|
1528
1732
|
if (!this.lanAccess) return [];
|
|
1529
1733
|
const { port } = new URL(this.address);
|
|
1530
|
-
return Object.values(
|
|
1734
|
+
return Object.values(os3.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1531
1735
|
}
|
|
1532
1736
|
async start() {
|
|
1533
1737
|
try {
|
|
@@ -1645,7 +1849,7 @@ ${error.message}`);
|
|
|
1645
1849
|
app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
|
|
1646
1850
|
const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
|
|
1647
1851
|
if (!file) return next();
|
|
1648
|
-
res.type(
|
|
1852
|
+
res.type(path6.extname(req.params[1]) || "application/octet-stream").send(file);
|
|
1649
1853
|
}));
|
|
1650
1854
|
app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
|
|
1651
1855
|
const id = decodeURIComponent(req.params[0]);
|
|
@@ -1656,7 +1860,7 @@ ${error.message}`);
|
|
|
1656
1860
|
res.type("js").send(this.builder.getModuleScript(id));
|
|
1657
1861
|
}));
|
|
1658
1862
|
app.get("/__wxa__/worker.js", this.withBuild((build2, _req, res) => {
|
|
1659
|
-
const hasWorker =
|
|
1863
|
+
const hasWorker = fs6.existsSync(path6.join(this.baseLib.dir, "WAWorker.js"));
|
|
1660
1864
|
res.type("js").send(workerScript(build2, { version: this.baseLib.version, hasWorker }));
|
|
1661
1865
|
}));
|
|
1662
1866
|
app.get("/__wxa__/service-bootstrap.js", this.withBuild((build2, _req, res) => {
|
|
@@ -1675,28 +1879,28 @@ ${error.message}`);
|
|
|
1675
1879
|
res.type("js").send(build2.pageFrame);
|
|
1676
1880
|
}));
|
|
1677
1881
|
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1678
|
-
const file =
|
|
1679
|
-
if (!
|
|
1680
|
-
res.type("json").send(ignoreConsoleHook(
|
|
1882
|
+
const file = path6.join(this.runtimeDir(), "frame.js.map");
|
|
1883
|
+
if (!fs6.existsSync(file)) return next();
|
|
1884
|
+
res.type("json").send(ignoreConsoleHook(fs6.readFileSync(file, "utf8")));
|
|
1681
1885
|
});
|
|
1682
1886
|
app.use("/__wxa__/runtime", express2.static(this.runtimeDir(), {
|
|
1683
1887
|
cacheControl: false,
|
|
1684
1888
|
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1685
1889
|
setHeaders: (res, file) => {
|
|
1686
|
-
if (
|
|
1890
|
+
if (path6.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1687
1891
|
}
|
|
1688
1892
|
}));
|
|
1689
1893
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1690
1894
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1691
|
-
const file =
|
|
1692
|
-
if (!file.startsWith(this.baseLib.dir +
|
|
1693
|
-
if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(
|
|
1895
|
+
const file = path6.resolve(this.baseLib.dir, req.params[0]);
|
|
1896
|
+
if (!file.startsWith(this.baseLib.dir + path6.sep) || !fs6.existsSync(file)) return next();
|
|
1897
|
+
if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(fs6.readFileSync(file, "utf8"), `${path6.basename(file)}?original`));
|
|
1694
1898
|
res.type("json").send(sourceMaps.get(file));
|
|
1695
1899
|
});
|
|
1696
1900
|
app.use("/__baselib__", express2.static(this.baseLib.dir, {
|
|
1697
1901
|
maxAge: "1d",
|
|
1698
1902
|
setHeaders: (res, file) => {
|
|
1699
|
-
if (file.endsWith(".js")) res.setHeader("SourceMap", `${
|
|
1903
|
+
if (file.endsWith(".js")) res.setHeader("SourceMap", `${path6.basename(file)}.map`);
|
|
1700
1904
|
}
|
|
1701
1905
|
}));
|
|
1702
1906
|
app.use(PROXY_PREFIX, express2.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
@@ -1756,7 +1960,7 @@ ${error.message}`);
|
|
|
1756
1960
|
return;
|
|
1757
1961
|
}
|
|
1758
1962
|
const data = node.data;
|
|
1759
|
-
res.type(
|
|
1963
|
+
res.type(path6.extname(req.params[0]));
|
|
1760
1964
|
res.setHeader("ETag", `"${node.etag}"`);
|
|
1761
1965
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1762
1966
|
const ranges = req.range(data.length);
|
|
@@ -1826,11 +2030,11 @@ ${error.message}`);
|
|
|
1826
2030
|
runtimeDir() {
|
|
1827
2031
|
const candidates = [
|
|
1828
2032
|
this.options.runtimeDir,
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2033
|
+
path6.join(__dirname, "runtime"),
|
|
2034
|
+
path6.join(__dirname, "../runtime"),
|
|
2035
|
+
path6.join(__dirname, "../../dist/runtime")
|
|
1832
2036
|
].filter((dir) => !!dir);
|
|
1833
|
-
const found = candidates.find((dir) =>
|
|
2037
|
+
const found = candidates.find((dir) => fs6.existsSync(path6.join(dir, "host.js")));
|
|
1834
2038
|
if (!found) {
|
|
1835
2039
|
throw new Error("\u627E\u4E0D\u5230\u6D4F\u89C8\u5668\u7AEF\u8FD0\u884C\u65F6\uFF08host.js\uFF09\uFF0C\u8BF7\u5148\u6267\u884C pnpm build");
|
|
1836
2040
|
}
|
|
@@ -1843,7 +2047,10 @@ ${error.message}`);
|
|
|
1843
2047
|
startWatching() {
|
|
1844
2048
|
let lastBuildFailed = false;
|
|
1845
2049
|
const watched = [this.project.root];
|
|
1846
|
-
|
|
2050
|
+
const outsideRoot = (file) => path6.relative(this.project.root, file).startsWith("..");
|
|
2051
|
+
if (this.setupFile && outsideRoot(this.setupFile)) watched.push(this.setupFile);
|
|
2052
|
+
const projectConfig = path6.join(this.project.projectPath, "project.config.json");
|
|
2053
|
+
if (outsideRoot(projectConfig) && fs6.existsSync(projectConfig)) watched.push(projectConfig);
|
|
1847
2054
|
this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {
|
|
1848
2055
|
const reloadOnly = structural || lastBuildFailed;
|
|
1849
2056
|
this.builder.invalidate(files);
|
|
@@ -1915,8 +2122,8 @@ function assertMountPath(mountPath) {
|
|
|
1915
2122
|
function resolveSetupFile(option, projectPath) {
|
|
1916
2123
|
if (option === false) return null;
|
|
1917
2124
|
if (!option) return findSetupFile(projectPath);
|
|
1918
|
-
const file =
|
|
1919
|
-
if (!
|
|
2125
|
+
const file = path6.resolve(option);
|
|
2126
|
+
if (!fs6.existsSync(file)) throw new Error(`\u627E\u4E0D\u5230 setup \u6587\u4EF6\uFF1A${file}`);
|
|
1920
2127
|
return file;
|
|
1921
2128
|
}
|
|
1922
2129
|
export {
|