@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.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
- switch (operation) {
78
- case "readFile": {
79
- const data = this.read(args.filePath);
80
- return args.encoding === void 0 ? { data: data.toString("base64"), binary: true } : { data: data.toString(args.encoding) };
81
- }
82
- case "writeFile":
83
- case "appendFile": {
84
- const data = Buffer.from(args.data ?? "", args.binary ? "base64" : args.encoding || "utf8");
85
- this.write(args.filePath, data, operation === "appendFile");
86
- return {};
87
- }
88
- case "copyFile":
89
- this.write(args.destPath, this.read(args.srcPath));
90
- return {};
91
- case "access":
92
- if (!this.stat(args.path)) throw new Error(`no such file or directory ${args.path}`);
93
- return {};
94
- case "stat": {
95
- const node = this.stat(args.path);
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 fs5 from "fs";
143
+ import fs6 from "fs";
132
144
  import http from "http";
133
145
  import net from "net";
134
- import os2 from "os";
135
- import path5 from "path";
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 fs from "fs";
202
- import os from "os";
203
- import path from "path";
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 ??= fs.mkdtempSync(path.join(os.tmpdir(), "wxa-runner-wxml-"));
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
- fs.rmSync(path.join(dir, rel), { force: true });
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 = path.join(dir, rel);
231
- fs.mkdirSync(path.dirname(target), { recursive: true });
232
- fs.writeFileSync(target, rel.endsWith(".wxml") ? this.transform(source, rel) : source);
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) fs.rmSync(this.dir, { recursive: true, force: true });
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 pageFiles = units.filter((id) => this.project.exists(id + ".wxss")).map((id) => `./${id}.wxss`);
423
- const commonFiles = this.project.listFiles([".wxss"]).map((file) => `./${file}`).filter((file) => !pageFiles.includes(file));
424
- const { comm, pages } = await compileWxss({ cwd: this.project.root, pageFiles, commonFiles });
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 (file.endsWith(".wxss")) return "wxss";
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 fs2 from "fs";
481
- import path2 from "path";
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(path2.join(projectPath, "project.config.json")) || {};
488
- this.root = path2.resolve(projectPath, projectConfig.miniprogramRoot || "");
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 (!fs2.existsSync(path2.join(this.root, "app.json"))) {
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(path2.join(this.root, "app.json"));
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 ? path2.posix.normalize(dir).replace(/^\/+|\/+$/g, "") : null;
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 = path2.posix.join(pkg.root, page);
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(path2.join(this.root, id + ".json")) };
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 = path2.posix.relative(path2.posix.dirname(id), ref);
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 = path2.posix.dirname(fromId);
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(path2.posix.join(fromDir, ref));
754
+ candidates.push(path3.posix.join(fromDir, ref));
566
755
  if (!ref.startsWith(".")) {
567
756
  let dir = fromDir;
568
757
  for (; ; ) {
569
- candidates.push(path2.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
758
+ candidates.push(path3.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
570
759
  if (dir === "." || dir === "") break;
571
- dir = path2.posix.dirname(dir);
760
+ dir = path3.posix.dirname(dir);
572
761
  }
573
762
  }
574
763
  }
575
764
  for (const candidate of candidates) {
576
- const normalized = path2.posix.normalize(candidate);
577
- for (const id of [normalized, path2.posix.join(normalized, "index")]) {
578
- if (fs2.existsSync(path2.join(this.root, id + ".json"))) return id;
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 fs2.readdirSync(dir, { withFileTypes: true })) {
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(path2.join(dir, entry.name), childRel);
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 fs2.existsSync(path2.join(this.root, relPath));
814
+ return fs3.existsSync(path3.join(this.root, relPath));
626
815
  }
627
816
  readText(relPath) {
628
- return fs2.readFileSync(path2.join(this.root, relPath), "utf8");
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 (!fs2.existsSync(file)) return null;
636
- const text = fs2.readFileSync(file, "utf8");
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(path6, body, timeout = DEFAULT_TIMEOUT) {
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}/${path6}?${query}`, {
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(path6, body) {
717
- return (await this.post(path6, body)).json();
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.status(400).set("x-wxa-proxy-error", "1").send(`invalid url "${req.originalUrl}"`);
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: hasBody ? req.body : void 0,
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
- const cause = error?.cause?.message;
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
- const responseHeaders = {};
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 = file.endsWith(".wxss") ? plan.styles : !owners.has(id) ? null : file.endsWith(".wxml") ? plan.templates : file.endsWith(".js") ? plan.scripts : null;
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 fs3 from "fs";
1355
- import path3 from "path";
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 = path3.join(projectPath, name);
1363
- if (fs3.existsSync(file)) return file;
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: path3.dirname(file),
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 fs4 from "fs";
1404
- import path4 from "path";
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(path4.relative(root, file).split(path4.sep).join("/"));
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(fs4.readFileSync(file)).digest("hex");
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(path5.resolve(options.projectPath));
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(os2.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
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(path5.extname(req.params[1]) || "application/octet-stream").send(file);
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 = fs5.existsSync(path5.join(this.baseLib.dir, "WAWorker.js"));
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 = path5.join(this.runtimeDir(), "frame.js.map");
1679
- if (!fs5.existsSync(file)) return next();
1680
- res.type("json").send(ignoreConsoleHook(fs5.readFileSync(file, "utf8")));
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 (path5.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
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 = path5.resolve(this.baseLib.dir, req.params[0]);
1692
- if (!file.startsWith(this.baseLib.dir + path5.sep) || !fs5.existsSync(file)) return next();
1693
- if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(fs5.readFileSync(file, "utf8"), `${path5.basename(file)}?original`));
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", `${path5.basename(file)}.map`);
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(path5.extname(req.params[0]));
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
- path5.join(__dirname, "runtime"),
1830
- path5.join(__dirname, "../runtime"),
1831
- path5.join(__dirname, "../../dist/runtime")
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) => fs5.existsSync(path5.join(dir, "host.js")));
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
- if (this.setupFile && path5.relative(this.project.root, this.setupFile).startsWith("..")) watched.push(this.setupFile);
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 = path5.resolve(option);
1919
- if (!fs5.existsSync(file)) throw new Error(`\u627E\u4E0D\u5230 setup \u6587\u4EF6\uFF1A${file}`);
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 {