@open-wxa/wxa-runner-core 0.8.2 → 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.d.mts +119 -3
- package/dist/index.d.ts +119 -3
- package/dist/index.js +478 -170
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +478 -170
- 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 +63 -44
- 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",
|
|
@@ -197,6 +367,50 @@ function createWxConfig(input) {
|
|
|
197
367
|
};
|
|
198
368
|
}
|
|
199
369
|
|
|
370
|
+
// src/project/WxmlMirror.ts
|
|
371
|
+
import fs2 from "fs";
|
|
372
|
+
import os2 from "os";
|
|
373
|
+
import path2 from "path";
|
|
374
|
+
var WxmlMirror = class {
|
|
375
|
+
constructor(transform) {
|
|
376
|
+
this.transform = transform;
|
|
377
|
+
this.dir = null;
|
|
378
|
+
/** 已写入的文件:源内容(判断要不要重新转换)与写入的内容 */
|
|
379
|
+
this.files = /* @__PURE__ */ new Map();
|
|
380
|
+
}
|
|
381
|
+
/** 镜像目录;还没同步过时为 null */
|
|
382
|
+
get directory() {
|
|
383
|
+
return this.dir;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* 让镜像目录与项目里的 wxml / wxs 一致,返回镜像目录。
|
|
387
|
+
* 只重写源内容变化的文件;项目里已删除的文件同时从镜像目录删除,被引用时 wcc 照常报错
|
|
388
|
+
*/
|
|
389
|
+
sync(project, relPaths) {
|
|
390
|
+
const dir = this.dir ??= fs2.mkdtempSync(path2.join(os2.tmpdir(), "wxa-runner-wxml-"));
|
|
391
|
+
const current = new Set(relPaths);
|
|
392
|
+
for (const rel of [...this.files.keys()]) {
|
|
393
|
+
if (current.has(rel)) continue;
|
|
394
|
+
fs2.rmSync(path2.join(dir, rel), { force: true });
|
|
395
|
+
this.files.delete(rel);
|
|
396
|
+
}
|
|
397
|
+
for (const rel of relPaths) {
|
|
398
|
+
const source = project.readText(rel);
|
|
399
|
+
if (this.files.get(rel) === source) continue;
|
|
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);
|
|
403
|
+
this.files.set(rel, source);
|
|
404
|
+
}
|
|
405
|
+
return dir;
|
|
406
|
+
}
|
|
407
|
+
dispose() {
|
|
408
|
+
if (this.dir) fs2.rmSync(this.dir, { recursive: true, force: true });
|
|
409
|
+
this.dir = null;
|
|
410
|
+
this.files.clear();
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
200
414
|
// src/project/AppBuilder.ts
|
|
201
415
|
var MODULE_PARAMS = [
|
|
202
416
|
"require",
|
|
@@ -223,7 +437,7 @@ var MODULE_PARAMS = [
|
|
|
223
437
|
].join(", ");
|
|
224
438
|
var ESM_SYNTAX = /(?:^|[;}])\s*(?:import\s*[{*'"]|import\s+[\w$]|export\s*[{*]|export\s+(?:default|const|let|var|function|class|async)\b)/m;
|
|
225
439
|
var AppBuilder = class {
|
|
226
|
-
constructor(project) {
|
|
440
|
+
constructor(project, options = {}) {
|
|
227
441
|
this.project = project;
|
|
228
442
|
this.version = 0;
|
|
229
443
|
/** invalidate 标记过的变化次数;每次构建记下开始时覆盖到第几次 */
|
|
@@ -233,6 +447,18 @@ var AppBuilder = class {
|
|
|
233
447
|
this.dirty = /* @__PURE__ */ new Set(["config", "wxml", "wxss", "js"]);
|
|
234
448
|
this.wxmlCache = null;
|
|
235
449
|
this.wxssCache = null;
|
|
450
|
+
/** 开启了 less / sass 编译插件时才有;插件设置变了就换一个 */
|
|
451
|
+
this.styleMirror = null;
|
|
452
|
+
this.wxmlMirror = options.wxmlTransform ? new WxmlMirror(options.wxmlTransform) : null;
|
|
453
|
+
}
|
|
454
|
+
/** wxmlTransform 的镜像目录;没有设置转换或还没编译过时为 null */
|
|
455
|
+
get wxmlMirrorDir() {
|
|
456
|
+
return this.wxmlMirror?.directory ?? null;
|
|
457
|
+
}
|
|
458
|
+
/** 删除 wxmlTransform 和样式预处理的镜像目录,开发服务器关闭时调用 */
|
|
459
|
+
dispose() {
|
|
460
|
+
this.wxmlMirror?.dispose();
|
|
461
|
+
this.styleMirror?.mirror.dispose();
|
|
236
462
|
}
|
|
237
463
|
/** 根据变化的文件标记需要重新构建的部分;返回 true 表示需要刷新 */
|
|
238
464
|
invalidate(changedFiles = []) {
|
|
@@ -357,7 +583,7 @@ ${pageStyles}`,
|
|
|
357
583
|
const json = unitJson.get(id);
|
|
358
584
|
return { path: id, tags: Object.keys({ ...json.usingComponents, ...json.componentGenerics }) };
|
|
359
585
|
});
|
|
360
|
-
const cwd = this.project.root;
|
|
586
|
+
const cwd = this.wxmlMirror ? this.wxmlMirror.sync(this.project, files.map((file) => file.slice(2))) : this.project.root;
|
|
361
587
|
const [full, cut] = await Promise.all([
|
|
362
588
|
compileWxml({ cwd, files, components, cut: false }),
|
|
363
589
|
compileWxml({ cwd, files, components, cut: true })
|
|
@@ -366,14 +592,26 @@ ${pageStyles}`,
|
|
|
366
592
|
}
|
|
367
593
|
/** 与开发者工具一致,项目里的 wxss 全部交给 wcsc:被 @import 的文件(如 uni-app 的 common/main.wxss)也要在列表里 */
|
|
368
594
|
async compileStyles(units) {
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
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 });
|
|
372
601
|
return {
|
|
373
602
|
comm: rewriteSafeAreaInsets(comm),
|
|
374
603
|
pages: Object.fromEntries(Object.entries(pages).map(([file, css]) => [file, rewriteSafeAreaInsets(css)]))
|
|
375
604
|
};
|
|
376
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
|
+
}
|
|
377
615
|
/** $gwx 加上各页面、组件的 json / wxml 注册;逻辑层用裁剪版 $gwx,渲染层用完整版 */
|
|
378
616
|
createTemplates(app, units, unitJson, gwx) {
|
|
379
617
|
const lines = [gwx, `__wxAppCode__["app.json"] = ${toJs(app)};`];
|
|
@@ -418,28 +656,32 @@ function toJs(value) {
|
|
|
418
656
|
function classifyFile(file) {
|
|
419
657
|
if (file.endsWith(".json")) return "config";
|
|
420
658
|
if (file.endsWith(".wxml") || file.endsWith(".wxs")) return "wxml";
|
|
421
|
-
if (
|
|
659
|
+
if (/\.(wxss|less|scss|sass|css)$/.test(file)) return "wxss";
|
|
422
660
|
if (file.endsWith(".js")) return "js";
|
|
423
661
|
return null;
|
|
424
662
|
}
|
|
425
663
|
|
|
426
664
|
// src/project/MiniProgram.ts
|
|
427
|
-
import
|
|
428
|
-
import
|
|
665
|
+
import fs3 from "fs";
|
|
666
|
+
import path3 from "path";
|
|
429
667
|
var IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".idea", ".vscode"]);
|
|
430
668
|
var PROJECT_FILES = /* @__PURE__ */ new Set(["project.config.json", "project.private.config.json", "sitemap.json", "app.json", "wxa.setup.js"]);
|
|
431
669
|
var MiniProgram = class {
|
|
432
670
|
constructor(projectPath) {
|
|
433
671
|
this.projectPath = projectPath;
|
|
434
|
-
const projectConfig = readJsonFile(
|
|
435
|
-
this.root =
|
|
672
|
+
const projectConfig = readJsonFile(path3.join(projectPath, "project.config.json")) || {};
|
|
673
|
+
this.root = path3.resolve(projectPath, projectConfig.miniprogramRoot || "");
|
|
436
674
|
this.appid = projectConfig.appid || "touristappid";
|
|
437
|
-
if (!
|
|
675
|
+
if (!fs3.existsSync(path3.join(this.root, "app.json"))) {
|
|
438
676
|
throw new Error(`${this.root} \u4E0B\u6CA1\u6709 app.json\uFF0C\u4E0D\u662F\u6709\u6548\u7684\u5C0F\u7A0B\u5E8F\u76EE\u5F55`);
|
|
439
677
|
}
|
|
440
678
|
}
|
|
679
|
+
/** project.config.json 里与 less / sass 编译插件有关的设置;每次重新读取,开发中修改了也能生效 */
|
|
680
|
+
get styleSetting() {
|
|
681
|
+
return readStyleSetting(readJsonFile(path3.join(this.projectPath, "project.config.json"))?.setting);
|
|
682
|
+
}
|
|
441
683
|
readAppJson() {
|
|
442
|
-
const app = readJsonFile(
|
|
684
|
+
const app = readJsonFile(path3.join(this.root, "app.json"));
|
|
443
685
|
if (!app || !Array.isArray(app.pages) || app.pages.length === 0) {
|
|
444
686
|
throw new Error("app.json \u7F3A\u5C11 pages \u914D\u7F6E");
|
|
445
687
|
}
|
|
@@ -451,14 +693,14 @@ var MiniProgram = class {
|
|
|
451
693
|
/** Worker 代码所在目录,不带首尾的 /;没有配置 workers 时为 null */
|
|
452
694
|
workersDir(app) {
|
|
453
695
|
const dir = typeof app.workers === "string" ? app.workers : app.workers?.path;
|
|
454
|
-
return dir ?
|
|
696
|
+
return dir ? path3.posix.normalize(dir).replace(/^\/+|\/+$/g, "") : null;
|
|
455
697
|
}
|
|
456
698
|
/** 主包页面 + 分包页面(带 root 前缀) */
|
|
457
699
|
allPages(app) {
|
|
458
700
|
const pages = [...app.pages];
|
|
459
701
|
for (const pkg of this.subPackages(app)) {
|
|
460
702
|
for (const page of pkg.pages) {
|
|
461
|
-
const full =
|
|
703
|
+
const full = path3.posix.join(pkg.root, page);
|
|
462
704
|
if (!pages.includes(full)) pages.push(full);
|
|
463
705
|
}
|
|
464
706
|
}
|
|
@@ -470,7 +712,7 @@ var MiniProgram = class {
|
|
|
470
712
|
* 所以能解析到的引用都换成组件的绝对路径
|
|
471
713
|
*/
|
|
472
714
|
readUnitJson(id, app) {
|
|
473
|
-
const json = { ...readJsonFile(
|
|
715
|
+
const json = { ...readJsonFile(path3.join(this.root, id + ".json")) };
|
|
474
716
|
const resolve = (fromId, ref) => {
|
|
475
717
|
const resolved = this.resolveComponent(fromId, ref);
|
|
476
718
|
return resolved ? "/" + resolved : null;
|
|
@@ -491,7 +733,7 @@ var MiniProgram = class {
|
|
|
491
733
|
if (resolved || ref.startsWith("/") || /^plugin(-private)?:\/\//.test(ref)) {
|
|
492
734
|
json.usingComponents[tag] = resolved ?? ref;
|
|
493
735
|
} else {
|
|
494
|
-
const relative =
|
|
736
|
+
const relative = path3.posix.relative(path3.posix.dirname(id), ref);
|
|
495
737
|
json.usingComponents[tag] = relative.startsWith(".") ? relative : "./" + relative;
|
|
496
738
|
}
|
|
497
739
|
}
|
|
@@ -504,25 +746,25 @@ var MiniProgram = class {
|
|
|
504
746
|
*/
|
|
505
747
|
resolveComponent(fromId, ref) {
|
|
506
748
|
if (!ref || ref.startsWith("plugin://") || ref.startsWith("plugin-private://")) return null;
|
|
507
|
-
const fromDir =
|
|
749
|
+
const fromDir = path3.posix.dirname(fromId);
|
|
508
750
|
const candidates = [];
|
|
509
751
|
if (ref.startsWith("/")) {
|
|
510
752
|
candidates.push(ref.slice(1));
|
|
511
753
|
} else {
|
|
512
|
-
candidates.push(
|
|
754
|
+
candidates.push(path3.posix.join(fromDir, ref));
|
|
513
755
|
if (!ref.startsWith(".")) {
|
|
514
756
|
let dir = fromDir;
|
|
515
757
|
for (; ; ) {
|
|
516
|
-
candidates.push(
|
|
758
|
+
candidates.push(path3.posix.join(dir === "." ? "" : dir, "miniprogram_npm", ref));
|
|
517
759
|
if (dir === "." || dir === "") break;
|
|
518
|
-
dir =
|
|
760
|
+
dir = path3.posix.dirname(dir);
|
|
519
761
|
}
|
|
520
762
|
}
|
|
521
763
|
}
|
|
522
764
|
for (const candidate of candidates) {
|
|
523
|
-
const normalized =
|
|
524
|
-
for (const id of [normalized,
|
|
525
|
-
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;
|
|
526
768
|
}
|
|
527
769
|
}
|
|
528
770
|
return null;
|
|
@@ -554,11 +796,11 @@ var MiniProgram = class {
|
|
|
554
796
|
listFiles(extensions) {
|
|
555
797
|
const result = [];
|
|
556
798
|
const walk = (dir, rel) => {
|
|
557
|
-
for (const entry of
|
|
799
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
558
800
|
if (entry.name.startsWith(".") || IGNORED_DIRS.has(entry.name)) continue;
|
|
559
801
|
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
560
802
|
if (entry.isDirectory()) {
|
|
561
|
-
walk(
|
|
803
|
+
walk(path3.join(dir, entry.name), childRel);
|
|
562
804
|
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
563
805
|
if (!rel && PROJECT_FILES.has(entry.name)) continue;
|
|
564
806
|
result.push(childRel);
|
|
@@ -569,18 +811,18 @@ var MiniProgram = class {
|
|
|
569
811
|
return result.sort();
|
|
570
812
|
}
|
|
571
813
|
exists(relPath) {
|
|
572
|
-
return
|
|
814
|
+
return fs3.existsSync(path3.join(this.root, relPath));
|
|
573
815
|
}
|
|
574
816
|
readText(relPath) {
|
|
575
|
-
return
|
|
817
|
+
return fs3.readFileSync(path3.join(this.root, relPath), "utf8");
|
|
576
818
|
}
|
|
577
819
|
isInIndependentSubPackage(id, app) {
|
|
578
820
|
return this.subPackages(app).some((pkg) => pkg.independent && id.startsWith(pkg.root.replace(/\/?$/, "/")));
|
|
579
821
|
}
|
|
580
822
|
};
|
|
581
823
|
function readJsonFile(file) {
|
|
582
|
-
if (!
|
|
583
|
-
const text =
|
|
824
|
+
if (!fs3.existsSync(file)) return null;
|
|
825
|
+
const text = fs3.readFileSync(file, "utf8");
|
|
584
826
|
try {
|
|
585
827
|
return JSON.parse(text);
|
|
586
828
|
} catch (error) {
|
|
@@ -646,9 +888,9 @@ var WechatDevClient = class {
|
|
|
646
888
|
*
|
|
647
889
|
* @throws 没有可用的登录态、超时、连不上、HTTP 状态不是 2xx 时,消息是给用户看的原因
|
|
648
890
|
*/
|
|
649
|
-
async post(
|
|
891
|
+
async post(path7, body, timeout = DEFAULT_TIMEOUT) {
|
|
650
892
|
const query = new URLSearchParams({ appid: this.appid, newticket: requireUsableTicket(await this.findTicket()), clientversion: CLIENT_VERSION });
|
|
651
|
-
const response = await fetch(`${BASE_URL}/${
|
|
893
|
+
const response = await fetch(`${BASE_URL}/${path7}?${query}`, {
|
|
652
894
|
method: "POST",
|
|
653
895
|
headers: { "Content-Type": "application/json" },
|
|
654
896
|
body: JSON.stringify(body),
|
|
@@ -660,8 +902,8 @@ var WechatDevClient = class {
|
|
|
660
902
|
return response;
|
|
661
903
|
}
|
|
662
904
|
/** 同 post,返回解析后的 JSON */
|
|
663
|
-
async postJson(
|
|
664
|
-
return (await this.post(
|
|
905
|
+
async postJson(path7, body) {
|
|
906
|
+
return (await this.post(path7, body)).json();
|
|
665
907
|
}
|
|
666
908
|
};
|
|
667
909
|
|
|
@@ -857,12 +1099,12 @@ function webviewHtml(build2, pagePath, webviewId, options) {
|
|
|
857
1099
|
return `<!DOCTYPE html>
|
|
858
1100
|
<html>
|
|
859
1101
|
<head>
|
|
860
|
-
${webviewHead(build2, pagePath, id)}
|
|
1102
|
+
${webviewHead(build2, pagePath, id, options.scripts)}
|
|
861
1103
|
${enableHotUpdate(options.hot)}<script src="/__wxa__/page-frame.js?v=${build2.version}"></script>
|
|
862
1104
|
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
863
1105
|
</head>
|
|
864
1106
|
<body>
|
|
865
|
-
<script>
|
|
1107
|
+
${scriptTags(options.scripts, "body")}<script>
|
|
866
1108
|
(function () {
|
|
867
1109
|
var path = ${toJs(pagePath)};
|
|
868
1110
|
if (typeof __wxAppCode__[path + '.wxss'] === 'function') __wxAppCode__[path + '.wxss']();
|
|
@@ -895,18 +1137,18 @@ self.console = __wxaConsole;
|
|
|
895
1137
|
importScripts(${modules.map((url) => toJs(url)).join(", ")});
|
|
896
1138
|
`;
|
|
897
1139
|
}
|
|
898
|
-
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId) {
|
|
1140
|
+
function pluginWebviewHtml(build2, plugin, innerPath, pagePath, webviewId, options = {}) {
|
|
899
1141
|
const id = Number(webviewId) || 0;
|
|
900
1142
|
const page = plugin.files.get(`/${innerPath}.html`)?.toString("utf8") ?? `<script>console.error(${JSON.stringify(`[wxa-runner] \u63D2\u4EF6 ${plugin.alias} \u91CC\u6CA1\u6709\u9875\u9762 ${innerPath}`)})</script>`;
|
|
901
1143
|
return `<!DOCTYPE html>
|
|
902
1144
|
<html>
|
|
903
1145
|
<head>
|
|
904
|
-
${webviewHead(build2, pagePath, id)}
|
|
1146
|
+
${webviewHead(build2, pagePath, id, options.scripts)}
|
|
905
1147
|
<script src="${pluginScriptUrl(plugin, "pageframe.js")}"></script>
|
|
906
1148
|
${pageWindow(build2, pagePath).disableScroll ? "<style>body{overflow-y:hidden}</style>" : ""}
|
|
907
1149
|
</head>
|
|
908
1150
|
<body>
|
|
909
|
-
${page}
|
|
1151
|
+
${scriptTags(options.scripts, "body")}${page}
|
|
910
1152
|
<script>parent.__wxaHost.frameLoaded('webview', ${id});</script>
|
|
911
1153
|
</body>
|
|
912
1154
|
</html>`;
|
|
@@ -914,7 +1156,7 @@ ${page}
|
|
|
914
1156
|
function pageWindow(build2, pagePath) {
|
|
915
1157
|
return { ...build2.wxConfig.global?.window, ...build2.wxConfig.page?.[pagePath + ".html"]?.window };
|
|
916
1158
|
}
|
|
917
|
-
function webviewHead(build2, pagePath, id) {
|
|
1159
|
+
function webviewHead(build2, pagePath, id, scripts) {
|
|
918
1160
|
const wxConfig = { ...build2.wxConfig, window: pageWindow(build2, pagePath) };
|
|
919
1161
|
return `<meta charset="utf-8">
|
|
920
1162
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
@@ -929,7 +1171,12 @@ function webviewHead(build2, pagePath, id) {
|
|
|
929
1171
|
<style>.wxa-service-canvas{position:absolute!important;left:0!important;top:0!important;width:100%!important;height:100%!important;visibility:inherit!important;pointer-events:none}</style>
|
|
930
1172
|
<script>var __wxConfig = ${toJs(wxConfig)};</script>
|
|
931
1173
|
<script src="/__wxa__/runtime/frame.js" data-kind="webview" data-webview-id="${id}"></script>
|
|
932
|
-
<script src="/__baselib__/WAWebview.js"></script>`;
|
|
1174
|
+
${scriptTags(scripts, "head")}<script src="/__baselib__/WAWebview.js"></script>`;
|
|
1175
|
+
}
|
|
1176
|
+
function scriptTags(scripts, position) {
|
|
1177
|
+
return (scripts ?? []).filter((script) => (script.position ?? "body") === position).map((script) => script.src !== void 0 ? `<script src="${escapeHtml(script.src)}"></script>
|
|
1178
|
+
` : `<script>${script.content.replace(/<\/script/gi, "<\\/script")}</script>
|
|
1179
|
+
`).join("");
|
|
933
1180
|
}
|
|
934
1181
|
function escapeHtml(text) {
|
|
935
1182
|
return text.replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch]);
|
|
@@ -1018,28 +1265,9 @@ var DROPPED_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-encoding", "con
|
|
|
1018
1265
|
async function handleProxy(req, res) {
|
|
1019
1266
|
const target = fromServerPath(PROXY_PREFIX, req.originalUrl);
|
|
1020
1267
|
if (!target) {
|
|
1021
|
-
res
|
|
1268
|
+
sendProxyError(res, 400, `invalid url "${req.originalUrl}"`);
|
|
1022
1269
|
return;
|
|
1023
1270
|
}
|
|
1024
|
-
let requestHeaders = {};
|
|
1025
|
-
try {
|
|
1026
|
-
requestHeaders = JSON.parse(req.get("x-wxa-headers") || "{}");
|
|
1027
|
-
} catch {
|
|
1028
|
-
}
|
|
1029
|
-
const headers = {};
|
|
1030
|
-
for (const [key, value] of Object.entries(requestHeaders)) {
|
|
1031
|
-
if (!DROPPED_REQUEST_HEADERS.has(key.toLowerCase()) && value !== void 0 && value !== null) {
|
|
1032
|
-
headers[key] = String(value);
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
const browserContentType = req.get("content-type") || "";
|
|
1036
|
-
if (browserContentType.startsWith("multipart/form-data")) {
|
|
1037
|
-
for (const key of Object.keys(headers)) {
|
|
1038
|
-
if (key.toLowerCase() === "content-type") delete headers[key];
|
|
1039
|
-
}
|
|
1040
|
-
headers["content-type"] = browserContentType;
|
|
1041
|
-
}
|
|
1042
|
-
const hasBody = !["GET", "HEAD"].includes(req.method) && Buffer.isBuffer(req.body) && req.body.length > 0;
|
|
1043
1271
|
const controller = new AbortController();
|
|
1044
1272
|
const cancel = () => controller.abort();
|
|
1045
1273
|
res.once("close", cancel);
|
|
@@ -1048,48 +1276,77 @@ async function handleProxy(req, res) {
|
|
|
1048
1276
|
try {
|
|
1049
1277
|
upstream = await fetch(target, {
|
|
1050
1278
|
method: req.method,
|
|
1051
|
-
headers,
|
|
1052
|
-
body:
|
|
1279
|
+
headers: forwardedRequestHeaders(req.get("x-wxa-headers"), req.get("content-type")),
|
|
1280
|
+
body: requestBody(req),
|
|
1053
1281
|
redirect: "follow",
|
|
1054
1282
|
signal: controller.signal
|
|
1055
1283
|
});
|
|
1056
1284
|
} catch (error) {
|
|
1057
1285
|
if (res.destroyed || controller.signal.aborted) return;
|
|
1058
|
-
|
|
1059
|
-
res.status(502).set("x-wxa-proxy-error", "1").send(cause || error.message);
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
const responseHeaders = {};
|
|
1063
|
-
upstream.headers.forEach((value, key) => {
|
|
1064
|
-
responseHeaders[key] = value;
|
|
1065
|
-
});
|
|
1066
|
-
const setCookies = typeof upstream.headers.getSetCookie === "function" ? upstream.headers.getSetCookie() : [];
|
|
1067
|
-
res.status(upstream.status);
|
|
1068
|
-
upstream.headers.forEach((value, key) => {
|
|
1069
|
-
if (!DROPPED_RESPONSE_HEADERS.has(key)) res.setHeader(key, value);
|
|
1070
|
-
});
|
|
1071
|
-
res.setHeader("x-wxa-headers", encodeURIComponent(JSON.stringify(responseHeaders)));
|
|
1072
|
-
res.setHeader("x-wxa-set-cookie", encodeURIComponent(JSON.stringify(setCookies)));
|
|
1073
|
-
res.flushHeaders();
|
|
1074
|
-
if (!upstream.body) {
|
|
1075
|
-
res.end();
|
|
1286
|
+
sendProxyError(res, 502, fetchErrorMessage(error));
|
|
1076
1287
|
return;
|
|
1077
1288
|
}
|
|
1078
|
-
await
|
|
1289
|
+
await relayResponse(upstream, res);
|
|
1079
1290
|
} catch (error) {
|
|
1080
1291
|
if (!res.destroyed) res.destroy(error);
|
|
1081
1292
|
} finally {
|
|
1082
1293
|
res.off("close", cancel);
|
|
1083
1294
|
}
|
|
1084
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
|
+
}
|
|
1085
1341
|
|
|
1086
1342
|
// src/server/hmr.ts
|
|
1343
|
+
var STYLE_FILE = /\.(wxss|less|scss|sass|css)$/;
|
|
1087
1344
|
function planHotUpdate(files, units) {
|
|
1088
1345
|
const owners = /* @__PURE__ */ new Set([...units.pages, ...units.components]);
|
|
1089
1346
|
const plan = { templates: [], scripts: [], styles: [] };
|
|
1090
1347
|
for (const file of files) {
|
|
1091
|
-
const id = file.replace(/\.(wxml|js|wxss)$/, "");
|
|
1092
|
-
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;
|
|
1093
1350
|
if (!target) return null;
|
|
1094
1351
|
if (!target.includes(id)) target.push(id);
|
|
1095
1352
|
}
|
|
@@ -1293,16 +1550,16 @@ function respond(action) {
|
|
|
1293
1550
|
}
|
|
1294
1551
|
|
|
1295
1552
|
// src/server/setup.ts
|
|
1296
|
-
import
|
|
1297
|
-
import
|
|
1553
|
+
import fs4 from "fs";
|
|
1554
|
+
import path4 from "path";
|
|
1298
1555
|
import { build } from "esbuild";
|
|
1299
1556
|
var SETUP_FILE_NAMES = ["wxa.setup.ts", "wxa.setup.js", "wxa.setup.mjs"];
|
|
1300
1557
|
var SETUP_MODULE = "@open-wxa/wxa-runner-core/setup";
|
|
1301
1558
|
var ENV_PREFIX = "WXA_";
|
|
1302
1559
|
function findSetupFile(projectPath) {
|
|
1303
1560
|
for (const name of SETUP_FILE_NAMES) {
|
|
1304
|
-
const file =
|
|
1305
|
-
if (
|
|
1561
|
+
const file = path4.join(projectPath, name);
|
|
1562
|
+
if (fs4.existsSync(file)) return file;
|
|
1306
1563
|
}
|
|
1307
1564
|
return null;
|
|
1308
1565
|
}
|
|
@@ -1314,7 +1571,7 @@ function collectSetupEnv(env) {
|
|
|
1314
1571
|
async function bundleSetup(file) {
|
|
1315
1572
|
const result = await build({
|
|
1316
1573
|
entryPoints: [file],
|
|
1317
|
-
absWorkingDir:
|
|
1574
|
+
absWorkingDir: path4.dirname(file),
|
|
1318
1575
|
bundle: true,
|
|
1319
1576
|
format: "esm",
|
|
1320
1577
|
platform: "browser",
|
|
@@ -1342,12 +1599,12 @@ function builtinSetupModule() {
|
|
|
1342
1599
|
|
|
1343
1600
|
// src/server/watcher.ts
|
|
1344
1601
|
import { createHash } from "crypto";
|
|
1345
|
-
import
|
|
1346
|
-
import
|
|
1602
|
+
import fs5 from "fs";
|
|
1603
|
+
import path5 from "path";
|
|
1347
1604
|
import chokidar from "chokidar";
|
|
1348
1605
|
var DEBOUNCE = 120;
|
|
1349
1606
|
var IGNORED = /(^|[/\\])(node_modules|\.git)([/\\]|$)/;
|
|
1350
|
-
var FINGERPRINTED = /\.(js|mjs|ts|json|wxml|wxss|wxs)$/;
|
|
1607
|
+
var FINGERPRINTED = /\.(js|mjs|ts|json|wxml|wxss|wxs|less|scss|sass|css)$/;
|
|
1351
1608
|
function watchProject(root, paths, onChange) {
|
|
1352
1609
|
let changed = /* @__PURE__ */ new Set();
|
|
1353
1610
|
let structural = false;
|
|
@@ -1371,7 +1628,7 @@ function watchProject(root, paths, onChange) {
|
|
|
1371
1628
|
const unchanged = updateFingerprint(fingerprints, file, event);
|
|
1372
1629
|
if (!scanned || unchanged) return;
|
|
1373
1630
|
if (event !== "change") structural = true;
|
|
1374
|
-
changed.add(
|
|
1631
|
+
changed.add(path5.relative(root, file).split(path5.sep).join("/"));
|
|
1375
1632
|
if (timer) clearTimeout(timer);
|
|
1376
1633
|
timer = setTimeout(flush, DEBOUNCE);
|
|
1377
1634
|
});
|
|
@@ -1385,7 +1642,7 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1385
1642
|
if (event !== "add" && event !== "change" || !FINGERPRINTED.test(file)) return false;
|
|
1386
1643
|
let next;
|
|
1387
1644
|
try {
|
|
1388
|
-
next = createHash("sha1").update(
|
|
1645
|
+
next = createHash("sha1").update(fs5.readFileSync(file)).digest("hex");
|
|
1389
1646
|
} catch {
|
|
1390
1647
|
return false;
|
|
1391
1648
|
}
|
|
@@ -1394,7 +1651,30 @@ function updateFingerprint(fingerprints, file, event) {
|
|
|
1394
1651
|
return previous === next;
|
|
1395
1652
|
}
|
|
1396
1653
|
|
|
1654
|
+
// src/server/extensions.ts
|
|
1655
|
+
function applyExtensions(base, extensions, context) {
|
|
1656
|
+
const webviewScripts = [...base.webviewScripts ?? []];
|
|
1657
|
+
const middlewares = [...base.middlewares ?? []];
|
|
1658
|
+
const transforms = base.wxmlTransform ? [base.wxmlTransform] : [];
|
|
1659
|
+
const entries = [];
|
|
1660
|
+
for (const extension of extensions) {
|
|
1661
|
+
let setup;
|
|
1662
|
+
try {
|
|
1663
|
+
setup = extension.setup(context);
|
|
1664
|
+
} catch (error) {
|
|
1665
|
+
throw new Error(`\u6269\u5C55 ${extension.name} \u521D\u59CB\u5316\u5931\u8D25\uFF1A${error.message}`);
|
|
1666
|
+
}
|
|
1667
|
+
webviewScripts.push(...setup.webviewScripts ?? []);
|
|
1668
|
+
middlewares.push(...setup.middlewares ?? []);
|
|
1669
|
+
if (setup.wxmlTransform) transforms.push(setup.wxmlTransform);
|
|
1670
|
+
if (setup.entry) entries.push({ name: extension.name, ...setup.entry });
|
|
1671
|
+
}
|
|
1672
|
+
const wxmlTransform = transforms.length === 0 ? void 0 : (code, file) => transforms.reduce((result, transform) => transform(result, file), code);
|
|
1673
|
+
return { webviewScripts, middlewares, wxmlTransform, entries };
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1397
1676
|
// src/server/DevServer.ts
|
|
1677
|
+
var RESERVED_PREFIXES = ["/__wxa__", "/__baselib__", "/__plugin__"];
|
|
1398
1678
|
var DEFAULT_DEVICE = {
|
|
1399
1679
|
model: "iPhone X",
|
|
1400
1680
|
screenWidth: 375,
|
|
@@ -1416,7 +1696,17 @@ var DevServer = class {
|
|
|
1416
1696
|
/** 运行时生成的临时文件(canvas 导出、拍照等),随服务器进程存在 */
|
|
1417
1697
|
this.fileSessions = /* @__PURE__ */ new Map();
|
|
1418
1698
|
this.logger = options.logger || console;
|
|
1419
|
-
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
|
+
}
|
|
1705
|
+
const extended = applyExtensions(options, options.extensions ?? [], { projectPath: this.project.projectPath, root: this.project.root });
|
|
1706
|
+
extended.middlewares.forEach(({ path: mountPath }) => assertMountPath(mountPath));
|
|
1707
|
+
this.webviewScripts = extended.webviewScripts;
|
|
1708
|
+
this.middlewares = extended.middlewares;
|
|
1709
|
+
this.extensionEntries = extended.entries;
|
|
1420
1710
|
this.baseLib = resolveBaseLib({ version: options.baseLibVersion, cacheDir: options.baseLibCacheDir });
|
|
1421
1711
|
if (!this.baseLib.compatible) {
|
|
1422
1712
|
this.logger.warn(`[wxa-runner] \u57FA\u7840\u5E93 ${this.baseLib.version} \u9AD8\u4E8E\u5DF2\u9A8C\u8BC1\u7684\u7248\u672C\u8303\u56F4\uFF0C\u53EF\u80FD\u65E0\u6CD5\u6B63\u5E38\u8FD0\u884C`);
|
|
@@ -1428,7 +1718,7 @@ var DevServer = class {
|
|
|
1428
1718
|
this.ticketSource = this.devtoolsLogin ? developerTicketSource(this.project.projectPath, this.logger) : null;
|
|
1429
1719
|
this.plugins = new PluginStore(this.ticketSource && new WechatDevClient(this.project.appid, this.ticketSource), this.logger);
|
|
1430
1720
|
this.device = { ...DEFAULT_DEVICE, ...options.device };
|
|
1431
|
-
this.builder = new AppBuilder(this.project);
|
|
1721
|
+
this.builder = new AppBuilder(this.project, { wxmlTransform: extended.wxmlTransform });
|
|
1432
1722
|
this.setupFile = resolveSetupFile(options.setup, this.project.projectPath);
|
|
1433
1723
|
this.setupEnv = collectSetupEnv(process.env);
|
|
1434
1724
|
this.app = this.createApp();
|
|
@@ -1441,7 +1731,7 @@ var DevServer = class {
|
|
|
1441
1731
|
get networkUrls() {
|
|
1442
1732
|
if (!this.lanAccess) return [];
|
|
1443
1733
|
const { port } = new URL(this.address);
|
|
1444
|
-
return Object.values(
|
|
1734
|
+
return Object.values(os3.networkInterfaces()).flat().filter((net2) => net2 && net2.family === "IPv4" && !net2.internal).map((net2) => `http://${net2.address}:${port}`);
|
|
1445
1735
|
}
|
|
1446
1736
|
async start() {
|
|
1447
1737
|
try {
|
|
@@ -1484,11 +1774,15 @@ ${error.message}`);
|
|
|
1484
1774
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
1485
1775
|
}
|
|
1486
1776
|
this.fileSessions.clear();
|
|
1777
|
+
this.builder.dispose();
|
|
1487
1778
|
}
|
|
1488
1779
|
createApp() {
|
|
1489
1780
|
const app = express2();
|
|
1490
1781
|
const root = this.project.root;
|
|
1491
1782
|
const noStore = (res) => res.setHeader("Cache-Control", "no-store");
|
|
1783
|
+
for (const { path: mountPath, handler } of this.middlewares) {
|
|
1784
|
+
app.use(mountPath, (req, res, next) => handler(req, res, next));
|
|
1785
|
+
}
|
|
1492
1786
|
app.get("/", (_req, res) => {
|
|
1493
1787
|
noStore(res);
|
|
1494
1788
|
res.type("html").send(hostHtml());
|
|
@@ -1543,7 +1837,8 @@ ${error.message}`);
|
|
|
1543
1837
|
const pagePath = decodeURIComponent(req.params[0]);
|
|
1544
1838
|
if (!build2.pages.includes(pagePath)) return next();
|
|
1545
1839
|
const pluginPage = findPluginPage(build2, pagePath);
|
|
1546
|
-
|
|
1840
|
+
const scripts = this.webviewScripts;
|
|
1841
|
+
res.type("html").send(pluginPage ? pluginWebviewHtml(build2, pluginPage.plugin, pluginPage.innerPath, pagePath, Number(req.query.id), { scripts }) : webviewHtml(build2, pagePath, Number(req.query.id), { hot: this.hotEnabled, scripts }));
|
|
1547
1842
|
}));
|
|
1548
1843
|
app.get("/__wxa__/plugin/:provider/:version/:file(appservice.js|pageframe.js)", this.withBuild((build2, req, res, next) => {
|
|
1549
1844
|
const { provider, version, file } = req.params;
|
|
@@ -1554,7 +1849,7 @@ ${error.message}`);
|
|
|
1554
1849
|
app.get(/^\/__plugin__\/([^/]+)(\/.+)$/, this.withBuild((build2, req, res, next) => {
|
|
1555
1850
|
const file = build2.plugins.find((plugin) => plugin.provider === req.params[0])?.files.get(decodeURIComponent(req.params[1]));
|
|
1556
1851
|
if (!file) return next();
|
|
1557
|
-
res.type(
|
|
1852
|
+
res.type(path6.extname(req.params[1]) || "application/octet-stream").send(file);
|
|
1558
1853
|
}));
|
|
1559
1854
|
app.get(/^\/__wxa__\/module\/(.+\.js)$/, this.withBuild((build2, req, res) => {
|
|
1560
1855
|
const id = decodeURIComponent(req.params[0]);
|
|
@@ -1565,7 +1860,7 @@ ${error.message}`);
|
|
|
1565
1860
|
res.type("js").send(this.builder.getModuleScript(id));
|
|
1566
1861
|
}));
|
|
1567
1862
|
app.get("/__wxa__/worker.js", this.withBuild((build2, _req, res) => {
|
|
1568
|
-
const hasWorker =
|
|
1863
|
+
const hasWorker = fs6.existsSync(path6.join(this.baseLib.dir, "WAWorker.js"));
|
|
1569
1864
|
res.type("js").send(workerScript(build2, { version: this.baseLib.version, hasWorker }));
|
|
1570
1865
|
}));
|
|
1571
1866
|
app.get("/__wxa__/service-bootstrap.js", this.withBuild((build2, _req, res) => {
|
|
@@ -1584,28 +1879,28 @@ ${error.message}`);
|
|
|
1584
1879
|
res.type("js").send(build2.pageFrame);
|
|
1585
1880
|
}));
|
|
1586
1881
|
app.get("/__wxa__/runtime/frame.js.map", (_req, res, next) => {
|
|
1587
|
-
const file =
|
|
1588
|
-
if (!
|
|
1589
|
-
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")));
|
|
1590
1885
|
});
|
|
1591
1886
|
app.use("/__wxa__/runtime", express2.static(this.runtimeDir(), {
|
|
1592
1887
|
cacheControl: false,
|
|
1593
1888
|
// Service Worker 默认只能接管自己所在目录下的页面,宿主页面在根路径
|
|
1594
1889
|
setHeaders: (res, file) => {
|
|
1595
|
-
if (
|
|
1890
|
+
if (path6.basename(file) === "sw.js") res.setHeader("Service-Worker-Allowed", "/");
|
|
1596
1891
|
}
|
|
1597
1892
|
}));
|
|
1598
1893
|
const sourceMaps = /* @__PURE__ */ new Map();
|
|
1599
1894
|
app.get(/^\/__baselib__\/(.+\.js)\.map$/, (req, res, next) => {
|
|
1600
|
-
const file =
|
|
1601
|
-
if (!file.startsWith(this.baseLib.dir +
|
|
1602
|
-
if (!sourceMaps.has(file)) sourceMaps.set(file, ignoreListSourceMap(
|
|
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`));
|
|
1603
1898
|
res.type("json").send(sourceMaps.get(file));
|
|
1604
1899
|
});
|
|
1605
1900
|
app.use("/__baselib__", express2.static(this.baseLib.dir, {
|
|
1606
1901
|
maxAge: "1d",
|
|
1607
1902
|
setHeaders: (res, file) => {
|
|
1608
|
-
if (file.endsWith(".js")) res.setHeader("SourceMap", `${
|
|
1903
|
+
if (file.endsWith(".js")) res.setHeader("SourceMap", `${path6.basename(file)}.map`);
|
|
1609
1904
|
}
|
|
1610
1905
|
}));
|
|
1611
1906
|
app.use(PROXY_PREFIX, express2.raw({ type: () => true, limit: "100mb" }), handleProxy);
|
|
@@ -1665,7 +1960,7 @@ ${error.message}`);
|
|
|
1665
1960
|
return;
|
|
1666
1961
|
}
|
|
1667
1962
|
const data = node.data;
|
|
1668
|
-
res.type(
|
|
1963
|
+
res.type(path6.extname(req.params[0]));
|
|
1669
1964
|
res.setHeader("ETag", `"${node.etag}"`);
|
|
1670
1965
|
res.setHeader("Accept-Ranges", "bytes");
|
|
1671
1966
|
const ranges = req.range(data.length);
|
|
@@ -1735,11 +2030,11 @@ ${error.message}`);
|
|
|
1735
2030
|
runtimeDir() {
|
|
1736
2031
|
const candidates = [
|
|
1737
2032
|
this.options.runtimeDir,
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
2033
|
+
path6.join(__dirname, "runtime"),
|
|
2034
|
+
path6.join(__dirname, "../runtime"),
|
|
2035
|
+
path6.join(__dirname, "../../dist/runtime")
|
|
1741
2036
|
].filter((dir) => !!dir);
|
|
1742
|
-
const found = candidates.find((dir) =>
|
|
2037
|
+
const found = candidates.find((dir) => fs6.existsSync(path6.join(dir, "host.js")));
|
|
1743
2038
|
if (!found) {
|
|
1744
2039
|
throw new Error("\u627E\u4E0D\u5230\u6D4F\u89C8\u5668\u7AEF\u8FD0\u884C\u65F6\uFF08host.js\uFF09\uFF0C\u8BF7\u5148\u6267\u884C pnpm build");
|
|
1745
2040
|
}
|
|
@@ -1752,7 +2047,10 @@ ${error.message}`);
|
|
|
1752
2047
|
startWatching() {
|
|
1753
2048
|
let lastBuildFailed = false;
|
|
1754
2049
|
const watched = [this.project.root];
|
|
1755
|
-
|
|
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);
|
|
1756
2054
|
this.watcher = watchProject(this.project.root, watched, async ({ files, structural }) => {
|
|
1757
2055
|
const reloadOnly = structural || lastBuildFailed;
|
|
1758
2056
|
this.builder.invalidate(files);
|
|
@@ -1783,7 +2081,7 @@ var DEFAULT_PORT = 3e3;
|
|
|
1783
2081
|
var LOCAL_HOST_OF_ANY_ADDRESS = /* @__PURE__ */ new Map([["0.0.0.0", "127.0.0.1"], ["::", "localhost"]]);
|
|
1784
2082
|
async function listen(server, port, host, logger) {
|
|
1785
2083
|
for (let candidate = port ?? DEFAULT_PORT; ; candidate++) {
|
|
1786
|
-
const taken =
|
|
2084
|
+
const taken = await takenOnLocalAddresses(candidate) || !await bind(server, candidate, host);
|
|
1787
2085
|
if (!taken) {
|
|
1788
2086
|
const actual = server.address().port;
|
|
1789
2087
|
if (port === void 0 && actual !== DEFAULT_PORT) logger.warn(`[wxa-runner] \u7AEF\u53E3 ${DEFAULT_PORT} \u5DF2\u88AB\u5360\u7528\uFF0C\u6539\u7528 ${actual}`);
|
|
@@ -1802,8 +2100,8 @@ function bind(server, port, host) {
|
|
|
1802
2100
|
});
|
|
1803
2101
|
});
|
|
1804
2102
|
}
|
|
1805
|
-
async function
|
|
1806
|
-
for (const address of ["127.0.0.1", "::1"]) {
|
|
2103
|
+
async function takenOnLocalAddresses(port) {
|
|
2104
|
+
for (const address of ["127.0.0.1", "::1", "0.0.0.0", "::"]) {
|
|
1807
2105
|
const probe = net.createServer();
|
|
1808
2106
|
const free = await bind(probe, port, address).catch(() => true);
|
|
1809
2107
|
if (!free) return true;
|
|
@@ -1811,11 +2109,21 @@ async function takenOnLoopback(port) {
|
|
|
1811
2109
|
}
|
|
1812
2110
|
return false;
|
|
1813
2111
|
}
|
|
2112
|
+
function assertMountPath(mountPath) {
|
|
2113
|
+
const literal = typeof mountPath === "string" && /^(\/[\w.~-]+)+$/.test(mountPath);
|
|
2114
|
+
const lower = literal ? mountPath.toLowerCase() : "";
|
|
2115
|
+
const reserved = RESERVED_PREFIXES.some((prefix) => lower === prefix || lower.startsWith(prefix + "/"));
|
|
2116
|
+
if (!literal || reserved) {
|
|
2117
|
+
throw new Error(
|
|
2118
|
+
`\u4E2D\u95F4\u4EF6\u8DEF\u5F84 ${String(mountPath)} \u4E0D\u53EF\u7528\uFF1A\u8981\u4EE5 / \u5F00\u5934\uFF0C\u53EA\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u548C _ . ~ -\uFF08\u4E0D\u652F\u6301\u901A\u914D\u7B26\u548C\u53C2\u6570\uFF09\uFF0C\u4E0D\u80FD\u662F /\uFF0C\u4E5F\u4E0D\u80FD\u7528 wxa-runner \u4FDD\u7559\u7684\u524D\u7F00 ${RESERVED_PREFIXES.join("\u3001")}\uFF08\u4E0D\u533A\u5206\u5927\u5C0F\u5199\uFF09`
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
1814
2122
|
function resolveSetupFile(option, projectPath) {
|
|
1815
2123
|
if (option === false) return null;
|
|
1816
2124
|
if (!option) return findSetupFile(projectPath);
|
|
1817
|
-
const file =
|
|
1818
|
-
if (!
|
|
2125
|
+
const file = path6.resolve(option);
|
|
2126
|
+
if (!fs6.existsSync(file)) throw new Error(`\u627E\u4E0D\u5230 setup \u6587\u4EF6\uFF1A${file}`);
|
|
1819
2127
|
return file;
|
|
1820
2128
|
}
|
|
1821
2129
|
export {
|