@open-wxa/wxa-runner-core 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -49,6 +49,47 @@ var FileStore = class {
49
49
  this.etagPrefix = (0, import_crypto.randomUUID)();
50
50
  this.version = 0;
51
51
  this.nodes = /* @__PURE__ */ new Map([["/", { dir: true, data: Buffer.alloc(0), mtime: Date.now(), etag: "" }]]);
52
+ this.operations = {
53
+ readFile: ({ filePath, encoding }) => {
54
+ const data = this.read(filePath);
55
+ return encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(encoding) };
56
+ },
57
+ writeFile: (args) => this.writeEncoded(args, false),
58
+ appendFile: (args) => this.writeEncoded(args, true),
59
+ copyFile: ({ srcPath, destPath }) => {
60
+ this.write(destPath, this.read(srcPath));
61
+ return {};
62
+ },
63
+ access: ({ path: path7 }) => {
64
+ this.existing(path7);
65
+ return {};
66
+ },
67
+ stat: ({ path: path7 }) => {
68
+ const node = this.existing(path7);
69
+ const stats = {
70
+ mode: node.dir ? 16877 : 33188,
71
+ size: node.data.length,
72
+ lastAccessedTime: Math.floor(node.mtime / 1e3),
73
+ lastModifiedTime: Math.floor(node.mtime / 1e3)
74
+ };
75
+ return { stats, ...stats };
76
+ },
77
+ getFileInfo: ({ filePath }) => ({ size: this.read(filePath).length }),
78
+ mkdir: ({ dirPath, recursive }) => {
79
+ this.mkdir(dirPath, !!recursive);
80
+ return {};
81
+ },
82
+ readdir: ({ dirPath }) => ({ files: this.readdir(dirPath) }),
83
+ unlink: ({ filePath }) => {
84
+ this.remove(filePath, { dir: false });
85
+ return {};
86
+ },
87
+ rmdir: ({ dirPath, recursive }) => {
88
+ this.assertNotRoot(dirPath);
89
+ this.remove(dirPath, { dir: true, recursive: !!recursive });
90
+ return {};
91
+ }
92
+ };
52
93
  this.roots = roots.map((root) => this.normalize(root));
53
94
  for (const root of this.roots) this.mkdir(root, true);
54
95
  }
@@ -109,66 +150,37 @@ var FileStore = class {
109
150
  }
110
151
  this.nodes.delete(target);
111
152
  }
153
+ /** 文件 API 经开发服务器调用的操作,参数和返回值的字段名与基础库一致 */
112
154
  execute(operation, args) {
113
- switch (operation) {
114
- case "readFile": {
115
- const data = this.read(args.filePath);
116
- return args.encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(args.encoding) };
117
- }
118
- case "writeFile":
119
- case "appendFile": {
120
- const data = Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8");
121
- this.write(args.filePath, data, operation === "appendFile");
122
- return {};
123
- }
124
- case "copyFile":
125
- this.write(args.destPath, this.read(args.srcPath));
126
- return {};
127
- case "access":
128
- if (!this.stat(args.path)) throw new Error(`no such file or directory ${args.path}`);
129
- return {};
130
- case "stat": {
131
- const node = this.stat(args.path);
132
- if (!node) throw new Error(`no such file or directory ${args.path}`);
133
- const stats = {
134
- mode: node.dir ? 16877 : 33188,
135
- size: node.data.length,
136
- lastAccessedTime: Math.floor(node.mtime / 1e3),
137
- lastModifiedTime: Math.floor(node.mtime / 1e3)
138
- };
139
- return { stats, ...stats };
140
- }
141
- case "getFileInfo":
142
- return { size: this.read(args.filePath).length };
143
- case "mkdir":
144
- this.mkdir(args.dirPath, !!args.recursive);
145
- return {};
146
- case "readdir":
147
- return { files: this.readdir(args.dirPath) };
148
- case "unlink":
149
- this.remove(args.filePath, { dir: false });
150
- return {};
151
- case "rmdir": {
152
- const target = this.normalize(args.dirPath);
153
- const prefix = target === "/" ? "/" : target + "/";
154
- if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
155
- throw new Error(`operation not permitted ${args.dirPath}`);
156
- }
157
- this.remove(args.dirPath, { dir: true, recursive: !!args.recursive });
158
- return {};
159
- }
160
- default:
161
- throw new Error(`unsupported filesystem operation ${operation}`);
155
+ if (!Object.prototype.hasOwnProperty.call(this.operations, operation)) {
156
+ throw new Error(`unsupported filesystem operation ${operation}`);
157
+ }
158
+ return this.operations[operation](args);
159
+ }
160
+ writeEncoded(args, append) {
161
+ this.write(args.filePath, Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8"), append);
162
+ return {};
163
+ }
164
+ existing(filePath) {
165
+ const node = this.stat(filePath);
166
+ if (!node) throw new Error(`no such file or directory ${filePath}`);
167
+ return node;
168
+ }
169
+ assertNotRoot(dirPath) {
170
+ const target = this.normalize(dirPath);
171
+ const prefix = target === "/" ? "/" : target + "/";
172
+ if (this.roots.some((root) => root === target || root.startsWith(prefix))) {
173
+ throw new Error(`operation not permitted ${dirPath}`);
162
174
  }
163
175
  }
164
176
  };
165
177
 
166
178
  // src/server/DevServer.ts
167
- var import_fs5 = __toESM(require("fs"));
179
+ var import_fs6 = __toESM(require("fs"));
168
180
  var import_http = __toESM(require("http"));
169
181
  var import_net = __toESM(require("net"));
170
- var import_os2 = __toESM(require("os"));
171
- var import_path5 = __toESM(require("path"));
182
+ var import_os3 = __toESM(require("os"));
183
+ var import_path6 = __toESM(require("path"));
172
184
  var import_express2 = __toESM(require("express"));
173
185
  var import_ws2 = require("ws");
174
186
  var import_wxa_runner_extractor5 = require("@open-wxa/wxa-runner-extractor");
@@ -177,6 +189,164 @@ var import_wxa_runner_extractor5 = require("@open-wxa/wxa-runner-extractor");
177
189
  var import_wxa_runner_compiler = require("@open-wxa/wxa-runner-compiler");
178
190
  var import_esbuild = require("esbuild");
179
191
 
192
+ // src/project/StyleMirror.ts
193
+ var import_fs = __toESM(require("fs"));
194
+ var import_module = require("module");
195
+ var import_os = __toESM(require("os"));
196
+ var import_path = __toESM(require("path"));
197
+ var import_url = require("url");
198
+ var PLUGIN_EXTENSIONS = {
199
+ less: [".less"],
200
+ sass: [".sass", ".scss"]
201
+ };
202
+ var IMPORT_WXSS = /(?:^|\s)?(?:@import)(?:\s*)?(["'])([^"']+.wxss)\1(?:\s*);/g;
203
+ function readStyleSetting(setting) {
204
+ const plugins = [];
205
+ let lessGlobalVars = {};
206
+ for (const item of Array.isArray(setting?.useCompilerPlugins) ? setting.useCompilerPlugins : []) {
207
+ const [name, options] = Array.isArray(item) ? item : [item, void 0];
208
+ if (name !== "less" && name !== "sass" || plugins.includes(name)) continue;
209
+ plugins.push(name);
210
+ if (name === "less" && options?.globalVars) lessGlobalVars = options.globalVars;
211
+ }
212
+ return {
213
+ plugins,
214
+ lessGlobalVars,
215
+ lessCommonFile: setting?.lessSetting?.commonUseFilePath || "app.less",
216
+ sassCommonFile: setting?.sassSetting?.sassCommonUseFilePath || "global.sass",
217
+ scssCommonFile: setting?.sassSetting?.scssCommonUseFilePath || "global.scss"
218
+ };
219
+ }
220
+ function ignoredStyleSources(project, setting) {
221
+ 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);
222
+ }
223
+ var StyleMirror = class {
224
+ constructor(setting) {
225
+ this.setting = setting;
226
+ this.dir = null;
227
+ /** 已写入的 .wxss 与写入的内容 */
228
+ this.files = /* @__PURE__ */ new Map();
229
+ this.compilers = /* @__PURE__ */ new Map();
230
+ }
231
+ /** 镜像目录;还没同步过时为 null */
232
+ get directory() {
233
+ return this.dir;
234
+ }
235
+ /**
236
+ * 编译全部预处理文件,与原有的 .wxss 一起写进镜像目录。
237
+ * @returns 镜像目录,以及其中全部 .wxss 的相对路径
238
+ */
239
+ async sync(project) {
240
+ const sources = this.pickSources(project);
241
+ const outputs = await Promise.all(
242
+ [...sources].map(async ([target, source]) => [target, await this.compile(project, source)])
243
+ );
244
+ const dir = this.dir ??= import_fs.default.mkdtempSync(import_path.default.join(import_os.default.tmpdir(), "wxa-runner-wxss-"));
245
+ for (const rel of [...this.files.keys()]) {
246
+ if (sources.has(rel)) continue;
247
+ import_fs.default.rmSync(import_path.default.join(dir, rel), { force: true });
248
+ this.files.delete(rel);
249
+ }
250
+ for (const [rel, css] of outputs) {
251
+ if (this.files.get(rel) === css) continue;
252
+ const target = import_path.default.join(dir, rel);
253
+ import_fs.default.mkdirSync(import_path.default.dirname(target), { recursive: true });
254
+ import_fs.default.writeFileSync(target, css);
255
+ this.files.set(rel, css);
256
+ }
257
+ return { cwd: dir, files: [...sources.keys()] };
258
+ }
259
+ dispose() {
260
+ if (this.dir) import_fs.default.rmSync(this.dir, { recursive: true, force: true });
261
+ this.dir = null;
262
+ this.files.clear();
263
+ }
264
+ /** 每个 .wxss 由哪个源文件生成:键为 .wxss 的相对路径 */
265
+ pickSources(project) {
266
+ const extensions = [".wxss", ...this.setting.plugins.flatMap((plugin) => PLUGIN_EXTENSIONS[plugin])];
267
+ const sources = /* @__PURE__ */ new Map();
268
+ for (const file of project.listFiles(extensions)) {
269
+ const ext = import_path.default.extname(file);
270
+ const target = file.slice(0, -ext.length) + ".wxss";
271
+ const current = sources.get(target);
272
+ if (!current || extensions.indexOf(ext) > extensions.indexOf(import_path.default.extname(current))) sources.set(target, file);
273
+ }
274
+ return sources;
275
+ }
276
+ async compile(project, file) {
277
+ const ext = import_path.default.extname(file);
278
+ if (ext === ".wxss") return project.readText(file);
279
+ const compiler = await this.loadCompiler(ext === ".less" ? "less" : "sass", project);
280
+ try {
281
+ return ext === ".less" ? await this.compileLess(compiler, project, file) : this.compileSass(compiler, project, file);
282
+ } catch (error) {
283
+ throw new Error(`${file} \u7F16\u8BD1\u5931\u8D25\uFF1A${describeError(project, error)}`);
284
+ }
285
+ }
286
+ async compileLess(less, project, file) {
287
+ const common = this.setting.lessCommonFile;
288
+ const prefix = Object.entries(this.setting.lessGlobalVars).map(([name, value]) => `@${name.replace(/^@/, "")}: ${value};`);
289
+ if (file !== common && project.exists(common)) prefix.push(`@import (optional, reference) '${toRoot(file)}/${common}';`);
290
+ let code = prefix.map((statement) => statement + " ").join("") + project.readText(file);
291
+ code = code.replace(IMPORT_WXSS, (statement, _quote, target) => statement.replace(target, `${target}?css`));
292
+ const { css } = await less.render(code, {
293
+ filename: import_path.default.join(project.root, file),
294
+ paths: [project.root]
295
+ });
296
+ return css.replace(/(@import\s*["'][^"']+\.wxss)\?css(["'])/g, "$1$2");
297
+ }
298
+ compileSass(sass, project, file) {
299
+ const indented = file.endsWith(".sass");
300
+ const preferred = indented ? this.setting.sassCommonFile : this.setting.scssCommonFile;
301
+ const fallback = indented ? this.setting.scssCommonFile : this.setting.sassCommonFile;
302
+ const common = project.exists(preferred) ? preferred : project.exists(fallback) ? fallback : null;
303
+ let code = project.readText(file);
304
+ if (common && file !== preferred && file !== fallback) {
305
+ code = `@use '${toRoot(file)}/${common}'${indented ? "\n" : "; "}${code}`;
306
+ }
307
+ const imported = /* @__PURE__ */ new Set();
308
+ code = code.replace(IMPORT_WXSS, (statement, _quote, target) => {
309
+ imported.add(target);
310
+ return statement.replace(target, `${target.slice(0, -".wxss".length)}.css`);
311
+ });
312
+ const css = typeof sass.compileString === "function" ? sass.compileString(code, { url: (0, import_url.pathToFileURL)(import_path.default.join(project.root, file)), syntax: indented ? "indented" : "scss", loadPaths: [project.root] }).css : sass.renderSync({ data: code, file: import_path.default.join(project.root, file), indentedSyntax: indented, includePaths: [project.root] }).css.toString();
313
+ return css.replace(
314
+ /(@import\s*["'])([^"']+)\.css(["'])/g,
315
+ (statement, head, target, tail) => imported.has(`${target}.wxss`) ? `${head}${target}.wxss${tail}` : statement
316
+ );
317
+ }
318
+ /** 用到时才加载:先从小程序根目录和项目目录向上查找 node_modules,找不到时用 wxa-runner 自带的 */
319
+ loadCompiler(name, project) {
320
+ let loading = this.compilers.get(name);
321
+ if (!loading) {
322
+ loading = importCompiler(name, project);
323
+ this.compilers.set(name, loading);
324
+ }
325
+ return loading;
326
+ }
327
+ };
328
+ async function importCompiler(name, project) {
329
+ let resolved = null;
330
+ try {
331
+ resolved = (0, import_module.createRequire)(import_path.default.join(project.root, "package.json")).resolve(name, { paths: [project.root, project.projectPath] });
332
+ } catch {
333
+ }
334
+ const module2 = resolved ? await import((0, import_url.pathToFileURL)(resolved).href) : name === "less" ? await import("less") : await import("sass");
335
+ const entry = name === "less" ? "render" : "renderSync";
336
+ return typeof module2[entry] === "function" ? module2 : module2.default;
337
+ }
338
+ function toRoot(file) {
339
+ const dir = import_path.default.posix.dirname(file);
340
+ return dir === "." ? "." : dir.split("/").map(() => "..").join("/");
341
+ }
342
+ function describeError(project, error) {
343
+ if (typeof error?.line === "number" && error.filename) {
344
+ const file = import_path.default.relative(project.root, error.filename).split(import_path.default.sep).join("/");
345
+ return `${file}:${error.line}:${(error.column ?? 0) + 1} ${error.message}`;
346
+ }
347
+ return error?.message ?? String(error);
348
+ }
349
+
180
350
  // src/project/wxConfig.ts
181
351
  var DEFAULT_WINDOW = {
182
352
  navigationBarBackgroundColor: "#000000",
@@ -234,9 +404,9 @@ function createWxConfig(input) {
234
404
  }
235
405
 
236
406
  // 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"));
407
+ var import_fs2 = __toESM(require("fs"));
408
+ var import_os2 = __toESM(require("os"));
409
+ var import_path2 = __toESM(require("path"));
240
410
  var WxmlMirror = class {
241
411
  constructor(transform) {
242
412
  this.transform = transform;
@@ -253,25 +423,25 @@ var WxmlMirror = class {
253
423
  * 只重写源内容变化的文件;项目里已删除的文件同时从镜像目录删除,被引用时 wcc 照常报错
254
424
  */
255
425
  sync(project, relPaths) {
256
- const dir = this.dir ??= import_fs.default.mkdtempSync(import_path.default.join(import_os.default.tmpdir(), "wxa-runner-wxml-"));
426
+ const dir = this.dir ??= import_fs2.default.mkdtempSync(import_path2.default.join(import_os2.default.tmpdir(), "wxa-runner-wxml-"));
257
427
  const current = new Set(relPaths);
258
428
  for (const rel of [...this.files.keys()]) {
259
429
  if (current.has(rel)) continue;
260
- import_fs.default.rmSync(import_path.default.join(dir, rel), { force: true });
430
+ import_fs2.default.rmSync(import_path2.default.join(dir, rel), { force: true });
261
431
  this.files.delete(rel);
262
432
  }
263
433
  for (const rel of relPaths) {
264
434
  const source = project.readText(rel);
265
435
  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);
436
+ const target = import_path2.default.join(dir, rel);
437
+ import_fs2.default.mkdirSync(import_path2.default.dirname(target), { recursive: true });
438
+ import_fs2.default.writeFileSync(target, rel.endsWith(".wxml") ? this.transform(source, rel) : source);
269
439
  this.files.set(rel, source);
270
440
  }
271
441
  return dir;
272
442
  }
273
443
  dispose() {
274
- if (this.dir) import_fs.default.rmSync(this.dir, { recursive: true, force: true });
444
+ if (this.dir) import_fs2.default.rmSync(this.dir, { recursive: true, force: true });
275
445
  this.dir = null;
276
446
  this.files.clear();
277
447
  }
@@ -313,15 +483,18 @@ var AppBuilder = class {
313
483
  this.dirty = /* @__PURE__ */ new Set(["config", "wxml", "wxss", "js"]);
314
484
  this.wxmlCache = null;
315
485
  this.wxssCache = null;
486
+ /** 开启了 less / sass 编译插件时才有;插件设置变了就换一个 */
487
+ this.styleMirror = null;
316
488
  this.wxmlMirror = options.wxmlTransform ? new WxmlMirror(options.wxmlTransform) : null;
317
489
  }
318
490
  /** wxmlTransform 的镜像目录;没有设置转换或还没编译过时为 null */
319
491
  get wxmlMirrorDir() {
320
492
  return this.wxmlMirror?.directory ?? null;
321
493
  }
322
- /** 删除 wxmlTransform 的镜像目录,开发服务器关闭时调用 */
494
+ /** 删除 wxmlTransform 和样式预处理的镜像目录,开发服务器关闭时调用 */
323
495
  dispose() {
324
496
  this.wxmlMirror?.dispose();
497
+ this.styleMirror?.mirror.dispose();
325
498
  }
326
499
  /** 根据变化的文件标记需要重新构建的部分;返回 true 表示需要刷新 */
327
500
  invalidate(changedFiles = []) {
@@ -455,14 +628,26 @@ ${pageStyles}`,
455
628
  }
456
629
  /** 与开发者工具一致,项目里的 wxss 全部交给 wcsc:被 @import 的文件(如 uni-app 的 common/main.wxss)也要在列表里 */
457
630
  async compileStyles(units) {
458
- const pageFiles = units.filter((id) => this.project.exists(id + ".wxss")).map((id) => `./${id}.wxss`);
459
- const commonFiles = this.project.listFiles([".wxss"]).map((file) => `./${file}`).filter((file) => !pageFiles.includes(file));
460
- const { comm, pages } = await (0, import_wxa_runner_compiler.compileWxss)({ cwd: this.project.root, pageFiles, commonFiles });
631
+ const mirror = this.currentStyleMirror();
632
+ const { cwd, files } = mirror ? await mirror.sync(this.project) : { cwd: this.project.root, files: this.project.listFiles([".wxss"]) };
633
+ const styles = new Set(files);
634
+ const pageFiles = units.filter((id) => styles.has(id + ".wxss")).map((id) => `./${id}.wxss`);
635
+ const commonFiles = files.map((file) => `./${file}`).filter((file) => !pageFiles.includes(file));
636
+ const { comm, pages } = await (0, import_wxa_runner_compiler.compileWxss)({ cwd, pageFiles, commonFiles });
461
637
  return {
462
638
  comm: rewriteSafeAreaInsets(comm),
463
639
  pages: Object.fromEntries(Object.entries(pages).map(([file, css]) => [file, rewriteSafeAreaInsets(css)]))
464
640
  };
465
641
  }
642
+ currentStyleMirror() {
643
+ const setting = this.project.styleSetting;
644
+ const key = JSON.stringify(setting);
645
+ if (this.styleMirror?.setting !== key) {
646
+ this.styleMirror?.mirror.dispose();
647
+ this.styleMirror = setting.plugins.length > 0 ? { setting: key, mirror: new StyleMirror(setting) } : null;
648
+ }
649
+ return this.styleMirror?.mirror ?? null;
650
+ }
466
651
  /** $gwx 加上各页面、组件的 json / wxml 注册;逻辑层用裁剪版 $gwx,渲染层用完整版 */
467
652
  createTemplates(app, units, unitJson, gwx) {
468
653
  const lines = [gwx, `__wxAppCode__["app.json"] = ${toJs(app)};`];
@@ -507,28 +692,32 @@ function toJs(value) {
507
692
  function classifyFile(file) {
508
693
  if (file.endsWith(".json")) return "config";
509
694
  if (file.endsWith(".wxml") || file.endsWith(".wxs")) return "wxml";
510
- if (file.endsWith(".wxss")) return "wxss";
695
+ if (/\.(wxss|less|scss|sass|css)$/.test(file)) return "wxss";
511
696
  if (file.endsWith(".js")) return "js";
512
697
  return null;
513
698
  }
514
699
 
515
700
  // src/project/MiniProgram.ts
516
- var import_fs2 = __toESM(require("fs"));
517
- var import_path2 = __toESM(require("path"));
701
+ var import_fs3 = __toESM(require("fs"));
702
+ var import_path3 = __toESM(require("path"));
518
703
  var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".idea", ".vscode"]);
519
704
  var PROJECT_FILES = /* @__PURE__ */ new Set(["project.config.json", "project.private.config.json", "sitemap.json", "app.json", "wxa.setup.js"]);
520
705
  var MiniProgram = class {
521
706
  constructor(projectPath) {
522
707
  this.projectPath = projectPath;
523
- const projectConfig = readJsonFile(import_path2.default.join(projectPath, "project.config.json")) || {};
524
- this.root = import_path2.default.resolve(projectPath, projectConfig.miniprogramRoot || "");
708
+ const projectConfig = readJsonFile(import_path3.default.join(projectPath, "project.config.json")) || {};
709
+ this.root = import_path3.default.resolve(projectPath, projectConfig.miniprogramRoot || "");
525
710
  this.appid = projectConfig.appid || "touristappid";
526
- if (!import_fs2.default.existsSync(import_path2.default.join(this.root, "app.json"))) {
711
+ if (!import_fs3.default.existsSync(import_path3.default.join(this.root, "app.json"))) {
527
712
  throw new Error(`${this.root} \u4E0B\u6CA1\u6709 app.json\uFF0C\u4E0D\u662F\u6709\u6548\u7684\u5C0F\u7A0B\u5E8F\u76EE\u5F55`);
528
713
  }
529
714
  }
715
+ /** project.config.json 里与 less / sass 编译插件有关的设置;每次重新读取,开发中修改了也能生效 */
716
+ get styleSetting() {
717
+ return readStyleSetting(readJsonFile(import_path3.default.join(this.projectPath, "project.config.json"))?.setting);
718
+ }
530
719
  readAppJson() {
531
- const app = readJsonFile(import_path2.default.join(this.root, "app.json"));
720
+ const app = readJsonFile(import_path3.default.join(this.root, "app.json"));
532
721
  if (!app || !Array.isArray(app.pages) || app.pages.length === 0) {
533
722
  throw new Error("app.json \u7F3A\u5C11 pages \u914D\u7F6E");
534
723
  }
@@ -540,14 +729,14 @@ var MiniProgram = class {
540
729
  /** Worker 代码所在目录,不带首尾的 /;没有配置 workers 时为 null */
541
730
  workersDir(app) {
542
731
  const dir = typeof app.workers === "string" ? app.workers : app.workers?.path;
543
- return dir ? import_path2.default.posix.normalize(dir).replace(/^\/+|\/+$/g, "") : null;
732
+ return dir ? import_path3.default.posix.normalize(dir).replace(/^\/+|\/+$/g, "") : null;
544
733
  }
545
734
  /** 主包页面 + 分包页面(带 root 前缀) */
546
735
  allPages(app) {
547
736
  const pages = [...app.pages];
548
737
  for (const pkg of this.subPackages(app)) {
549
738
  for (const page of pkg.pages) {
550
- const full = import_path2.default.posix.join(pkg.root, page);
739
+ const full = import_path3.default.posix.join(pkg.root, page);
551
740
  if (!pages.includes(full)) pages.push(full);
552
741
  }
553
742
  }
@@ -559,7 +748,7 @@ var MiniProgram = class {
559
748
  * 所以能解析到的引用都换成组件的绝对路径
560
749
  */
561
750
  readUnitJson(id, app) {
562
- const json = { ...readJsonFile(import_path2.default.join(this.root, id + ".json")) };
751
+ const json = { ...readJsonFile(import_path3.default.join(this.root, id + ".json")) };
563
752
  const resolve = (fromId, ref) => {
564
753
  const resolved = this.resolveComponent(fromId, ref);
565
754
  return resolved ? "/" + resolved : null;
@@ -580,7 +769,7 @@ var MiniProgram = class {
580
769
  if (resolved || ref.startsWith("/") || /^plugin(-private)?:\/\//.test(ref)) {
581
770
  json.usingComponents[tag] = resolved ?? ref;
582
771
  } else {
583
- const relative = import_path2.default.posix.relative(import_path2.default.posix.dirname(id), ref);
772
+ const relative = import_path3.default.posix.relative(import_path3.default.posix.dirname(id), ref);
584
773
  json.usingComponents[tag] = relative.startsWith(".") ? relative : "./" + relative;
585
774
  }
586
775
  }
@@ -593,25 +782,25 @@ var MiniProgram = class {
593
782
  */
594
783
  resolveComponent(fromId, ref) {
595
784
  if (!ref || ref.startsWith("plugin://") || ref.startsWith("plugin-private://")) return null;
596
- const fromDir = import_path2.default.posix.dirname(fromId);
785
+ const fromDir = import_path3.default.posix.dirname(fromId);
597
786
  const candidates = [];
598
787
  if (ref.startsWith("/")) {
599
788
  candidates.push(ref.slice(1));
600
789
  } else {
601
- candidates.push(import_path2.default.posix.join(fromDir, ref));
790
+ candidates.push(import_path3.default.posix.join(fromDir, ref));
602
791
  if (!ref.startsWith(".")) {
603
792
  let dir = fromDir;
604
793
  for (; ; ) {
605
- candidates.push(import_path2.default.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
794
+ candidates.push(import_path3.default.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
606
795
  if (dir === "." || dir === "") break;
607
- dir = import_path2.default.posix.dirname(dir);
796
+ dir = import_path3.default.posix.dirname(dir);
608
797
  }
609
798
  }
610
799
  }
611
800
  for (const candidate of candidates) {
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;
801
+ const normalized = import_path3.default.posix.normalize(candidate);
802
+ for (const id of [normalized, import_path3.default.posix.join(normalized, "index")]) {
803
+ if (import_fs3.default.existsSync(import_path3.default.join(this.root, id + ".json"))) return id;
615
804
  }
616
805
  }
617
806
  return null;
@@ -643,11 +832,11 @@ var MiniProgram = class {
643
832
  listFiles(extensions) {
644
833
  const result = [];
645
834
  const walk = (dir, rel) => {
646
- for (const entry of import_fs2.default.readdirSync(dir, { withFileTypes: true })) {
835
+ for (const entry of import_fs3.default.readdirSync(dir, { withFileTypes: true })) {
647
836
  if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
648
837
  const childRel = rel ? `${rel}/${entry.name}` : entry.name;
649
838
  if (entry.isDirectory()) {
650
- walk(import_path2.default.join(dir, entry.name), childRel);
839
+ walk(import_path3.default.join(dir, entry.name), childRel);
651
840
  } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
652
841
  if (!rel && PROJECT_FILES.has(entry.name)) continue;
653
842
  result.push(childRel);
@@ -658,18 +847,18 @@ var MiniProgram = class {
658
847
  return result.sort();
659
848
  }
660
849
  exists(relPath) {
661
- return import_fs2.default.existsSync(import_path2.default.join(this.root, relPath));
850
+ return import_fs3.default.existsSync(import_path3.default.join(this.root, relPath));
662
851
  }
663
852
  readText(relPath) {
664
- return import_fs2.default.readFileSync(import_path2.default.join(this.root, relPath), "utf8");
853
+ return import_fs3.default.readFileSync(import_path3.default.join(this.root, relPath), "utf8");
665
854
  }
666
855
  isInIndependentSubPackage(id, app) {
667
856
  return this.subPackages(app).some((pkg) => pkg.independent && id.startsWith(pkg.root.replace(/\/?$/, "/")));
668
857
  }
669
858
  };
670
859
  function readJsonFile(file) {
671
- if (!import_fs2.default.existsSync(file)) return null;
672
- const text = import_fs2.default.readFileSync(file, "utf8");
860
+ if (!import_fs3.default.existsSync(file)) return null;
861
+ const text = import_fs3.default.readFileSync(file, "utf8");
673
862
  try {
674
863
  return JSON.parse(text);
675
864
  } catch (error) {
@@ -735,9 +924,9 @@ var WechatDevClient = class {
735
924
  *
736
925
  * @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
737
926
  */
738
- async post(path6, body, timeout = DEFAULT_TIMEOUT) {
927
+ async post(path7, body, timeout = DEFAULT_TIMEOUT) {
739
928
  const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
740
- const response = await fetch(`${BASE_URL}/${path6}?${query}`, {
929
+ const response = await fetch(`${BASE_URL}/${path7}?${query}`, {
741
930
  method: "POST",
742
931
  headers: { "Content-Type": "application/json" },
743
932
  body: JSON.stringify(body),
@@ -749,8 +938,8 @@ var WechatDevClient = class {
749
938
  return response;
750
939
  }
751
940
  /** 同 post,返回解析后的 JSON */
752
- async postJson(path6, body) {
753
- return (await this.post(path6, body)).json();
941
+ async postJson(path7, body) {
942
+ return (await this.post(path7, body)).json();
754
943
  }
755
944
  };
756
945
 
@@ -1112,28 +1301,9 @@ var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "con
1112
1301
  async function handleProxy(req, res) {
1113
1302
  const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
1114
1303
  if (!target) {
1115
- res.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${req.originalUrl}"`);
1304
+ sendProxyError(res, 400, `invalid url "${req.originalUrl}"`);
1116
1305
  return;
1117
1306
  }
1118
- let requestHeaders = {};
1119
- try {
1120
- requestHeaders = JSON.parse(req.get("x-wxa-headers") || "{}");
1121
- } catch {
1122
- }
1123
- const headers = {};
1124
- for (const [key, value] of Object.entries(requestHeaders)) {
1125
- if (!DROPPED_REQUEST_HEADERS.has(key.toLowerCase()) && value !== void 0 && value !== null) {
1126
- headers[key] = String(value);
1127
- }
1128
- }
1129
- const browserContentType = req.get("content-type") || "";
1130
- if (browserContentType.startsWith("multipart/form-data")) {
1131
- for (const key of Object.keys(headers)) {
1132
- if (key.toLowerCase() === "content-type") delete headers[key];
1133
- }
1134
- headers["content-type"] = browserContentType;
1135
- }
1136
- const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
1137
1307
  const controller = new AbortController();
1138
1308
  const cancel = () => controller.abort();
1139
1309
  res.once("close", cancel);
@@ -1142,48 +1312,77 @@ async function handleProxy(req, res) {
1142
1312
  try {
1143
1313
  upstream = await fetch(target, {
1144
1314
  method: req.method,
1145
- headers,
1146
- body: hasBody ? req.body : void 0,
1315
+ headers: forwardedRequestHeaders(req.get("x-wxa-headers"), req.get("content-type")),
1316
+ body: requestBody(req),
1147
1317
  redirect: "follow",
1148
1318
  signal: controller.signal
1149
1319
  });
1150
1320
  } catch (error) {
1151
1321
  if (res.destroyed || controller.signal.aborted) return;
1152
- const cause = error?.cause?.message;
1153
- res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
1322
+ sendProxyError(res, 502, fetchErrorMessage(error));
1154
1323
  return;
1155
1324
  }
1156
- const responseHeaders = {};
1157
- upstream.headers.forEach((value, key) => {
1158
- responseHeaders[key] = value;
1159
- });
1160
- const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
1161
- res.status(upstream.status);
1162
- upstream.headers.forEach((value, key) => {
1163
- if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
1164
- });
1165
- res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
1166
- res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
1167
- res.flushHeaders();
1168
- if (!upstream.body) {
1169
- res.end();
1170
- return;
1171
- }
1172
- await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(upstream.body), res);
1325
+ await relayResponse(upstream, res);
1173
1326
  } catch (error) {
1174
1327
  if (!res.destroyed) res.destroy(error);
1175
1328
  } finally {
1176
1329
  res.off("close", cancel);
1177
1330
  }
1178
1331
  }
1332
+ function requestBody(req) {
1333
+ const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
1334
+ return hasBody ? req.body : void 0;
1335
+ }
1336
+ function fetchErrorMessage(error) {
1337
+ return error?.cause?.message || error.message;
1338
+ }
1339
+ function sendProxyError(res, status, message) {
1340
+ res.status(status).set("x-wxa-proxy-error", "1").send(message);
1341
+ }
1342
+ function forwardedRequestHeaders(encoded, browserContentType = "") {
1343
+ const multipart = browserContentType.startsWith("multipart/form-data");
1344
+ const headers = {};
1345
+ for (const [key, value] of Object.entries(parseHeaders(encoded))) {
1346
+ const name = key.toLowerCase();
1347
+ if (DROPPED_REQUEST_HEADERS.has(name) || multipart && name === "content-type" || value === void 0 || value === null) continue;
1348
+ headers[key] = String(value);
1349
+ }
1350
+ if (multipart) headers["content-type"] = browserContentType;
1351
+ return headers;
1352
+ }
1353
+ function parseHeaders(encoded = "{}") {
1354
+ try {
1355
+ return JSON.parse(encoded);
1356
+ } catch {
1357
+ return {};
1358
+ }
1359
+ }
1360
+ async function relayResponse(upstream, res) {
1361
+ const responseHeaders = {};
1362
+ upstream.headers.forEach((value, key) => {
1363
+ responseHeaders[key] = value;
1364
+ if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
1365
+ });
1366
+ res.status(upstream.status);
1367
+ res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
1368
+ const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
1369
+ res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
1370
+ res.flushHeaders();
1371
+ if (!upstream.body) {
1372
+ res.end();
1373
+ return;
1374
+ }
1375
+ await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(upstream.body), res);
1376
+ }
1179
1377
 
1180
1378
  // src/server/hmr.ts
1379
+ var STYLE_FILE = /\.(wxss|less|scss|sass|css)$/;
1181
1380
  function planHotUpdate(files, units) {
1182
1381
  const owners = /* @__PURE__ */ new Set([...units.pages, ...units.components]);
1183
1382
  const plan = { templates: [], scripts: [], styles: [] };
1184
1383
  for (const file of files) {
1185
- const id = file.replace(/\.(wxml|js|wxss)$/, "");
1186
- const target = file.endsWith(".wxss") ? plan.styles : !owners.has(id) ? null : file.endsWith(".wxml") ? plan.templates : file.endsWith(".js") ? plan.scripts : null;
1384
+ const id = file.replace(/\.(wxml|js|wxss|less|scss|sass|css)$/, "");
1385
+ const target = STYLE_FILE.test(file) ? plan.styles : !owners.has(id) ? null : file.endsWith(".wxml") ? plan.templates : file.endsWith(".js") ? plan.scripts : null;
1187
1386
  if (!target) return null;
1188
1387
  if (!target.includes(id)) target.push(id);
1189
1388
  }
@@ -1387,16 +1586,16 @@ function respond(action) {
1387
1586
  }
1388
1587
 
1389
1588
  // src/server/setup.ts
1390
- var import_fs3 = __toESM(require("fs"));
1391
- var import_path3 = __toESM(require("path"));
1589
+ var import_fs4 = __toESM(require("fs"));
1590
+ var import_path4 = __toESM(require("path"));
1392
1591
  var import_esbuild2 = require("esbuild");
1393
1592
  var SETUP_FILE_NAMES = ["wxa.setup.ts", "wxa.setup.js", "wxa.setup.mjs"];
1394
1593
  var SETUP_MODULE = "@open-wxa/wxa-runner-core/setup";
1395
1594
  var ENV_PREFIX = "WXA_";
1396
1595
  function findSetupFile(projectPath) {
1397
1596
  for (const name of SETUP_FILE_NAMES) {
1398
- const file = import_path3.default.join(projectPath, name);
1399
- if (import_fs3.default.existsSync(file)) return file;
1597
+ const file = import_path4.default.join(projectPath, name);
1598
+ if (import_fs4.default.existsSync(file)) return file;
1400
1599
  }
1401
1600
  return null;
1402
1601
  }
@@ -1408,7 +1607,7 @@ function collectSetupEnv(env) {
1408
1607
  async function bundleSetup(file) {
1409
1608
  const result = await (0, import_esbuild2.build)({
1410
1609
  entryPoints: [file],
1411
- absWorkingDir: import_path3.default.dirname(file),
1610
+ absWorkingDir: import_path4.default.dirname(file),
1412
1611
  bundle: true,
1413
1612
  format: "esm",
1414
1613
  platform: "browser",
@@ -1436,12 +1635,12 @@ function builtinSetupModule() {
1436
1635
 
1437
1636
  // src/server/watcher.ts
1438
1637
  var import_crypto3 = require("crypto");
1439
- var import_fs4 = __toESM(require("fs"));
1440
- var import_path4 = __toESM(require("path"));
1638
+ var import_fs5 = __toESM(require("fs"));
1639
+ var import_path5 = __toESM(require("path"));
1441
1640
  var import_chokidar = __toESM(require("chokidar"));
1442
1641
  var DEBOUNCE = 120;
1443
1642
  var IGNORED = /(^|[/\\])(node_modules|\.git)([/\\]|$)/;
1444
- var FINGERPRINTED = /\.(js|mjs|ts|json|wxml|wxss|wxs)$/;
1643
+ var FINGERPRINTED = /\.(js|mjs|ts|json|wxml|wxss|wxs|less|scss|sass|css)$/;
1445
1644
  function watchProject(root, paths, onChange) {
1446
1645
  let changed = /* @__PURE__ */ new Set();
1447
1646
  let structural = false;
@@ -1465,7 +1664,7 @@ function watchProject(root, paths, onChange) {
1465
1664
  const unchanged = updateFingerprint(fingerprints, file, event);
1466
1665
  if (!scanned || unchanged) return;
1467
1666
  if (event !== "change") structural = true;
1468
- changed.add(import_path4.default.relative(root, file).split(import_path4.default.sep).join("/"));
1667
+ changed.add(import_path5.default.relative(root, file).split(import_path5.default.sep).join("/"));
1469
1668
  if (timer) clearTimeout(timer);
1470
1669
  timer = setTimeout(flush, DEBOUNCE);
1471
1670
  });
@@ -1479,7 +1678,7 @@ function updateFingerprint(fingerprints, file, event) {
1479
1678
  if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
1480
1679
  let next;
1481
1680
  try {
1482
- next = (0, import_crypto3.createHash)("sha1").update(import_fs4.default.readFileSync(file)).digest("hex");
1681
+ next = (0, import_crypto3.createHash)("sha1").update(import_fs5.default.readFileSync(file)).digest("hex");
1483
1682
  } catch {
1484
1683
  return false;
1485
1684
  }
@@ -1533,7 +1732,12 @@ var DevServer = class {
1533
1732
  /** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
1534
1733
  this.fileSessions = /* @__PURE__ */ new Map();
1535
1734
  this.logger = options.logger || console;
1536
- this.project = new MiniProgram(import_path5.default.resolve(options.projectPath));
1735
+ this.project = new MiniProgram(import_path6.default.resolve(options.projectPath));
1736
+ for (const { plugin, files } of ignoredStyleSources(this.project, this.project.styleSetting)) {
1737
+ this.logger.warn(
1738
+ `[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`
1739
+ );
1740
+ }
1537
1741
  const extended = applyExtensions(options, options.extensions ?? [], { projectPath: this.project.projectPath, root: this.project.root });
1538
1742
  extended.middlewares.forEach(({ path: mountPath }) => assertMountPath(mountPath));
1539
1743
  this.webviewScripts = extended.webviewScripts;
@@ -1563,7 +1767,7 @@ var DevServer = class {
1563
1767
  get networkUrls() {
1564
1768
  if (!this.lanAccess) return [];
1565
1769
  const { port } = new URL(this.address);
1566
- return Object.values(import_os2.default.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
1770
+ return Object.values(import_os3.default.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
1567
1771
  }
1568
1772
  async start() {
1569
1773
  try {
@@ -1681,7 +1885,7 @@ ${error.message}`);
1681
1885
  app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
1682
1886
  const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
1683
1887
  if (!file) return next();
1684
- res.type(import_path5.default.extname(req.params[1]) || "application/octet-stream").send(file);
1888
+ res.type(import_path6.default.extname(req.params[1]) || "application/octet-stream").send(file);
1685
1889
  }));
1686
1890
  app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
1687
1891
  const id = decodeURIComponent(req.params[0]);
@@ -1692,7 +1896,7 @@ ${error.message}`);
1692
1896
  res.type("js").send(this.builder.getModuleScript(id));
1693
1897
  }));
1694
1898
  app.get("/__wxa__/worker.js", this.withBuild((build2, _req, res) => {
1695
- const hasWorker = import_fs5.default.existsSync(import_path5.default.join(this.baseLib.dir, "WAWorker.js"));
1899
+ const hasWorker = import_fs6.default.existsSync(import_path6.default.join(this.baseLib.dir, "WAWorker.js"));
1696
1900
  res.type("js").send(workerScript(build2, { version: this.baseLib.version, hasWorker }));
1697
1901
  }));
1698
1902
  app.get("/__wxa__/service-bootstrap.js", this.withBuild((build2, _req, res) => {
@@ -1711,28 +1915,28 @@ ${error.message}`);
1711
1915
  res.type("js").send(build2.pageFrame);
1712
1916
  }));
1713
1917
  app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
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")));
1918
+ const file = import_path6.default.join(this.runtimeDir(), "frame.js.map");
1919
+ if (!import_fs6.default.existsSync(file)) return next();
1920
+ res.type("json").send(ignoreConsoleHook(import_fs6.default.readFileSync(file, "utf8")));
1717
1921
  });
1718
1922
  app.use("/__wxa__/runtime", import_express2.default.static(this.runtimeDir(), {
1719
1923
  cacheControl: false,
1720
1924
  // Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
1721
1925
  setHeaders: (res, file) => {
1722
- if (import_path5.default.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
1926
+ if (import_path6.default.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
1723
1927
  }
1724
1928
  }));
1725
1929
  const sourceMaps = /* @__PURE__ */ new Map();
1726
1930
  app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
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`));
1931
+ const file = import_path6.default.resolve(this.baseLib.dir, req.params[0]);
1932
+ if (!file.startsWith(this.baseLib.dir + import_path6.default.sep) || !import_fs6.default.existsSync(file)) return next();
1933
+ if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(import_fs6.default.readFileSync(file, "utf8"), `${import_path6.default.basename(file)}?original`));
1730
1934
  res.type("json").send(sourceMaps.get(file));
1731
1935
  });
1732
1936
  app.use("/__baselib__", import_express2.default.static(this.baseLib.dir, {
1733
1937
  maxAge: "1d",
1734
1938
  setHeaders: (res, file) => {
1735
- if (file.endsWith(".js")) res.setHeader("SourceMap", `${import_path5.default.basename(file)}.map`);
1939
+ if (file.endsWith(".js")) res.setHeader("SourceMap", `${import_path6.default.basename(file)}.map`);
1736
1940
  }
1737
1941
  }));
1738
1942
  app.use(PROXY_PREFIX, import_express2.default.raw({ type: () => true, limit: "100mb" }), handleProxy);
@@ -1792,7 +1996,7 @@ ${error.message}`);
1792
1996
  return;
1793
1997
  }
1794
1998
  const data = node.data;
1795
- res.type(import_path5.default.extname(req.params[0]));
1999
+ res.type(import_path6.default.extname(req.params[0]));
1796
2000
  res.setHeader("ETag", `"${node.etag}"`);
1797
2001
  res.setHeader("Accept-Ranges", "bytes");
1798
2002
  const ranges = req.range(data.length);
@@ -1862,11 +2066,11 @@ ${error.message}`);
1862
2066
  runtimeDir() {
1863
2067
  const candidates = [
1864
2068
  this.options.runtimeDir,
1865
- import_path5.default.join(__dirname, "runtime"),
1866
- import_path5.default.join(__dirname, "../runtime"),
1867
- import_path5.default.join(__dirname, "../../dist/runtime")
2069
+ import_path6.default.join(__dirname, "runtime"),
2070
+ import_path6.default.join(__dirname, "../runtime"),
2071
+ import_path6.default.join(__dirname, "../../dist/runtime")
1868
2072
  ].filter((dir) => !!dir);
1869
- const found = candidates.find((dir) => import_fs5.default.existsSync(import_path5.default.join(dir, "host.js")));
2073
+ const found = candidates.find((dir) => import_fs6.default.existsSync(import_path6.default.join(dir, "host.js")));
1870
2074
  if (!found) {
1871
2075
  throw new Error("\u627E\u4E0D\u5230\u6D4F\u89C8\u5668\u7AEF\u8FD0\u884C\u65F6\uFF08host.js\uFF09\uFF0C\u8BF7\u5148\u6267\u884C pnpm build");
1872
2076
  }
@@ -1879,7 +2083,10 @@ ${error.message}`);
1879
2083
  startWatching() {
1880
2084
  let lastBuildFailed = false;
1881
2085
  const watched = [this.project.root];
1882
- if (this.setupFile && import_path5.default.relative(this.project.root, this.setupFile).startsWith("..")) watched.push(this.setupFile);
2086
+ const outsideRoot = (file) => import_path6.default.relative(this.project.root, file).startsWith("..");
2087
+ if (this.setupFile && outsideRoot(this.setupFile)) watched.push(this.setupFile);
2088
+ const projectConfig = import_path6.default.join(this.project.projectPath, "project.config.json");
2089
+ if (outsideRoot(projectConfig) && import_fs6.default.existsSync(projectConfig)) watched.push(projectConfig);
1883
2090
  this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {
1884
2091
  const reloadOnly = structural || lastBuildFailed;
1885
2092
  this.builder.invalidate(files);
@@ -1951,8 +2158,8 @@ function assertMountPath(mountPath) {
1951
2158
  function resolveSetupFile(option, projectPath) {
1952
2159
  if (option === false) return null;
1953
2160
  if (!option) return findSetupFile(projectPath);
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}`);
2161
+ const file = import_path6.default.resolve(option);
2162
+ if (!import_fs6.default.existsSync(file)) throw new Error(`\u627E\u4E0D\u5230 setup \u6587\u4EF6\uFF1A${file}`);
1956
2163
  return file;
1957
2164
  }
1958
2165
  // Annotate the CommonJS export names for ESM import in node: