@uni-helper/vite-plugin-uni-pages 0.3.24 → 0.4.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
@@ -1,55 +1,69 @@
1
- import path, { join, dirname, resolve, extname } from 'node:path';
2
- import process from 'node:process';
3
- import { babelParse, isCallOf } from 'ast-kit';
4
- import chokidar from 'chokidar';
5
- import { lightYellow, bold, link, dim } from 'kolorist';
6
- import MagicString from 'magic-string';
7
- import { normalizePath, createLogger } from 'vite';
8
- import fs, { existsSync } from 'node:fs';
9
- import { platform } from '@uni-helper/uni-env';
10
- import { parse, assign, stringify, CommentArray } from 'comment-json';
11
- import Debug from 'debug';
12
- import detectIndent from 'detect-indent';
13
- import detectNewline from 'detect-newline';
14
- import { loadConfig } from 'unconfig';
15
- import { readFile, mkdir, writeFile as writeFile$1 } from 'node:fs/promises';
16
- import fg from 'fast-glob';
17
- import lockfile from 'proper-lockfile';
18
- import writeFileAtomic from 'write-file-atomic';
19
- import { createRequire } from 'node:module';
20
- import vm from 'node:vm';
21
- import babelGenerator from '@babel/generator';
22
- import groupBy from 'lodash.groupby';
23
- import * as ts from 'typescript';
24
- import * as t from '@babel/types';
25
- import { parse as parse$2 } from '@vue/compiler-sfc';
26
- import JSON5 from 'json5';
27
- import { parse as parse$1 } from 'yaml';
28
-
1
+ import { createRequire } from "node:module";
2
+ import path, { dirname, extname, join, resolve } from "node:path";
3
+ import process from "node:process";
4
+ import { babelParse, isCallOf } from "ast-kit";
5
+ import chokidar from "chokidar";
6
+ import MagicString from "magic-string";
7
+ import { createLogger, normalizePath } from "vite";
8
+ import fs, { existsSync } from "node:fs";
9
+ import { platform } from "@uni-helper/uni-env";
10
+ import { CommentArray, parse, stringify } from "comment-json";
11
+ import dbg from "debug";
12
+ import detectIndent from "detect-indent";
13
+ import detectNewline from "detect-newline";
14
+ import { loadConfig } from "unconfig";
15
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
16
+ import lockfile from "proper-lockfile";
17
+ import { globSync } from "tinyglobby";
18
+ import writeFileAtomic from "write-file-atomic";
19
+ import vm from "node:vm";
20
+ import babelGenerator from "@babel/generator";
21
+ import groupBy from "lodash.groupby";
22
+ import * as ts from "typescript";
23
+ import * as t from "@babel/types";
24
+ import { parse as parse$1 } from "@vue/compiler-sfc";
25
+ //#region src/constant.ts
26
+ /** Virtual module identifier for Vite module resolution */
29
27
  const MODULE_ID_VIRTUAL = "virtual:uni-pages";
28
+ /** Resolved virtual module identifier with \0 prefix to avoid conflicts with other modules */
30
29
  const RESOLVED_MODULE_ID_VIRTUAL = `\0${MODULE_ID_VIRTUAL}`;
30
+ /** Output filename, the uni-app page configuration file */
31
31
  const OUTPUT_NAME = "pages.json";
32
- const FILE_EXTENSIONS = ["vue", "nvue", "uvue"];
33
-
32
+ /** Supported page file extensions */
33
+ const FILE_EXTENSIONS = [
34
+ "vue",
35
+ "nvue",
36
+ "uvue"
37
+ ];
38
+ //#endregion
39
+ //#region ../../node_modules/.pnpm/@antfu+utils@9.3.0/node_modules/@antfu/utils/dist/index.mjs
34
40
  function slash(str) {
35
- return str.replace(/\\/g, "/");
41
+ return str.replace(/\\/g, "/");
36
42
  }
37
-
43
+ //#endregion
44
+ //#region src/declaration.ts
45
+ /**
46
+ * Generate TypeScript declaration file content
47
+ * Provide type hints for uni-app navigation APIs (navigateTo, redirectTo, switchTab, reLaunch)
48
+ * Ensure page paths get type checking at compile time
49
+ *
50
+ * @param ctx - Page context instance
51
+ * @returns Declaration file code string
52
+ */
38
53
  function getDeclaration(ctx) {
39
- const subPagesPath = ctx.subPageMetaData.map((sub) => {
40
- return sub.pages.map((v) => `"/${normalizePath(join(sub.root, v.path))}"`);
41
- }).flat();
42
- const tabsPagesPath = ctx.pagesGlobConfig?.tabBar?.list?.map((v) => {
43
- return `"/${v.pagePath}"`;
44
- }) ?? [];
45
- const allPagesPath = [...ctx.pageMetaData.filter((page) => !tabsPagesPath.includes(page.path)).map((v) => `"/${v.path}"`), ...subPagesPath];
46
- const code = `/* eslint-disable */
54
+ const subPagesPath = ctx.subPageMetaData.map((sub) => {
55
+ return sub.pages.map((v) => `"/${normalizePath(join(sub.root, v.path))}"`);
56
+ }).flat();
57
+ const tabsPagesPath = ctx.pagesGlobConfig?.tabBar?.list?.map((v) => {
58
+ return `"/${v.pagePath}"`;
59
+ }) ?? [];
60
+ return `/* eslint-disable */
47
61
  /* prettier-ignore */
48
62
  // @ts-nocheck
49
63
  // Generated by vite-plugin-uni-pages
50
64
 
51
65
  type _LocationUrl =
52
- ${allPagesPath.join(" |\n ")};
66
+ ${[...ctx.pageMetaData.filter((page) => !tabsPagesPath.includes(page.path)).map((v) => `"/${v.path}"`), ...subPagesPath].join(" |\n ")};
53
67
 
54
68
  interface NavigateToOptions {
55
69
  url: _LocationUrl;
@@ -73,1109 +87,1220 @@ declare module "virtual:uni-pages" {
73
87
  export type LocationUrl = _LocationUrl;
74
88
  }
75
89
  `;
76
- return code;
77
90
  }
78
- async function writeFile(filePath, content) {
79
- await mkdir(dirname(filePath), { recursive: true });
80
- return await writeFile$1(filePath, content, "utf-8");
91
+ /**
92
+ * Write file, automatically create directories
93
+ * @param filePath - File path
94
+ * @param content - File content
95
+ */
96
+ async function writeFile$1(filePath, content) {
97
+ await mkdir(dirname(filePath), { recursive: true });
98
+ return await writeFile(filePath, content, "utf-8");
81
99
  }
100
+ /**
101
+ * Write declaration file to disk
102
+ * Only writes when content changes to avoid unnecessary file operations
103
+ *
104
+ * @param ctx - Page context instance
105
+ * @param filepath - Declaration file output path
106
+ */
82
107
  async function writeDeclaration(ctx, filepath) {
83
- const originalContent = existsSync(filepath) ? await readFile(filepath, "utf-8") : "";
84
- const code = getDeclaration(ctx);
85
- if (!code)
86
- return;
87
- if (code !== originalContent)
88
- await writeFile(filepath, code);
108
+ const originalContent = existsSync(filepath) ? await readFile(filepath, "utf-8") : "";
109
+ const code = getDeclaration(ctx);
110
+ if (!code) return;
111
+ if (code !== originalContent) await writeFile$1(filepath, code);
89
112
  }
90
-
113
+ //#endregion
114
+ //#region src/utils.ts
115
+ /**
116
+ * Invalidate virtual module to trigger HMR update
117
+ * When page configuration changes, the virtual module needs to be invalidated to regenerate content
118
+ *
119
+ * @param server - Vite dev server instance
120
+ */
91
121
  function invalidatePagesModule(server) {
92
- const { moduleGraph } = server;
93
- const mods = moduleGraph.getModulesByFile(RESOLVED_MODULE_ID_VIRTUAL);
94
- if (mods) {
95
- const seen = /* @__PURE__ */ new Set();
96
- mods.forEach((mod) => {
97
- moduleGraph.invalidateModule(mod, seen);
98
- });
99
- }
122
+ const { moduleGraph } = server;
123
+ const mods = moduleGraph.getModulesByFile(RESOLVED_MODULE_ID_VIRTUAL);
124
+ if (mods) {
125
+ const seen = /* @__PURE__ */ new Set();
126
+ mods.forEach((mod) => {
127
+ moduleGraph.invalidateModule(mod, seen);
128
+ });
129
+ }
100
130
  }
131
+ /**
132
+ * Debug logging utility
133
+ * Uses debug package for leveled log output
134
+ * Enable via environment variable DEBUG=vite-plugin-uni-pages:*
135
+ */
101
136
  const debug = {
102
- hmr: Debug("vite-plugin-uni-pages:hmr"),
103
- routeBlock: Debug("vite-plugin-uni-pages:routeBlock"),
104
- options: Debug("vite-plugin-uni-pages:options"),
105
- pages: Debug("vite-plugin-uni-pages:pages"),
106
- subPages: Debug("vite-plugin-uni-pages:subPages"),
107
- error: Debug("vite-plugin-uni-pages:error"),
108
- cache: Debug("vite-plugin-uni-pages:cache"),
109
- declaration: Debug("vite-plugin-uni-pages:declaration"),
110
- definePage: Debug("vite-plugin-uni-pages:definePage")
137
+ /** HMR related logs */
138
+ hmr: dbg("vite-plugin-uni-pages:hmr"),
139
+ /** Configuration options related logs */
140
+ options: dbg("vite-plugin-uni-pages:options"),
141
+ /** Main package page scanning logs */
142
+ pages: dbg("vite-plugin-uni-pages:pages"),
143
+ /** Sub-package page scanning logs */
144
+ subPages: dbg("vite-plugin-uni-pages:subPages"),
145
+ /** Error logs */
146
+ error: dbg("vite-plugin-uni-pages:error"),
147
+ /** Cache related logs */
148
+ cache: dbg("vite-plugin-uni-pages:cache"),
149
+ /** Declaration file generation logs */
150
+ declaration: dbg("vite-plugin-uni-pages:declaration"),
151
+ /** definePage macro parsing logs */
152
+ definePage: dbg("vite-plugin-uni-pages:definePage")
111
153
  };
154
+ /**
155
+ * Convert file extensions array to glob pattern
156
+ * @param extensions - File extensions array
157
+ * @returns Glob pattern string
158
+ */
112
159
  function extsToGlob(extensions) {
113
- return extensions.length > 1 ? `{${extensions.join(",")}}` : extensions[0] || "";
160
+ return extensions.length > 1 ? `{${extensions.join(",")}}` : extensions[0] || "";
114
161
  }
115
- function isTargetFile(path2) {
116
- const ext = path2.split(".").pop();
117
- return FILE_EXTENSIONS.includes(ext);
162
+ /**
163
+ * Check if file is a target page file
164
+ * Determine if the file extension is in the supported page file types list
165
+ *
166
+ * @param path - File path
167
+ * @returns Whether it's a target file
168
+ */
169
+ function isTargetFile(path) {
170
+ const ext = path.split(".").pop();
171
+ return FILE_EXTENSIONS.includes(ext);
118
172
  }
173
+ /**
174
+ * merge page meta data array by path and assign style
175
+ * @param pageMetaData page meta data array
176
+ * TODO: support merge middleware
177
+ */
119
178
  function mergePageMetaDataArray(pageMetaData) {
120
- const pageMetaDataObj = groupBy(pageMetaData, "path");
121
- const result = [];
122
- for (const path2 in pageMetaDataObj) {
123
- const _pageMetaData = pageMetaDataObj[path2];
124
- const options = _pageMetaData[0];
125
- for (const page of _pageMetaData) {
126
- options.style = Object.assign(options.style ?? {}, page.style ?? {});
127
- Object.assign(options, page);
128
- }
129
- result.push(options);
130
- }
131
- return result;
179
+ const pageMetaDataObj = groupBy(pageMetaData, "path");
180
+ const result = [];
181
+ for (const path in pageMetaDataObj) {
182
+ const _pageMetaData = pageMetaDataObj[path];
183
+ const options = _pageMetaData[0];
184
+ for (const page of _pageMetaData) {
185
+ options.style = Object.assign(options.style ?? {}, page.style ?? {});
186
+ Object.assign(options, page);
187
+ }
188
+ result.push(options);
189
+ }
190
+ return result;
132
191
  }
192
+ /**
193
+ * Convert TypeScript / JavaScript script code to object/function
194
+ *
195
+ * @param options - Configuration required for script execution
196
+ * @param options.imports - List of module import statements to include
197
+ * @param options.code - TypeScript code content to execute
198
+ * @param options.filename - Script filename for error location and context
199
+ * @returns Script execution result, if export is a function then execute and return its return value
200
+ */
133
201
  async function parseCode(options) {
134
- const { imports = [], code, filename } = options;
135
- let jsCode = "";
136
- try {
137
- const tmpCode = `${imports.join("\n")}
138
- export default ${code}`;
139
- jsCode = ts.transpileModule(tmpCode, {
140
- compilerOptions: {
141
- module: ts.ModuleKind.CommonJS,
142
- // 生成的模块格式为 CommonJS(Node.js 默认格式)
143
- target: ts.ScriptTarget.ES2022,
144
- // 编译后的 JavaScript 目标版本
145
- noEmit: true,
146
- // 不生成输出文件
147
- strict: false,
148
- // 关闭所有严格类型检查选项
149
- noImplicitAny: false,
150
- // 允许表达式和 any 类型
151
- strictNullChecks: false,
152
- // 关闭严格的 null 和 undefined 检查
153
- strictFunctionTypes: false,
154
- // 关闭函数参数的严格逆变比较
155
- strictBindCallApply: false,
156
- // 关闭对 bind、call 和 apply 方法的严格类型检查
157
- strictPropertyInitialization: false,
158
- // 关闭类属性初始化的严格检查
159
- noImplicitThis: false,
160
- // 允许 this 表达式具有隐式的 any 类型
161
- alwaysStrict: false,
162
- // 不以严格模式解析并为每个源文件生成 "use strict" 指令
163
- allowJs: true,
164
- // 允许编译 JavaScript 文件
165
- checkJs: false,
166
- // 不检查 JavaScript 文件中的类型
167
- skipLibCheck: true,
168
- // 跳过对 TypeScript 声明文件 (*.d.ts) 的类型检查
169
- esModuleInterop: true,
170
- // 启用 ES 模块互操作性,允许使用 import 导入 CommonJS 模块
171
- removeComments: true
172
- // 删除注释
173
- },
174
- jsDocParsingMode: ts.JSDocParsingMode.ParseNone
175
- // 不解析 JSDoc
176
- }).outputText;
177
- const dir = path.dirname(filename);
178
- const vmContext = {
179
- module: {},
180
- exports: {},
181
- __filename: filename,
182
- __dirname: dir,
183
- require: createRequire(dir),
184
- import: (id) => import(id),
185
- // 定时器相关
186
- setTimeout,
187
- clearTimeout,
188
- setInterval,
189
- clearInterval,
190
- setImmediate,
191
- clearImmediate,
192
- // 控制台相关
193
- console,
194
- // URL 处理
195
- URL,
196
- URLSearchParams,
197
- // 进程和性能相关
198
- performance,
199
- // 全局对象引用
200
- global: globalThis,
201
- globalThis
202
- };
203
- const script = new vm.Script(jsCode, { filename });
204
- await script.runInNewContext(vmContext, {
205
- timeout: 1e3
206
- // 设置超时避免长时间运行
207
- });
208
- const result = vmContext.exports.default || vmContext.exports;
209
- return result;
210
- } catch (error) {
211
- throw new Error(`EXEC SCRIPT FAIL IN ${filename}: ${error.message}
212
-
213
- ${jsCode}
214
-
215
- `);
216
- }
202
+ const { imports = [], code, filename } = options;
203
+ let jsCode = "";
204
+ try {
205
+ const tmpCode = `${imports.join("\n")}\n export default ${code}`;
206
+ jsCode = ts.transpileModule(tmpCode, {
207
+ compilerOptions: {
208
+ module: ts.ModuleKind.CommonJS,
209
+ target: ts.ScriptTarget.ES2022,
210
+ noEmit: true,
211
+ strict: false,
212
+ noImplicitAny: false,
213
+ strictNullChecks: false,
214
+ strictFunctionTypes: false,
215
+ strictBindCallApply: false,
216
+ strictPropertyInitialization: false,
217
+ noImplicitThis: false,
218
+ alwaysStrict: false,
219
+ allowJs: true,
220
+ checkJs: false,
221
+ skipLibCheck: true,
222
+ esModuleInterop: true,
223
+ removeComments: true
224
+ },
225
+ jsDocParsingMode: ts.JSDocParsingMode.ParseNone
226
+ }).outputText;
227
+ const dir = path.dirname(filename);
228
+ const vmContext = {
229
+ module: {},
230
+ exports: {},
231
+ __filename: filename,
232
+ __dirname: dir,
233
+ require: createRequire(dir),
234
+ import: (id) => import(id),
235
+ setTimeout,
236
+ clearTimeout,
237
+ setInterval,
238
+ clearInterval,
239
+ setImmediate,
240
+ clearImmediate,
241
+ console,
242
+ URL,
243
+ URLSearchParams,
244
+ performance,
245
+ global: globalThis,
246
+ globalThis
247
+ };
248
+ await new vm.Script(jsCode, { filename }).runInNewContext(vmContext, { timeout: 1e3 });
249
+ return vmContext.exports.default || vmContext.exports;
250
+ } catch (error) {
251
+ throw new Error(`EXEC SCRIPT FAIL IN ${filename}: ${error.message} \n\n${jsCode}\n\n`);
252
+ }
217
253
  }
254
+ /**
255
+ * Async sleep function
256
+ * @param ms - Sleep duration in milliseconds
257
+ * @returns Promise object
258
+ */
218
259
  function sleep(ms) {
219
- return new Promise((resolve) => setTimeout(resolve, ms));
260
+ return new Promise((resolve) => setTimeout(resolve, ms));
220
261
  }
262
+ /**
263
+ * Get default export of a module
264
+ * Compatible with CommonJS and ES Module export styles
265
+ *
266
+ * @param expr - Module object
267
+ * @returns Default export value
268
+ */
221
269
  function getDefaultExport(expr) {
222
- return expr.default === void 0 ? expr : expr.default;
270
+ return expr.default === void 0 ? expr : expr.default;
223
271
  }
272
+ /** Babel code generator for converting AST back to code */
224
273
  const babelGenerate = getDefaultExport(babelGenerator);
225
-
274
+ function stripType(item) {
275
+ const { type, ...rest } = item;
276
+ return rest;
277
+ }
278
+ //#endregion
279
+ //#region src/files.ts
280
+ /**
281
+ * Resolves the files that are valid pages for the given context.
282
+ */
226
283
  function getPageFiles(path, options) {
227
- const { exclude } = options;
228
- const ext = extsToGlob(FILE_EXTENSIONS);
229
- const files = fg.sync(`**/*.${ext}`, {
230
- ignore: exclude,
231
- onlyFiles: true,
232
- cwd: path
233
- });
234
- return files;
284
+ const { exclude } = options;
285
+ return globSync(`**/*.${extsToGlob(FILE_EXTENSIONS)}`, {
286
+ ignore: exclude,
287
+ onlyFiles: true,
288
+ cwd: path
289
+ });
235
290
  }
291
+ /**
292
+ * Check the pages.json file at the specified path, create an empty pages.json file if it doesn't exist or is not a valid file
293
+ * @param path - File path to check
294
+ * @returns boolean - Whether the operation was successful
295
+ */
236
296
  function checkPagesJsonFileSync(path) {
237
- const createEmptyFile = (path2) => {
238
- try {
239
- fs.writeFileSync(
240
- path2,
241
- JSON.stringify({ pages: [{ path: "" }] }, null, 2),
242
- { encoding: "utf-8" }
243
- );
244
- return true;
245
- } catch {
246
- return false;
247
- }
248
- };
249
- const unlinkFile = (path2) => {
250
- try {
251
- fs.unlinkSync(path2);
252
- return true;
253
- } catch {
254
- return false;
255
- }
256
- };
257
- try {
258
- try {
259
- fs.accessSync(path, fs.constants.F_OK);
260
- } catch {
261
- return createEmptyFile(path);
262
- }
263
- const stat = fs.statSync(path);
264
- if (!stat.isFile()) {
265
- if (!unlinkFile(path)) {
266
- return false;
267
- }
268
- return createEmptyFile(path);
269
- }
270
- try {
271
- fs.accessSync(path, fs.constants.R_OK | fs.constants.W_OK);
272
- return true;
273
- } catch {
274
- if (!unlinkFile(path)) {
275
- return false;
276
- }
277
- return createEmptyFile(path);
278
- }
279
- } catch {
280
- return createEmptyFile(path);
281
- }
297
+ /**
298
+ * Create an empty pages.json file
299
+ * @param path - File path
300
+ * @returns boolean - Whether the creation was successful
301
+ */
302
+ const createEmptyFile = (path) => {
303
+ try {
304
+ fs.writeFileSync(path, JSON.stringify({ pages: [{ path: "" }] }, null, 2), { encoding: "utf-8" });
305
+ return true;
306
+ } catch {
307
+ return false;
308
+ }
309
+ };
310
+ /**
311
+ * Delete the file at the specified path
312
+ * @param path - File path
313
+ * @returns boolean - Whether the deletion was successful
314
+ */
315
+ const unlinkFile = (path) => {
316
+ try {
317
+ fs.unlinkSync(path);
318
+ return true;
319
+ } catch {
320
+ return false;
321
+ }
322
+ };
323
+ try {
324
+ try {
325
+ fs.accessSync(path, fs.constants.F_OK);
326
+ } catch {
327
+ return createEmptyFile(path);
328
+ }
329
+ if (!fs.statSync(path).isFile()) {
330
+ if (!unlinkFile(path)) return false;
331
+ return createEmptyFile(path);
332
+ }
333
+ try {
334
+ fs.accessSync(path, fs.constants.R_OK | fs.constants.W_OK);
335
+ return true;
336
+ } catch {
337
+ if (!unlinkFile(path)) return false;
338
+ return createEmptyFile(path);
339
+ }
340
+ } catch {
341
+ return createEmptyFile(path);
342
+ }
282
343
  }
344
+ /**
345
+ * Safely write file using file lock
346
+ * Avoid data corruption caused by concurrent writes through file lock
347
+ * Use atomic write to ensure file write integrity
348
+ *
349
+ * @param path - File path
350
+ * @param content - File content
351
+ * @param retry - Number of retries when lock acquisition fails, defaults to 3
352
+ */
283
353
  async function writeFileWithLock(path, content, retry = 3) {
284
- if (retry <= 0) {
285
- debug.error(`${path} \u83B7\u53D6\u6587\u4EF6\u9501\u5931\u8D25\uFF0C\u5199\u5165\u5931\u8D25`);
286
- return;
287
- }
288
- let release;
289
- try {
290
- try {
291
- release = await lockfile.lock(path, { realpath: false });
292
- } catch {
293
- await sleep(500);
294
- return writeFileWithLock(path, content, retry - 1);
295
- }
296
- await writeFileAtomic(path, content);
297
- } finally {
298
- if (release) {
299
- await release();
300
- }
301
- }
354
+ if (retry <= 0) {
355
+ debug.error(`${path} Failed to acquire file lock, write failed`);
356
+ return;
357
+ }
358
+ let release;
359
+ try {
360
+ try {
361
+ release = await lockfile.lock(path, { realpath: false });
362
+ } catch {
363
+ await sleep(500);
364
+ return writeFileWithLock(path, content, retry - 1);
365
+ }
366
+ await writeFileAtomic(path, content);
367
+ } finally {
368
+ if (release) await release();
369
+ }
302
370
  }
303
-
371
+ //#endregion
372
+ //#region src/options.ts
373
+ /**
374
+ * Resolve user configuration options
375
+ * Merge user-provided configuration with default values and handle path resolution
376
+ *
377
+ * @param userOptions - User configuration options
378
+ * @param viteRoot - Vite project root directory
379
+ * @returns Resolved configuration options
380
+ */
304
381
  function resolveOptions(userOptions, viteRoot = process.cwd()) {
305
- const {
306
- dts = true,
307
- configSource = "pages.config",
308
- homePage = ["pages/index", "pages/index/index"],
309
- mergePages = true,
310
- dir = "src/pages",
311
- subPackages = [],
312
- outDir = "src",
313
- exclude = ["node_modules", ".git", "**/__*__/**"],
314
- routeBlockLang = "json5",
315
- minify = false,
316
- debug = false,
317
- onBeforeLoadUserConfig = () => {
318
- },
319
- onAfterLoadUserConfig = () => {
320
- },
321
- onBeforeScanPages = () => {
322
- },
323
- onAfterScanPages = () => {
324
- },
325
- onBeforeMergePageMetaData = () => {
326
- },
327
- onAfterMergePageMetaData = () => {
328
- },
329
- onBeforeWriteFile = () => {
330
- },
331
- onAfterWriteFile = () => {
332
- }
333
- } = userOptions;
334
- const root = viteRoot || slash(process.env.VITE_ROOT_DIR || process.cwd());
335
- const resolvedDirs = resolvePageDirs(dir, root, exclude);
336
- const resolvedSubDirs = subPackages.map((dir2) => slash(dir2));
337
- const resolvedHomePage = typeof homePage === "string" ? [homePage] : homePage;
338
- const resolvedConfigSource = typeof configSource === "string" ? [{ files: configSource }] : configSource;
339
- const resolvedDts = !dts ? false : typeof dts === "string" ? dts : resolve(viteRoot, "uni-pages.d.ts");
340
- const resolvedOptions = {
341
- dts: resolvedDts,
342
- configSource: Array.isArray(resolvedConfigSource) ? resolvedConfigSource : [resolvedConfigSource],
343
- homePage: resolvedHomePage,
344
- mergePages,
345
- dirs: resolvedDirs,
346
- subPackages: resolvedSubDirs,
347
- outDir,
348
- exclude,
349
- routeBlockLang,
350
- root,
351
- minify,
352
- debug,
353
- onBeforeLoadUserConfig,
354
- onAfterLoadUserConfig,
355
- onBeforeScanPages,
356
- onAfterScanPages,
357
- onBeforeMergePageMetaData,
358
- onAfterMergePageMetaData,
359
- onBeforeWriteFile,
360
- onAfterWriteFile
361
- };
362
- return resolvedOptions;
382
+ const { dts = true, configSource = "pages.config", homePage = ["pages/index", "pages/index/index"], mergePages = true, dir = "src/pages", subPackages = [], outDir = "src", exclude = [
383
+ "node_modules",
384
+ ".git",
385
+ "**/__*__/**"
386
+ ], minify = false, debug = false, onBeforeLoadUserConfig = () => {}, onAfterLoadUserConfig = () => {}, onBeforeScanPages = () => {}, onAfterScanPages = () => {}, onBeforeMergePageMetaData = () => {}, onAfterMergePageMetaData = () => {}, onBeforeWriteFile = () => {}, onAfterWriteFile = () => {} } = userOptions;
387
+ const root = viteRoot || slash(process.env.VITE_ROOT_DIR || process.cwd());
388
+ const resolvedDirs = resolvePageDirs(dir, root, exclude);
389
+ const subPackageRootMap = /* @__PURE__ */ new Map();
390
+ const resolvedSubDirs = [];
391
+ for (const sub of subPackages) if (typeof sub === "string") resolvedSubDirs.push(slash(sub));
392
+ else {
393
+ const dirPath = slash(sub.dir);
394
+ resolvedSubDirs.push(dirPath);
395
+ subPackageRootMap.set(dirPath, sub.root);
396
+ }
397
+ const resolvedHomePage = typeof homePage === "string" ? [homePage] : homePage;
398
+ const resolvedConfigSource = typeof configSource === "string" ? [{ files: configSource }] : configSource;
399
+ return {
400
+ dts: !dts ? false : typeof dts === "string" ? dts : resolve(viteRoot, "uni-pages.d.ts"),
401
+ configSource: Array.isArray(resolvedConfigSource) ? resolvedConfigSource : [resolvedConfigSource],
402
+ homePage: resolvedHomePage,
403
+ mergePages,
404
+ dirs: resolvedDirs,
405
+ subPackages: resolvedSubDirs,
406
+ subPackageRootMap,
407
+ outDir,
408
+ exclude,
409
+ root,
410
+ minify,
411
+ debug,
412
+ onBeforeLoadUserConfig,
413
+ onAfterLoadUserConfig,
414
+ onBeforeScanPages,
415
+ onAfterScanPages,
416
+ onBeforeMergePageMetaData,
417
+ onAfterMergePageMetaData,
418
+ onBeforeWriteFile,
419
+ onAfterWriteFile
420
+ };
363
421
  }
422
+ /**
423
+ * Resolves the page dirs for its for its given globs
424
+ */
364
425
  function resolvePageDirs(dir, root, exclude) {
365
- const dirs = fg.sync(slash(dir), {
366
- ignore: exclude,
367
- onlyDirectories: true,
368
- dot: true,
369
- unique: true,
370
- cwd: root
371
- });
372
- return dirs;
373
- }
374
-
375
- function parseCustomBlock(block, filePath, routeBlockLang) {
376
- const lang = block.lang ?? routeBlockLang;
377
- const attr = {
378
- type: "page",
379
- ...block.attrs
380
- };
381
- let content;
382
- debug.routeBlock(`use ${lang} parser`);
383
- if (lang === "json5") {
384
- try {
385
- content = JSON5.parse(block.content);
386
- } catch (err) {
387
- throw new Error(
388
- `Invalid JSON5 format of <${block.type}> content in ${filePath}
389
- ${err.message}`
390
- );
391
- }
392
- } else if (lang === "jsonc") {
393
- try {
394
- content = parse(block.content);
395
- } catch (err) {
396
- throw new Error(
397
- `Invalid JSONC format of <${block.type}> content in ${filePath}
398
- ${err.message}`
399
- );
400
- }
401
- } else if (lang === "json") {
402
- try {
403
- content = JSON.parse(block.content);
404
- } catch (err) {
405
- throw new Error(
406
- `Invalid JSON format of <${block.type}> content in ${filePath}
407
- ${err.message}`
408
- );
409
- }
410
- } else if (lang === "yaml" || lang === "yml") {
411
- try {
412
- content = parse$1(block.content);
413
- } catch (err) {
414
- throw new Error(
415
- `Invalid YAML format of <${block.type}> content in ${filePath}
416
- ${err.message}`
417
- );
418
- }
419
- }
420
- return {
421
- attr,
422
- content: content ?? {}
423
- };
426
+ return globSync(slash(dir), {
427
+ ignore: exclude,
428
+ onlyDirectories: true,
429
+ expandDirectories: false,
430
+ dot: true,
431
+ cwd: root
432
+ });
424
433
  }
425
- function getRouteSfcBlock(sfc) {
426
- return sfc?.customBlocks.find((b) => b.type === "route");
427
- }
428
- function getRouteBlock(path, blockStr, routeBlockLang) {
429
- if (!blockStr)
430
- return;
431
- return parseCustomBlock(blockStr, path, routeBlockLang);
432
- }
433
-
434
- var __defProp$1 = Object.defineProperty;
435
- var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
436
- var __publicField$1 = (obj, key, value) => {
437
- __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
438
- return value;
434
+ //#endregion
435
+ //#region src/page.ts
436
+ /**
437
+ * Page class representing a Vue page file
438
+ *
439
+ * Responsibilities:
440
+ * 1. Read page file content
441
+ * 2. Parse page metadata defined by definePage macro
442
+ * 3. Provide tabBar configuration information
443
+ * 4. Track page file change status
444
+ */
445
+ var Page = class {
446
+ /** Page context instance */
447
+ ctx;
448
+ /** Page path information containing relative and absolute paths */
449
+ path;
450
+ /** Page URI used for pages.json path field */
451
+ uri;
452
+ /** Whether the page has changed, used for incremental update judgment */
453
+ changed = true;
454
+ /** Raw JSON string of page metadata for change detection */
455
+ raw = "";
456
+ /** Parsed page metadata */
457
+ meta;
458
+ /**
459
+ * Create a page instance
460
+ * @param ctx - Page context instance
461
+ * @param path - Page path information
462
+ */
463
+ constructor(ctx, path) {
464
+ this.ctx = ctx;
465
+ this.path = path;
466
+ this.uri = normalizePath(path.relativePath.replace(extname(path.relativePath), ""));
467
+ }
468
+ /**
469
+ * Get page metadata
470
+ * Parse configuration defined by definePage macro and return metadata for pages.json
471
+ *
472
+ * @param forceUpdate - Whether to force update, ignoring cache
473
+ * @returns Page metadata object
474
+ */
475
+ async getPageMeta(forceUpdate = false) {
476
+ if (forceUpdate || !this.meta) await this.read();
477
+ const { path, tabBar: _, ...others } = this.meta || {};
478
+ return {
479
+ path: path ?? this.uri,
480
+ ...others
481
+ };
482
+ }
483
+ /**
484
+ * Get page tabBar configuration
485
+ * Extract tabBar related configuration from definePage macro
486
+ *
487
+ * @param forceUpdate - Whether to force update, ignoring cache
488
+ * @returns tabBar configuration object, or undefined if page doesn't define tabBar
489
+ */
490
+ async getTabBar(forceUpdate = false) {
491
+ if (forceUpdate || !this.meta) await this.read();
492
+ const { tabBar } = this.meta || {};
493
+ if (tabBar === void 0) return;
494
+ return {
495
+ ...tabBar,
496
+ pagePath: tabBar.pagePath || this.uri,
497
+ index: tabBar.index ?? 0
498
+ };
499
+ }
500
+ /**
501
+ * Check if the page has changed
502
+ * @returns Whether the page has changed
503
+ */
504
+ hasChanged() {
505
+ return this.changed;
506
+ }
507
+ /**
508
+ * Read page file and parse metadata
509
+ * Extract configuration defined by definePage macro from Vue SFC
510
+ */
511
+ async read() {
512
+ let meta;
513
+ try {
514
+ meta = await this.readPageMetaFromFile();
515
+ } catch (err) {
516
+ debug.error(err);
517
+ return;
518
+ }
519
+ let raw = "";
520
+ try {
521
+ raw = JSON.stringify(meta);
522
+ } catch {}
523
+ this.changed = this.raw !== raw;
524
+ this.meta = meta;
525
+ this.raw = raw;
526
+ }
527
+ async readPageMetaFromFile() {
528
+ try {
529
+ const meta = await tryPageMetaFromMacro(parseSFC(await fs.promises.readFile(this.path.absolutePath, { encoding: "utf-8" }), { filename: this.path.absolutePath }));
530
+ if (meta) return meta;
531
+ return { type: "page" };
532
+ } catch (err) {
533
+ throw new Error(`Read page meta fail in ${this.path.relativePath}\n${err.message}`);
534
+ }
535
+ }
439
536
  };
440
- class Page {
441
- constructor(ctx, path) {
442
- __publicField$1(this, "ctx");
443
- __publicField$1(this, "path");
444
- __publicField$1(this, "uri");
445
- __publicField$1(this, "changed", true);
446
- __publicField$1(this, "raw", "");
447
- __publicField$1(this, "meta");
448
- this.ctx = ctx;
449
- this.path = path;
450
- this.uri = normalizePath(path.relativePath.replace(extname(path.relativePath), ""));
451
- }
452
- async getPageMeta(forceUpdate = false) {
453
- if (forceUpdate || !this.meta) {
454
- await this.read();
455
- }
456
- const { path, tabBar: _, ...others } = this.meta || {};
457
- return {
458
- path: path ?? this.uri,
459
- ...others
460
- };
461
- }
462
- async getTabBar(forceUpdate = false) {
463
- if (forceUpdate || !this.meta) {
464
- await this.read();
465
- }
466
- const { tabBar } = this.meta || {};
467
- if (tabBar === void 0) {
468
- return void 0;
469
- }
470
- return {
471
- ...tabBar,
472
- pagePath: tabBar.pagePath || this.uri,
473
- index: tabBar.index || 0
474
- };
475
- }
476
- hasChanged() {
477
- return this.changed;
478
- }
479
- async read() {
480
- let meta;
481
- try {
482
- meta = await this.readPageMetaFromFile();
483
- } catch (err) {
484
- debug.error(err);
485
- return;
486
- }
487
- let raw = "";
488
- try {
489
- raw = JSON.stringify(meta);
490
- } catch {
491
- }
492
- this.changed = this.raw !== raw;
493
- this.meta = meta;
494
- this.raw = raw;
495
- }
496
- async readPageMetaFromFile() {
497
- try {
498
- const content = await fs.promises.readFile(this.path.absolutePath, { encoding: "utf-8" });
499
- const sfc = parseSFC(content, { filename: this.path.absolutePath });
500
- const meta = await tryPageMetaFromMacro(sfc);
501
- if (meta) {
502
- return meta;
503
- }
504
- return tryPageMetaFromCustomBlock(sfc, this.ctx.options.routeBlockLang);
505
- } catch (err) {
506
- throw new Error(`Read page meta fail in ${this.path.relativePath}
507
- ${err.message}`);
508
- }
509
- }
510
- }
537
+ /**
538
+ * Parse Vue Single File Component (SFC)
539
+ * Compatible with different versions of @vue/compiler-sfc
540
+ *
541
+ * @param code - Vue SFC source code
542
+ * @param options - Parse options
543
+ * @returns SFC descriptor object
544
+ */
511
545
  function parseSFC(code, options) {
512
- return parse$2(code, {
513
- pad: "space",
514
- ...options
515
- }).descriptor || parse$2({
516
- source: code,
517
- ...options
518
- });
546
+ return parse$1(code, {
547
+ pad: "space",
548
+ ...options
549
+ }).descriptor || parse$1({
550
+ source: code,
551
+ ...options
552
+ });
519
553
  }
554
+ /**
555
+ * Try to extract page metadata defined by definePage macro from SFC
556
+ * Support using definePage in script setup or regular script
557
+ *
558
+ * @param sfc - Vue SFC descriptor
559
+ * @returns Page metadata object, or undefined if definePage is not found
560
+ */
520
561
  async function tryPageMetaFromMacro(sfc) {
521
- const sfcScript = sfc.scriptSetup || sfc.script;
522
- if (!sfcScript) {
523
- return void 0;
524
- }
525
- const ast = babelParse(sfcScript.content, sfcScript.lang || "js", {
526
- plugins: [["importAttributes", { deprecatedAssertSyntax: true }]]
527
- });
528
- const macro = findMacro(ast.body, sfc.filename);
529
- if (macro) {
530
- const imports = findImports(ast.body).filter((imp) => !!imp.specifiers.length).map((imp) => babelGenerate(imp).code);
531
- const [macroOption] = macro.arguments;
532
- const code = babelGenerate(macroOption).code;
533
- const parsed = await parseCode({
534
- imports,
535
- code,
536
- filename: sfc.filename
537
- });
538
- const res = typeof parsed === "function" ? await Promise.resolve(parsed()) : await Promise.resolve(parsed);
539
- return {
540
- type: "page",
541
- ...res
542
- };
543
- }
544
- return void 0;
545
- }
546
- async function tryPageMetaFromCustomBlock(sfc, routeBlockLang) {
547
- const block = getRouteSfcBlock(sfc);
548
- const routeBlock = getRouteBlock(sfc.filename, block, routeBlockLang);
549
- const pageMeta = {
550
- type: routeBlock?.attr.type ?? "page"
551
- };
552
- if (routeBlock) {
553
- try {
554
- assign(pageMeta, routeBlock.content);
555
- } catch {
556
- }
557
- }
558
- return pageMeta;
562
+ const sfcScript = sfc.scriptSetup || sfc.script;
563
+ if (!sfcScript) return;
564
+ const ast = babelParse(sfcScript.content, sfcScript.lang || "js", { plugins: [["importAttributes", { deprecatedAssertSyntax: true }]] });
565
+ const macro = findMacro(ast.body, sfc.filename);
566
+ if (macro) {
567
+ const imports = findImports(ast.body).filter((imp) => !!imp.specifiers.length).map((imp) => babelGenerate(imp).code);
568
+ const [macroOption] = macro.arguments;
569
+ const code = babelGenerate(macroOption).code;
570
+ const parsed = await parseCode({
571
+ imports,
572
+ code,
573
+ filename: sfc.filename
574
+ });
575
+ return {
576
+ type: "page",
577
+ ...typeof parsed === "function" ? await Promise.resolve(parsed()) : await Promise.resolve(parsed)
578
+ };
579
+ }
559
580
  }
581
+ /**
582
+ * Find definePage macro call in AST
583
+ * Support function expressions, arrow functions and object expressions as arguments
584
+ *
585
+ * @param stmts - AST statement array
586
+ * @param filename - Filename for error reporting
587
+ * @returns definePage call expression, or undefined if not found
588
+ */
560
589
  function findMacro(stmts, filename) {
561
- let macro;
562
- for (const stmt of stmts) {
563
- let node = stmt;
564
- if (stmt.type === "ExpressionStatement")
565
- node = stmt.expression;
566
- if (isCallOf(node, "definePage")) {
567
- macro = node;
568
- break;
569
- }
570
- }
571
- if (!macro)
572
- return;
573
- const [opt] = macro.arguments;
574
- if (opt && !t.isFunctionExpression(opt) && !t.isArrowFunctionExpression(opt) && !t.isObjectExpression(opt)) {
575
- debug.definePage(`definePage() \u53C2\u6570\u4EC5\u652F\u6301\u51FD\u6570\u6216\u5BF9\u8C61\uFF1A${filename}`);
576
- return;
577
- }
578
- return macro;
590
+ let macro;
591
+ for (const stmt of stmts) {
592
+ let node = stmt;
593
+ if (stmt.type === "ExpressionStatement") node = stmt.expression;
594
+ if (isCallOf(node, "definePage")) {
595
+ macro = node;
596
+ break;
597
+ }
598
+ }
599
+ if (!macro) return;
600
+ const [opt] = macro.arguments;
601
+ if (opt && !t.isFunctionExpression(opt) && !t.isArrowFunctionExpression(opt) && !t.isObjectExpression(opt)) {
602
+ debug.definePage(`definePage() 参数仅支持函数或对象:${filename}`);
603
+ return;
604
+ }
605
+ return macro;
579
606
  }
607
+ /**
608
+ * Extract all import declarations from AST
609
+ * Used to provide necessary imports when executing definePage arguments
610
+ *
611
+ * @param stmts - AST statement array
612
+ * @returns Import declaration array
613
+ */
580
614
  function findImports(stmts) {
581
- const imports = [];
582
- for (const stmt of stmts) {
583
- if (t.isImportDeclaration(stmt)) {
584
- imports.push(stmt);
585
- }
586
- }
587
- return imports;
615
+ const imports = [];
616
+ for (const stmt of stmts) if (t.isImportDeclaration(stmt)) imports.push(stmt);
617
+ return imports;
588
618
  }
589
-
590
- var __defProp = Object.defineProperty;
591
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
592
- var __publicField = (obj, key, value) => {
593
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
594
- return value;
619
+ //#endregion
620
+ //#region src/context.ts
621
+ /**
622
+ * Page context class responsible for page scanning, config loading, page metadata merging and pages.json generation
623
+ *
624
+ * Core responsibilities:
625
+ * 1. Scan page directories and collect page files
626
+ * 2. Load user configuration files (pages.config.ts, etc.)
627
+ * 3. Parse page metadata (definePage macro)
628
+ * 4. Merge page configurations and generate pages.json
629
+ * 5. Provide virtual module and HMR support
630
+ */
631
+ var PageContext = class {
632
+ _server;
633
+ /** Parsed configuration object from user config file (pages.config.ts) */
634
+ pagesGlobConfig;
635
+ /** Source path list of user configuration files */
636
+ pagesConfigSourcePaths = [];
637
+ /** Main package page map, key is the absolute path of the page file */
638
+ pages = /* @__PURE__ */ new Map();
639
+ /** Sub-package page map, key is the sub-package root directory, value is the page map under that sub-package */
640
+ subPages = /* @__PURE__ */ new Map();
641
+ /** Main package page metadata array for generating pages.json pages field */
642
+ pageMetaData = [];
643
+ /** Sub-package page metadata array for generating pages.json subPackages field */
644
+ subPageMetaData = [];
645
+ /** Generated pages.json file path */
646
+ resolvedPagesJSONPath = "";
647
+ resolvedPagesJSONIndent;
648
+ resolvedPagesJSONNewline;
649
+ resolvedPagesJSONEofNewline;
650
+ /** Original options passed by the user */
651
+ rawOptions;
652
+ /** Project root directory */
653
+ root;
654
+ /** Resolved configuration options */
655
+ options;
656
+ logger;
657
+ /** Whether to work with vite-plugin-uni-platform plugin */
658
+ withUniPlatform = false;
659
+ /** Cached previous pages.json content to avoid redundant writes */
660
+ lastPagesJson = "";
661
+ /**
662
+ * Create a PageContext instance
663
+ * @param userOptions - User configuration options
664
+ * @param viteRoot - Vite project root directory, defaults to current working directory
665
+ */
666
+ constructor(userOptions, viteRoot = process.cwd()) {
667
+ this.rawOptions = userOptions;
668
+ this.root = slash(viteRoot);
669
+ debug.options("root", this.root);
670
+ this.options = resolveOptions(userOptions, this.root);
671
+ const debugOption = this.options.debug;
672
+ if (debugOption) {
673
+ const prefix = "vite-plugin-uni-pages:";
674
+ const suffix = typeof debugOption === "boolean" ? "*" : debugOption;
675
+ dbg.enable(`${prefix}${suffix}`);
676
+ }
677
+ this.resolvedPagesJSONPath = path.join(this.root, this.options.outDir, OUTPUT_NAME);
678
+ debug.options(this.options);
679
+ }
680
+ /**
681
+ * Set the Vite logger
682
+ * @param logger - Vite logger instance
683
+ */
684
+ setLogger(logger) {
685
+ this.logger = logger;
686
+ }
687
+ /**
688
+ * Load user page configuration file (e.g. pages.config.ts)
689
+ * Uses unconfig to load configuration, supporting multiple config file formats
690
+ */
691
+ async loadUserPagesConfig() {
692
+ const configSource = this.options.configSource;
693
+ const { config, sources } = await loadConfig({
694
+ cwd: this.root,
695
+ sources: configSource,
696
+ defaults: {}
697
+ });
698
+ this.pagesGlobConfig = config.default || config;
699
+ this.pagesConfigSourcePaths = sources;
700
+ debug.options(this.pagesGlobConfig);
701
+ }
702
+ /**
703
+ * Scan main package page directories and collect all page file paths
704
+ * Scan corresponding directories based on the configured dirs option
705
+ */
706
+ async scanPages() {
707
+ const paths = this.options.dirs.map((dir) => {
708
+ return {
709
+ dir,
710
+ files: getPagePaths(dir, this.options)
711
+ };
712
+ }).map((page) => page.files).flat();
713
+ debug.pages(paths);
714
+ const pages = /* @__PURE__ */ new Map();
715
+ for (const path of paths) {
716
+ const page = this.pages.get(path.absolutePath) || new Page(this, path);
717
+ pages.set(path.absolutePath, page);
718
+ }
719
+ this.pages = pages;
720
+ }
721
+ /**
722
+ * Scan sub-package page directories and collect all sub-package page file paths
723
+ * Scan corresponding directories based on the configured subPackages option
724
+ */
725
+ async scanSubPages() {
726
+ const paths = {};
727
+ const subPages = /* @__PURE__ */ new Map();
728
+ for (const dir of this.options.subPackages) {
729
+ const pagePaths = getPagePaths(dir, this.options);
730
+ paths[dir] = pagePaths;
731
+ const pages = /* @__PURE__ */ new Map();
732
+ for (const path of pagePaths) {
733
+ const page = this.subPages.get(dir)?.get(path.absolutePath) || new Page(this, path);
734
+ pages.set(path.absolutePath, page);
735
+ }
736
+ subPages.set(dir, pages);
737
+ }
738
+ debug.subPages(JSON.stringify(paths, null, 2));
739
+ this.subPages = subPages;
740
+ }
741
+ /**
742
+ * Set up Vite dev server for HMR and file watching
743
+ * @param server - Vite dev server instance
744
+ */
745
+ setupViteServer(server) {
746
+ if (this._server === server) return;
747
+ this._server = server;
748
+ this.setupWatcher(server.watcher);
749
+ }
750
+ /**
751
+ * Set up file watcher to monitor page file and config file changes
752
+ * Automatically update pages.json when page files or config files change
753
+ * @param watcher - chokidar file watcher instance
754
+ */
755
+ async setupWatcher(watcher) {
756
+ watcher.add(this.pagesConfigSourcePaths);
757
+ const targetDirs = [...this.options.dirs, ...this.options.subPackages].map((v) => slash(path.resolve(this.root, v)));
758
+ const isInTargetDirs = (filePath) => targetDirs.some((v) => slash(path.resolve(this.root, filePath)).startsWith(v));
759
+ watcher.on("add", async (path) => {
760
+ path = slash(path);
761
+ if (!isTargetFile(path)) return;
762
+ if (!isInTargetDirs(path)) return;
763
+ debug.pages(`File added: ${path}`);
764
+ if (await this.updatePagesJSON()) this.onUpdate();
765
+ });
766
+ watcher.on("change", async (path) => {
767
+ path = slash(path);
768
+ if (!isTargetFile(path)) return;
769
+ if (!isInTargetDirs(path)) return;
770
+ debug.pages(`File changed: ${path}`);
771
+ debug.pages(targetDirs);
772
+ debug.pages(isInTargetDirs(path));
773
+ if (await this.updatePagesJSON(path)) this.onUpdate();
774
+ });
775
+ watcher.on("change", async (path) => {
776
+ if (this.pagesConfigSourcePaths.includes(path)) {
777
+ debug.pages(`Config source changed: ${path}`);
778
+ if (await this.updatePagesJSON()) this.onUpdate();
779
+ }
780
+ });
781
+ watcher.on("unlink", async (path) => {
782
+ path = slash(path);
783
+ if (!isTargetFile(path)) return;
784
+ if (!isInTargetDirs(path)) return;
785
+ debug.pages(`File removed: ${path}`);
786
+ if (await this.updatePagesJSON()) this.onUpdate();
787
+ });
788
+ }
789
+ /**
790
+ * Page update callback, triggered when page files or configuration changes
791
+ * Responsible for invalidating virtual modules and notifying the browser to do a full reload
792
+ */
793
+ onUpdate() {
794
+ if (!this._server) return;
795
+ invalidatePagesModule(this._server);
796
+ debug.hmr("Reload generated pages.");
797
+ this._server.ws.send({ type: "full-reload" });
798
+ }
799
+ /**
800
+ * parse pages rules && set page type
801
+ * @param pages page path array
802
+ * @param type page type
803
+ * @param overrides custom page config
804
+ * @returns pages rules
805
+ */
806
+ async parsePages(pages, type, overrides) {
807
+ const jobs = [];
808
+ for (const [_, page] of pages) jobs.push(page.getPageMeta());
809
+ const generatedPageMetaData = await Promise.all(jobs);
810
+ const customPageMetaData = overrides || [];
811
+ const result = customPageMetaData.length ? mergePageMetaDataArray(generatedPageMetaData.concat(customPageMetaData)) : generatedPageMetaData;
812
+ const parseMeta = Array.from(result.reduce((map, page) => {
813
+ map.set(page.path, page);
814
+ return map;
815
+ }, /* @__PURE__ */ new Map()).values());
816
+ return type === "main" ? this.setHomePage(parseMeta) : parseMeta;
817
+ }
818
+ /**
819
+ * set home page
820
+ * @param result pages rules array
821
+ * @returns pages rules
822
+ */
823
+ setHomePage(result) {
824
+ if (!result.some(({ type }) => type === "home")) {
825
+ const basePath = slash(path.join(this.options.root, this.options.outDir));
826
+ const resolvedHomePages = this.options.homePage.map((v) => {
827
+ return slash(path.relative(basePath, slash(path.resolve(basePath, v))));
828
+ });
829
+ const matchHomePage = (itemPath, configPath) => {
830
+ if (itemPath === configPath) return true;
831
+ const normalizedItem = itemPath.replace(/\\/g, "/");
832
+ const normalizedConfig = configPath.replace(/\\/g, "/");
833
+ return normalizedItem.endsWith(`/${normalizedConfig}`);
834
+ };
835
+ if (!result.some((item) => {
836
+ const isFound = resolvedHomePages.some((expectedPath) => matchHomePage(item.path, expectedPath));
837
+ if (isFound) item.type = "home";
838
+ return isFound;
839
+ })) this.logger?.warn("No home page found, check the configuration of pages.config.ts, or add the `homePage` option to UniPages in vite.config.js, or add `definePage({ type: \"home\" })` in your vue page.", { timestamp: true });
840
+ }
841
+ result.sort((page) => page.type === "home" ? -1 : 0);
842
+ return result;
843
+ }
844
+ /**
845
+ * Merge main package page metadata
846
+ * Filter out pages belonging to sub-packages, then parse page metadata and merge user configuration
847
+ */
848
+ async mergePageMetaData() {
849
+ const subPageAbsolutePaths = Array.from(this.subPages.values()).flatMap((v) => Array.from(v.keys()));
850
+ for (const subPageAbsolutePath of subPageAbsolutePaths) this.pages.delete(subPageAbsolutePath);
851
+ const pageMetaData = await this.parsePages(this.pages, "main", this.pagesGlobConfig?.pages);
852
+ this.pageMetaData = pageMetaData;
853
+ debug.pages(this.pageMetaData);
854
+ }
855
+ /**
856
+ * Merge sub-package page metadata
857
+ * Parse page metadata for each sub-package and handle sub-package configuration inheritance
858
+ * Preserves sub-package level properties like plugins from user config
859
+ */
860
+ async mergeSubPageMetaData() {
861
+ const subPageMaps = {};
862
+ const subPlugins = {};
863
+ const subPackages = this.pagesGlobConfig?.subPackages || [];
864
+ for (const [dir, pages] of this.subPages) {
865
+ const basePath = slash(path.join(this.options.root, this.options.outDir));
866
+ const root = this.options.subPackageRootMap.get(dir) ?? slash(path.relative(basePath, path.join(this.options.root, dir)));
867
+ const globPackage = subPackages?.find((v) => v.root === root);
868
+ subPageMaps[root] = await this.parsePages(pages, "sub", globPackage?.pages);
869
+ subPageMaps[root] = subPageMaps[root].map((page) => ({
870
+ ...page,
871
+ path: slash(path.relative(root, page.path))
872
+ }));
873
+ if (globPackage?.plugins) subPlugins[root] = globPackage.plugins;
874
+ }
875
+ for (const { root, pages, plugins } of subPackages) if (root && !subPageMaps[root]) {
876
+ subPageMaps[root] = pages || [];
877
+ if (plugins) subPlugins[root] = plugins;
878
+ }
879
+ const subPageMetaData = Object.keys(subPageMaps).map((root) => ({
880
+ root,
881
+ pages: subPageMaps[root],
882
+ ...subPlugins[root] && { plugins: subPlugins[root] }
883
+ })).filter((meta) => meta.pages.length > 0);
884
+ this.subPageMetaData = subPageMetaData;
885
+ debug.subPages(this.subPageMetaData);
886
+ }
887
+ /**
888
+ * Update pages.json file
889
+ * This is the core method responsible for coordinating the entire page configuration generation flow:
890
+ * 1. Check file changes (if filepath is specified)
891
+ * 2. Load user configuration
892
+ * 3. Scan page files
893
+ * 4. Merge page metadata
894
+ * 5. Generate and write pages.json
895
+ * @param filepath - Changed file path for incremental update judgment
896
+ * @returns Whether pages.json was successfully updated
897
+ */
898
+ async updatePagesJSON(filepath) {
899
+ if (filepath) {
900
+ let page = this.pages.get(filepath);
901
+ if (!page) {
902
+ let subPage;
903
+ for (const [_, pages] of this.subPages) {
904
+ subPage = pages.get(filepath);
905
+ if (subPage) break;
906
+ }
907
+ page = subPage;
908
+ }
909
+ if (page) {
910
+ await page.read();
911
+ if (!page.hasChanged()) {
912
+ debug.cache(`The page meta on page ${filepath} did not send any changes, skipping`);
913
+ return false;
914
+ }
915
+ }
916
+ }
917
+ checkPagesJsonFileSync(this.resolvedPagesJSONPath);
918
+ this.options.onBeforeLoadUserConfig(this);
919
+ await this.loadUserPagesConfig();
920
+ this.options.onAfterLoadUserConfig(this);
921
+ if (this.options.mergePages) {
922
+ this.options.onBeforeScanPages(this);
923
+ await this.scanPages();
924
+ await this.scanSubPages();
925
+ this.options.onAfterScanPages(this);
926
+ }
927
+ this.options.onBeforeMergePageMetaData(this);
928
+ await this.mergePageMetaData();
929
+ await this.mergeSubPageMetaData();
930
+ this.options.onAfterMergePageMetaData(this);
931
+ const pagesMap = /* @__PURE__ */ new Map();
932
+ (this.withUniPlatform ? this.pageMetaData.filter((v) => !/\..*$/.test(v.path) || v.path.includes(platform)).map((v) => {
933
+ v.path = v.path.replace(/\..*$/, "");
934
+ return v;
935
+ }) : this.pageMetaData).forEach((v) => pagesMap.set(v.path, v));
936
+ this.pageMetaData = [...pagesMap.values()];
937
+ this.options.onBeforeWriteFile(this);
938
+ const pagesJson = stringify(await this.genratePagesJSON(), null, this.options.minify ? void 0 : await this.getIndent()) + (await this.getEndOfLine() ? await this.getNewline() : "");
939
+ this.generateDeclaration();
940
+ if (this.lastPagesJson === pagesJson) {
941
+ debug.pages("PagesJson Not have change");
942
+ return false;
943
+ }
944
+ await writeFileWithLock(this.resolvedPagesJSONPath, pagesJson);
945
+ this.lastPagesJson = pagesJson;
946
+ this.options.onAfterWriteFile(this);
947
+ return true;
948
+ }
949
+ /**
950
+ * Generate virtual module content
951
+ * Returns JavaScript code containing pages and subPackages exports
952
+ * @returns Virtual module code string
953
+ */
954
+ virtualModule() {
955
+ return [`export const pages = ${this.resolveRoutes()};`, `export const subPackages = ${this.resolveSubRoutes()};`].join("\n");
956
+ }
957
+ /**
958
+ * Resolve main package route data to JSON string
959
+ * @returns JSON string of main package page metadata
960
+ */
961
+ resolveRoutes() {
962
+ return stringify(this.pageMetaData.map(stripType), null, 2);
963
+ }
964
+ /**
965
+ * Resolve sub-package route data to JSON string
966
+ * @returns JSON string of sub-package page metadata
967
+ */
968
+ resolveSubRoutes() {
969
+ return stringify(this.subPageMetaData.map(({ pages, ...rest }) => ({
970
+ ...rest,
971
+ pages: pages.map(stripType)
972
+ })), null, 2);
973
+ }
974
+ /**
975
+ * Resolve tabBar configuration
976
+ * Merges page-defined tabBar items with config-defined tabBar
977
+ * @returns Merged tabBar configuration object, or undefined if no tabBar exists
978
+ */
979
+ async resolveTabBar() {
980
+ const tabBarItems = [];
981
+ for (const [_, page] of this.pages) {
982
+ const tabbar = await page.getTabBar();
983
+ if (tabbar) tabBarItems.push(tabbar);
984
+ }
985
+ if (tabBarItems.length === 0) return this.pagesGlobConfig?.tabBar;
986
+ const tabBar = {
987
+ ...this.pagesGlobConfig?.tabBar,
988
+ list: this.pagesGlobConfig?.tabBar?.list || []
989
+ };
990
+ const pagePaths = /* @__PURE__ */ new Map();
991
+ for (const item of tabBar.list) pagePaths.set(item.pagePath, true);
992
+ tabBarItems.sort((a, b) => a.index - b.index);
993
+ for (const item of tabBarItems) if (!pagePaths.has(item.pagePath)) {
994
+ const { index: _, ...tabbar } = item;
995
+ tabBar.list.push(tabbar);
996
+ }
997
+ return tabBar;
998
+ }
999
+ /**
1000
+ * Generate TypeScript declaration file
1001
+ * Generate type definitions for page paths to provide type hints during navigation
1002
+ */
1003
+ generateDeclaration() {
1004
+ if (!this.options.dts) return;
1005
+ debug.declaration("generating");
1006
+ return writeDeclaration(this, this.options.dts);
1007
+ }
1008
+ async genratePagesJSON() {
1009
+ const { pages: oldPages, subPackages: oldSubPackages, tabBar: oldTabBar } = parse(await fs.promises.readFile(this.resolvedPagesJSONPath, { encoding: "utf-8" }).catch(() => "") || "{}");
1010
+ const { pages: _pages, subPackages: _subPackages, tabBar: _tabBar, ...pageJson } = this.pagesGlobConfig || {};
1011
+ const currentPlatform = platform.toUpperCase();
1012
+ pageJson.pages = mergePlatformItems(oldPages, currentPlatform, this.pageMetaData, "path").map(stripType);
1013
+ if (pageJson.pages && pageJson.pages.length > 0) {
1014
+ const pagesArray = pageJson.pages;
1015
+ const homeIndex = pagesArray.findIndex((page) => page.type === "home");
1016
+ if (homeIndex > 0) {
1017
+ const [homePage] = pagesArray.splice(homeIndex, 1);
1018
+ pagesArray.unshift(homePage);
1019
+ }
1020
+ }
1021
+ pageJson.subPackages = oldSubPackages || new CommentArray();
1022
+ const newSubPackages = /* @__PURE__ */ new Map();
1023
+ for (const item of this.subPageMetaData) newSubPackages.set(item.root, item);
1024
+ for (const existing of pageJson.subPackages) {
1025
+ const sub = newSubPackages.get(existing.root);
1026
+ if (sub) {
1027
+ existing.pages = mergePlatformItems(existing.pages, currentPlatform, sub.pages, "path").map(stripType);
1028
+ if (sub.plugins) existing.plugins = sub.plugins;
1029
+ newSubPackages.delete(existing.root);
1030
+ }
1031
+ }
1032
+ for (const [_, newSub] of newSubPackages) {
1033
+ const subPackage = {
1034
+ root: newSub.root,
1035
+ pages: mergePlatformItems(void 0, currentPlatform, newSub.pages, "path").map(stripType)
1036
+ };
1037
+ if (newSub.plugins) subPackage.plugins = newSub.plugins;
1038
+ pageJson.subPackages.push(subPackage);
1039
+ }
1040
+ const { list, ...tabBarOthers } = await this.resolveTabBar() || {};
1041
+ if (list) {
1042
+ const { list: oldList } = oldTabBar || {};
1043
+ const newList = mergePlatformItems(oldList, currentPlatform, list, "pagePath");
1044
+ pageJson.tabBar = {
1045
+ ...tabBarOthers,
1046
+ list: newList
1047
+ };
1048
+ } else pageJson.tabBar = void 0;
1049
+ return pageJson;
1050
+ }
1051
+ async readInfoFromPagesJSON() {
1052
+ const resolvedPagesJSONContent = await fs.promises.readFile(this.resolvedPagesJSONPath, { encoding: "utf-8" }).catch(() => "");
1053
+ this.resolvedPagesJSONIndent = detectIndent(resolvedPagesJSONContent).indent || " ";
1054
+ this.resolvedPagesJSONNewline = detectNewline(resolvedPagesJSONContent) || "\n";
1055
+ this.resolvedPagesJSONEofNewline = (resolvedPagesJSONContent.at(-1) ?? "\n") === this.resolvedPagesJSONNewline;
1056
+ }
1057
+ async getIndent() {
1058
+ if (!this.resolvedPagesJSONIndent) await this.readInfoFromPagesJSON();
1059
+ return this.resolvedPagesJSONIndent;
1060
+ }
1061
+ async getNewline() {
1062
+ if (!this.resolvedPagesJSONNewline) await this.readInfoFromPagesJSON();
1063
+ return this.resolvedPagesJSONNewline;
1064
+ }
1065
+ async getEndOfLine() {
1066
+ if (!this.resolvedPagesJSONEofNewline) await this.readInfoFromPagesJSON();
1067
+ return this.resolvedPagesJSONEofNewline;
1068
+ }
595
1069
  };
596
- class PageContext {
597
- constructor(userOptions, viteRoot = process.cwd()) {
598
- __publicField(this, "_server");
599
- __publicField(this, "pagesGlobConfig");
600
- __publicField(this, "pagesConfigSourcePaths", []);
601
- __publicField(this, "pages", /* @__PURE__ */ new Map());
602
- // abs path -> Page
603
- __publicField(this, "subPages", /* @__PURE__ */ new Map());
604
- // root -> abs path -> page
605
- __publicField(this, "pageMetaData", []);
606
- __publicField(this, "subPageMetaData", []);
607
- __publicField(this, "resolvedPagesJSONPath", "");
608
- __publicField(this, "resolvedPagesJSONIndent");
609
- // ' '
610
- __publicField(this, "resolvedPagesJSONNewline");
611
- // '\n'
612
- __publicField(this, "resolvedPagesJSONEofNewline");
613
- // true
614
- __publicField(this, "rawOptions");
615
- __publicField(this, "root");
616
- __publicField(this, "options");
617
- __publicField(this, "logger");
618
- __publicField(this, "withUniPlatform", false);
619
- __publicField(this, "lastPagesJson", "");
620
- this.rawOptions = userOptions;
621
- this.root = slash(viteRoot);
622
- debug.options("root", this.root);
623
- this.options = resolveOptions(userOptions, this.root);
624
- const debugOption = this.options.debug;
625
- if (debugOption) {
626
- const prefix = "vite-plugin-uni-pages:";
627
- const suffix = typeof debugOption === "boolean" ? "*" : debugOption;
628
- Debug.enable(`${prefix}${suffix}`);
629
- }
630
- this.resolvedPagesJSONPath = path.join(this.root, this.options.outDir, OUTPUT_NAME);
631
- debug.options(this.options);
632
- }
633
- setLogger(logger) {
634
- this.logger = logger;
635
- }
636
- async loadUserPagesConfig() {
637
- const configSource = this.options.configSource;
638
- const { config, sources } = await loadConfig({ cwd: this.root, sources: configSource, defaults: {} });
639
- this.pagesGlobConfig = config.default || config;
640
- this.pagesConfigSourcePaths = sources;
641
- debug.options(this.pagesGlobConfig);
642
- }
643
- async scanPages() {
644
- const pageDirFiles = this.options.dirs.map((dir) => {
645
- return { dir, files: getPagePaths(dir, this.options) };
646
- });
647
- const paths = pageDirFiles.map((page) => page.files).flat();
648
- debug.pages(paths);
649
- const pages = /* @__PURE__ */ new Map();
650
- for (const path2 of paths) {
651
- const page = this.pages.get(path2.absolutePath) || new Page(this, path2);
652
- pages.set(path2.absolutePath, page);
653
- }
654
- this.pages = pages;
655
- }
656
- async scanSubPages() {
657
- const paths = {};
658
- const subPages = /* @__PURE__ */ new Map();
659
- for (const dir of this.options.subPackages) {
660
- const pagePaths = getPagePaths(dir, this.options);
661
- paths[dir] = pagePaths;
662
- const pages = /* @__PURE__ */ new Map();
663
- for (const path2 of pagePaths) {
664
- const page = this.subPages.get(dir)?.get(path2.absolutePath) || new Page(this, path2);
665
- pages.set(path2.absolutePath, page);
666
- }
667
- subPages.set(dir, pages);
668
- }
669
- debug.subPages(JSON.stringify(paths, null, 2));
670
- this.subPages = subPages;
671
- }
672
- setupViteServer(server) {
673
- if (this._server === server)
674
- return;
675
- this._server = server;
676
- this.setupWatcher(server.watcher);
677
- }
678
- async setupWatcher(watcher) {
679
- watcher.add(this.pagesConfigSourcePaths);
680
- const targetDirs = [...this.options.dirs, ...this.options.subPackages].map((v) => slash(path.resolve(this.root, v)));
681
- const isInTargetDirs = (filePath) => targetDirs.some((v) => slash(path.resolve(this.root, filePath)).startsWith(v));
682
- watcher.on("add", async (path2) => {
683
- path2 = slash(path2);
684
- if (!isTargetFile(path2))
685
- return;
686
- if (!isInTargetDirs(path2))
687
- return;
688
- debug.pages(`File added: ${path2}`);
689
- if (await this.updatePagesJSON())
690
- this.onUpdate();
691
- });
692
- watcher.on("change", async (path2) => {
693
- path2 = slash(path2);
694
- if (!isTargetFile(path2))
695
- return;
696
- if (!isInTargetDirs(path2))
697
- return;
698
- debug.pages(`File changed: ${path2}`);
699
- debug.pages(targetDirs);
700
- debug.pages(isInTargetDirs(path2));
701
- if (await this.updatePagesJSON(path2))
702
- this.onUpdate();
703
- });
704
- watcher.on("change", async (path2) => {
705
- if (this.pagesConfigSourcePaths.includes(path2)) {
706
- debug.pages(`Config source changed: ${path2}`);
707
- if (await this.updatePagesJSON())
708
- this.onUpdate();
709
- }
710
- });
711
- watcher.on("unlink", async (path2) => {
712
- path2 = slash(path2);
713
- if (!isTargetFile(path2))
714
- return;
715
- if (!isInTargetDirs(path2))
716
- return;
717
- debug.pages(`File removed: ${path2}`);
718
- if (await this.updatePagesJSON())
719
- this.onUpdate();
720
- });
721
- }
722
- onUpdate() {
723
- if (!this._server)
724
- return;
725
- invalidatePagesModule(this._server);
726
- debug.hmr("Reload generated pages.");
727
- this._server.ws.send({
728
- type: "full-reload"
729
- });
730
- }
731
- /**
732
- * parse pages rules && set page type
733
- * @param pages page path array
734
- * @param type page type
735
- * @param overrides custom page config
736
- * @returns pages rules
737
- */
738
- async parsePages(pages, type, overrides) {
739
- const jobs = [];
740
- for (const [_, page] of pages) {
741
- jobs.push(page.getPageMeta());
742
- }
743
- const generatedPageMetaData = await Promise.all(jobs);
744
- const customPageMetaData = overrides || [];
745
- const result = customPageMetaData.length ? mergePageMetaDataArray(generatedPageMetaData.concat(customPageMetaData)) : generatedPageMetaData;
746
- const parseMeta = Array.from(
747
- result.reduce((map, page) => {
748
- map.set(page.path, page);
749
- return map;
750
- }, /* @__PURE__ */ new Map()).values()
751
- );
752
- return type === "main" ? this.setHomePage(parseMeta) : parseMeta;
753
- }
754
- /**
755
- * set home page
756
- * @param result pages rules array
757
- * @returns pages rules
758
- */
759
- setHomePage(result) {
760
- const hasHome = result.some(({ type }) => type === "home");
761
- if (!hasHome) {
762
- const isFoundHome = result.some((item) => {
763
- const isFound = this.options.homePage.find((v) => v === item.path);
764
- if (isFound)
765
- item.type = "home";
766
- return isFound;
767
- });
768
- if (!isFoundHome) {
769
- this.logger?.warn('No home page found, check the configuration of pages.config.ts, or add the `homePage` option to UniPages in vite.config.js, or add `type="home"` to the routeBlock of your vue page.', {
770
- timestamp: true
771
- });
772
- }
773
- }
774
- result.sort((page) => page.type === "home" ? -1 : 0);
775
- return result;
776
- }
777
- async mergePageMetaData() {
778
- const subPageAbsolutePaths = Array.from(this.subPages.values()).flatMap((v) => Array.from(v.keys()));
779
- for (const subPageAbsolutePath of subPageAbsolutePaths)
780
- this.pages.delete(subPageAbsolutePath);
781
- const pageMetaData = await this.parsePages(this.pages, "main", this.pagesGlobConfig?.pages);
782
- this.pageMetaData = pageMetaData;
783
- debug.pages(this.pageMetaData);
784
- }
785
- async mergeSubPageMetaData() {
786
- const subPageMaps = {};
787
- const subPackages = this.pagesGlobConfig?.subPackages || [];
788
- for (const [dir, pages] of this.subPages) {
789
- const basePath = slash(path.join(this.options.root, this.options.outDir));
790
- const root = slash(path.relative(basePath, path.join(this.options.root, dir)));
791
- const globPackage = subPackages?.find((v) => v.root === root);
792
- subPageMaps[root] = await this.parsePages(pages, "sub", globPackage?.pages);
793
- subPageMaps[root] = subPageMaps[root].map((page) => ({ ...page, path: slash(path.relative(root, page.path)) }));
794
- }
795
- for (const { root, pages } of subPackages) {
796
- if (root && !subPageMaps[root])
797
- subPageMaps[root] = pages || [];
798
- }
799
- const subPageMetaData = Object.keys(subPageMaps).map((root) => ({ root, pages: subPageMaps[root] })).filter((meta) => meta.pages.length > 0);
800
- this.subPageMetaData = subPageMetaData;
801
- debug.subPages(this.subPageMetaData);
802
- }
803
- async getTabBarMerged() {
804
- const tabBarItems = [];
805
- for (const [_, page] of this.pages) {
806
- const tabbar = await page.getTabBar();
807
- if (tabbar) {
808
- tabBarItems.push(tabbar);
809
- }
810
- }
811
- if (tabBarItems.length === 0) {
812
- return this.pagesGlobConfig?.tabBar;
813
- }
814
- const tabBar = {
815
- ...this.pagesGlobConfig?.tabBar,
816
- list: this.pagesGlobConfig?.tabBar?.list || []
817
- };
818
- const pagePaths = /* @__PURE__ */ new Map();
819
- for (const item of tabBar.list) {
820
- pagePaths.set(item.pagePath, true);
821
- }
822
- tabBarItems.sort((a, b) => a.index - b.index);
823
- for (const item of tabBarItems) {
824
- if (!pagePaths.has(item.pagePath)) {
825
- const { index: _, ...tabbar } = item;
826
- tabBar.list.push(tabbar);
827
- }
828
- }
829
- return tabBar;
830
- }
831
- async updatePagesJSON(filepath) {
832
- if (filepath) {
833
- let page = this.pages.get(filepath);
834
- if (!page) {
835
- let subPage;
836
- for (const [_, pages2] of this.subPages) {
837
- subPage = pages2.get(filepath);
838
- if (subPage) {
839
- break;
840
- }
841
- }
842
- page = subPage;
843
- }
844
- if (page) {
845
- await page.read();
846
- if (!page.hasChanged()) {
847
- debug.cache(`The route block on page ${filepath} did not send any changes, skipping`);
848
- return false;
849
- }
850
- }
851
- }
852
- checkPagesJsonFileSync(this.resolvedPagesJSONPath);
853
- this.options.onBeforeLoadUserConfig(this);
854
- await this.loadUserPagesConfig();
855
- this.options.onAfterLoadUserConfig(this);
856
- if (this.options.mergePages) {
857
- this.options.onBeforeScanPages(this);
858
- await this.scanPages();
859
- await this.scanSubPages();
860
- this.options.onAfterScanPages(this);
861
- }
862
- this.options.onBeforeMergePageMetaData(this);
863
- await this.mergePageMetaData();
864
- await this.mergeSubPageMetaData();
865
- this.options.onAfterMergePageMetaData(this);
866
- const pagesMap = /* @__PURE__ */ new Map();
867
- const pages = this.withUniPlatform ? this.pageMetaData.filter((v) => !/\..*$/.test(v.path) || v.path.includes(platform)).map((v) => {
868
- v.path = v.path.replace(/\..*$/, "");
869
- return v;
870
- }) : this.pageMetaData;
871
- pages.forEach((v) => pagesMap.set(v.path, v));
872
- this.pageMetaData = [...pagesMap.values()];
873
- this.options.onBeforeWriteFile(this);
874
- const data = await this.genratePagesJSON();
875
- const pagesJson = stringify(
876
- data,
877
- null,
878
- this.options.minify ? void 0 : await this.getIndent()
879
- ) + (await this.getEndOfLine() ? await this.getNewline() : "");
880
- this.generateDeclaration();
881
- if (this.lastPagesJson === pagesJson) {
882
- debug.pages("PagesJson Not have change");
883
- return false;
884
- }
885
- await writeFileWithLock(this.resolvedPagesJSONPath, pagesJson);
886
- this.lastPagesJson = pagesJson;
887
- this.options.onAfterWriteFile(this);
888
- return true;
889
- }
890
- virtualModule() {
891
- const pages = `export const pages = ${this.resolveRoutes()};`;
892
- const subPackages = `export const subPackages = ${this.resolveSubRoutes()};`;
893
- return [pages, subPackages].join("\n");
894
- }
895
- resolveRoutes() {
896
- return stringify(this.pageMetaData, null, 2);
897
- }
898
- resolveSubRoutes() {
899
- return stringify(this.subPageMetaData, null, 2);
900
- }
901
- generateDeclaration() {
902
- if (!this.options.dts)
903
- return;
904
- debug.declaration("generating");
905
- return writeDeclaration(this, this.options.dts);
906
- }
907
- async genratePagesJSON() {
908
- const content = await fs.promises.readFile(this.resolvedPagesJSONPath, { encoding: "utf-8" }).catch(() => "");
909
- const { pages: oldPages, subPackages: oldSubPackages, tabBar: oldTabBar } = parse(content || "{}");
910
- const { pages: _, subPackages: __, tabBar: ___, ...pageJson } = this.pagesGlobConfig || {};
911
- const currentPlatform = platform.toUpperCase();
912
- pageJson.pages = mergePlatformItems(oldPages, currentPlatform, this.pageMetaData, "path");
913
- pageJson.subPackages = oldSubPackages || new CommentArray();
914
- const newSubPackages = /* @__PURE__ */ new Map();
915
- for (const item of this.subPageMetaData) {
916
- newSubPackages.set(item.root, item);
917
- }
918
- for (const existing of pageJson.subPackages) {
919
- const sub = newSubPackages.get(existing.root);
920
- if (sub) {
921
- existing.pages = mergePlatformItems(existing.pages, currentPlatform, sub.pages, "path");
922
- newSubPackages.delete(existing.root);
923
- }
924
- }
925
- for (const [_2, newSub] of newSubPackages) {
926
- pageJson.subPackages.push({
927
- root: newSub.root,
928
- pages: mergePlatformItems(void 0, currentPlatform, newSub.pages, "path")
929
- });
930
- }
931
- const { list, ...tabBarOthers } = await this.getTabBarMerged() || {};
932
- if (list) {
933
- const { list: oldList } = oldTabBar || {};
934
- const newList = mergePlatformItems(oldList, currentPlatform, list, "pagePath");
935
- pageJson.tabBar = {
936
- ...tabBarOthers,
937
- // 每次都直接更新除 list 外的其他属性
938
- list: newList
939
- };
940
- } else {
941
- pageJson.tabBar = void 0;
942
- }
943
- return pageJson;
944
- }
945
- async readInfoFromPagesJSON() {
946
- const resolvedPagesJSONContent = await fs.promises.readFile(this.resolvedPagesJSONPath, { encoding: "utf-8" }).catch(() => "");
947
- this.resolvedPagesJSONIndent = detectIndent(resolvedPagesJSONContent).indent || " ";
948
- this.resolvedPagesJSONNewline = detectNewline(resolvedPagesJSONContent) || "\n";
949
- this.resolvedPagesJSONEofNewline = (resolvedPagesJSONContent.at(-1) ?? "\n") === this.resolvedPagesJSONNewline;
950
- }
951
- async getIndent() {
952
- if (!this.resolvedPagesJSONIndent) {
953
- await this.readInfoFromPagesJSON();
954
- }
955
- return this.resolvedPagesJSONIndent;
956
- }
957
- async getNewline() {
958
- if (!this.resolvedPagesJSONNewline) {
959
- await this.readInfoFromPagesJSON();
960
- }
961
- return this.resolvedPagesJSONNewline;
962
- }
963
- async getEndOfLine() {
964
- if (!this.resolvedPagesJSONEofNewline) {
965
- await this.readInfoFromPagesJSON();
966
- }
967
- return this.resolvedPagesJSONEofNewline;
968
- }
969
- }
1070
+ /**
1071
+ * Get all page paths in the specified directory
1072
+ * @param dir - Page directory path
1073
+ * @param options - Resolved configuration options
1074
+ * @returns Page path array containing relative and absolute paths
1075
+ */
970
1076
  function getPagePaths(dir, options) {
971
- const pagesDirPath = slash(path.resolve(options.root, dir));
972
- const basePath = slash(path.join(options.root, options.outDir));
973
- const files = getPageFiles(pagesDirPath, options);
974
- debug.pages(dir, files);
975
- const pagePaths = files.map((file) => slash(file)).map((file) => ({
976
- relativePath: path.relative(basePath, slash(path.resolve(pagesDirPath, file))),
977
- absolutePath: slash(path.resolve(pagesDirPath, file))
978
- }));
979
- return pagePaths;
1077
+ const pagesDirPath = slash(path.resolve(options.root, dir));
1078
+ const basePath = slash(path.join(options.root, options.outDir));
1079
+ const files = getPageFiles(pagesDirPath, options);
1080
+ debug.pages(dir, files);
1081
+ return files.map((file) => slash(file)).map((file) => ({
1082
+ relativePath: path.relative(basePath, slash(path.resolve(pagesDirPath, file))),
1083
+ absolutePath: slash(path.resolve(pagesDirPath, file))
1084
+ }));
980
1085
  }
1086
+ /**
1087
+ * Merge multi-platform page configuration items
1088
+ * Handle conditional compilation comments (#ifdef / #endif), merge configuration items from different platforms into one array
1089
+ * Same configuration items will automatically merge platform identifiers, different configuration items will keep conditional compilation comments
1090
+ *
1091
+ * @param source - Existing configuration item array (from pages.json)
1092
+ * @param currentPlatform - Current platform identifier (e.g. H5, MP-WEIXIN)
1093
+ * @param items - New configuration item array
1094
+ * @param uniqueKeyName - Field name used to identify configuration item uniqueness (e.g. 'path' or 'pagePath')
1095
+ * @returns Merged configuration item array with conditional compilation comments
1096
+ */
981
1097
  function mergePlatformItems(source, currentPlatform, items, uniqueKeyName) {
982
- const src = source || new CommentArray();
983
- currentPlatform = currentPlatform.toUpperCase();
984
- let lastPlatforms = [];
985
- for (const comment of src[Symbol.for("before:0")] || []) {
986
- const trimed = comment.value.trim();
987
- if (trimed.startsWith("GENERATED BY UNI-PAGES, PLATFORM:")) {
988
- lastPlatforms = trimed.split(":")[1].split("||").map((s) => s.trim()).filter((s) => s !== currentPlatform).sort();
989
- }
990
- }
991
- const tmpMap = /* @__PURE__ */ new Map();
992
- for (let i = 0; i < src.length; i++) {
993
- const item = src[i];
994
- const uniqueKey = item[uniqueKeyName];
995
- if (!uniqueKey) {
996
- continue;
997
- }
998
- const beforeComments = src[Symbol.for(`before:${i}`)];
999
- const ifdefComment = beforeComments?.find((c) => c.value.trim().startsWith("#ifdef"));
1000
- let platforms = [...lastPlatforms];
1001
- if (ifdefComment) {
1002
- const match = ifdefComment.value.match(/#ifdef\s+(.+)/);
1003
- if (match) {
1004
- platforms = match[1].split("||").map((p) => p.trim()).filter((s) => s !== currentPlatform).sort();
1005
- }
1006
- }
1007
- if (platforms.length === 0) {
1008
- continue;
1009
- }
1010
- const existing = tmpMap.get(uniqueKey) || [];
1011
- existing.push({ item, itemStr: JSON.stringify(item), platforms, platformStr: platforms.join(" || ") });
1012
- tmpMap.set(uniqueKey, existing);
1013
- }
1014
- for (const item of items) {
1015
- const newItem = item;
1016
- const uniqueKey = item[uniqueKeyName];
1017
- if (!uniqueKey) {
1018
- continue;
1019
- }
1020
- if (!tmpMap.has(uniqueKey)) {
1021
- tmpMap.set(uniqueKey, [{
1022
- item: newItem,
1023
- itemStr: JSON.stringify(newItem),
1024
- platforms: [currentPlatform],
1025
- platformStr: currentPlatform
1026
- }]);
1027
- continue;
1028
- }
1029
- const existing = tmpMap.get(uniqueKey);
1030
- const newItemStr = JSON.stringify(newItem);
1031
- const equalObj = existing.find((val) => val.itemStr === newItemStr);
1032
- if (equalObj) {
1033
- equalObj.platforms.push(currentPlatform);
1034
- equalObj.platforms.sort();
1035
- equalObj.platformStr = equalObj.platforms.join(" || ");
1036
- } else {
1037
- existing.push({
1038
- item: newItem,
1039
- itemStr: newItemStr,
1040
- platforms: [currentPlatform],
1041
- platformStr: currentPlatform
1042
- });
1043
- }
1044
- }
1045
- const result = new CommentArray();
1046
- const platformUsage = {};
1047
- tmpMap.forEach((val) => {
1048
- Object.values(val).forEach((v) => {
1049
- platformUsage[v.platformStr] = (platformUsage[v.platformStr] || 0) + 1;
1050
- });
1051
- });
1052
- const usageKeys = Object.keys(platformUsage);
1053
- const defaultPlatformStr = usageKeys.length ? usageKeys.reduce((a, b) => platformUsage[a] > platformUsage[b] ? a : b) : currentPlatform;
1054
- result[Symbol.for("before:0")] = [{
1055
- type: "LineComment",
1056
- value: ` GENERATED BY UNI-PAGES, PLATFORM: ${defaultPlatformStr}`,
1057
- inline: false,
1058
- loc: {
1059
- start: { line: 0, column: 0 },
1060
- end: { line: 0, column: 0 }
1061
- }
1062
- }];
1063
- for (const [_, list] of tmpMap) {
1064
- for (const { item, platformStr } of list) {
1065
- result.push(item);
1066
- if (platformStr !== defaultPlatformStr) {
1067
- result[Symbol.for(`before:${result.length - 1}`)] = [{
1068
- type: "LineComment",
1069
- value: ` #ifdef ${platformStr}`,
1070
- inline: false,
1071
- loc: {
1072
- start: { line: 0, column: 0 },
1073
- end: { line: 0, column: 0 }
1074
- }
1075
- }];
1076
- result[Symbol.for(`after:${result.length - 1}`)] = [{
1077
- type: "LineComment",
1078
- value: " #endif",
1079
- inline: false,
1080
- loc: {
1081
- start: { line: 0, column: 0 },
1082
- end: { line: 0, column: 0 }
1083
- }
1084
- }];
1085
- }
1086
- }
1087
- }
1088
- return result;
1098
+ const src = source || new CommentArray();
1099
+ currentPlatform = currentPlatform.toUpperCase();
1100
+ let lastPlatforms = [];
1101
+ for (const comment of src[Symbol.for("before:0")] || []) {
1102
+ const trimed = comment.value.trim();
1103
+ if (trimed.startsWith("GENERATED BY UNI-PAGES, PLATFORM:")) lastPlatforms = trimed.split(":")[1].split("||").map((s) => s.trim()).filter((s) => s !== currentPlatform).sort();
1104
+ }
1105
+ const tmpMap = /* @__PURE__ */ new Map();
1106
+ for (let i = 0; i < src.length; i++) {
1107
+ const item = src[i];
1108
+ const uniqueKey = item[uniqueKeyName];
1109
+ if (!uniqueKey) continue;
1110
+ const ifdefComment = src[Symbol.for(`before:${i}`)]?.find((c) => c.value.trim().startsWith("#ifdef"));
1111
+ let platforms = [...lastPlatforms];
1112
+ if (ifdefComment) {
1113
+ const match = ifdefComment.value.match(/#ifdef\s+(.+)/);
1114
+ if (match) platforms = match[1].split("||").map((p) => p.trim()).filter((s) => s !== currentPlatform).sort();
1115
+ }
1116
+ if (platforms.length === 0) continue;
1117
+ const existing = tmpMap.get(uniqueKey) || [];
1118
+ existing.push({
1119
+ item,
1120
+ itemStr: JSON.stringify(item),
1121
+ platforms,
1122
+ platformStr: platforms.join(" || ")
1123
+ });
1124
+ tmpMap.set(uniqueKey, existing);
1125
+ }
1126
+ for (const item of items) {
1127
+ const newItem = item;
1128
+ const uniqueKey = item[uniqueKeyName];
1129
+ if (!uniqueKey) continue;
1130
+ if (!tmpMap.has(uniqueKey)) {
1131
+ tmpMap.set(uniqueKey, [{
1132
+ item: newItem,
1133
+ itemStr: JSON.stringify(newItem),
1134
+ platforms: [currentPlatform],
1135
+ platformStr: currentPlatform
1136
+ }]);
1137
+ continue;
1138
+ }
1139
+ const existing = tmpMap.get(uniqueKey);
1140
+ const newItemStr = JSON.stringify(newItem);
1141
+ const equalObj = existing.find((val) => val.itemStr === newItemStr);
1142
+ if (equalObj) {
1143
+ equalObj.platforms.push(currentPlatform);
1144
+ equalObj.platforms.sort();
1145
+ equalObj.platformStr = equalObj.platforms.join(" || ");
1146
+ } else existing.push({
1147
+ item: newItem,
1148
+ itemStr: newItemStr,
1149
+ platforms: [currentPlatform],
1150
+ platformStr: currentPlatform
1151
+ });
1152
+ }
1153
+ const result = new CommentArray();
1154
+ const platformUsage = {};
1155
+ tmpMap.forEach((val) => {
1156
+ Object.values(val).forEach((v) => {
1157
+ platformUsage[v.platformStr] = (platformUsage[v.platformStr] || 0) + 1;
1158
+ });
1159
+ });
1160
+ const usageKeys = Object.keys(platformUsage);
1161
+ const defaultPlatformStr = usageKeys.length ? usageKeys.reduce((a, b) => platformUsage[a] > platformUsage[b] ? a : b) : currentPlatform;
1162
+ result[Symbol.for("before:0")] = [{
1163
+ type: "LineComment",
1164
+ value: ` GENERATED BY UNI-PAGES, PLATFORM: ${defaultPlatformStr}`,
1165
+ inline: false,
1166
+ loc: {
1167
+ start: {
1168
+ line: 0,
1169
+ column: 0
1170
+ },
1171
+ end: {
1172
+ line: 0,
1173
+ column: 0
1174
+ }
1175
+ }
1176
+ }];
1177
+ for (const [_, list] of tmpMap) for (const { item, platformStr } of list) {
1178
+ result.push(item);
1179
+ if (platformStr !== defaultPlatformStr) {
1180
+ result[Symbol.for(`before:${result.length - 1}`)] = [{
1181
+ type: "LineComment",
1182
+ value: ` #ifdef ${platformStr}`,
1183
+ inline: false,
1184
+ loc: {
1185
+ start: {
1186
+ line: 0,
1187
+ column: 0
1188
+ },
1189
+ end: {
1190
+ line: 0,
1191
+ column: 0
1192
+ }
1193
+ }
1194
+ }];
1195
+ result[Symbol.for(`after:${result.length - 1}`)] = [{
1196
+ type: "LineComment",
1197
+ value: " #endif",
1198
+ inline: false,
1199
+ loc: {
1200
+ start: {
1201
+ line: 0,
1202
+ column: 0
1203
+ },
1204
+ end: {
1205
+ line: 0,
1206
+ column: 0
1207
+ }
1208
+ }
1209
+ }];
1210
+ }
1211
+ }
1212
+ return result;
1089
1213
  }
1090
-
1214
+ //#endregion
1215
+ //#region src/config/index.ts
1216
+ /**
1217
+ * Define uni-pages configuration
1218
+ * Used in pages.config.ts configuration file to provide type hints and validation
1219
+ *
1220
+ * @param config - Page configuration object
1221
+ * @returns Configuration object
1222
+ */
1091
1223
  function defineUniPages(config) {
1092
- return config;
1224
+ return config;
1093
1225
  }
1094
-
1226
+ //#endregion
1227
+ //#region src/index.ts
1228
+ /**
1229
+ * vite-plugin-uni-pages plugin main entry
1230
+ *
1231
+ * Automatically scan page directories and generate pages.json configuration file
1232
+ * Support definePage macro for defining page metadata
1233
+ * Support multi-platform conditional compilation
1234
+ * Support sub-package configuration
1235
+ * Support TypeScript declaration file generation
1236
+ *
1237
+ * @param userOptions - User configuration options
1238
+ * @returns Vite plugin instance
1239
+ */
1095
1240
  function VitePluginUniPages(userOptions = {}) {
1096
- let ctx;
1097
- const resolvedPagesJSONPath = path.join(
1098
- process.env.VITE_ROOT_DIR || process.cwd(),
1099
- userOptions.outDir ?? "src",
1100
- OUTPUT_NAME
1101
- );
1102
- checkPagesJsonFileSync(resolvedPagesJSONPath);
1103
- return {
1104
- name: "vite-plugin-uni-pages",
1105
- enforce: "pre",
1106
- async configResolved(config) {
1107
- ctx = new PageContext(userOptions, config.root);
1108
- if (config.plugins.some((v) => v.name === "vite-plugin-uni-platform"))
1109
- ctx.withUniPlatform = true;
1110
- const logger = createLogger(void 0, {
1111
- prefix: "[vite-plugin-uni-pages]"
1112
- });
1113
- ctx.setLogger(logger);
1114
- await ctx.updatePagesJSON();
1115
- if (config.command === "build") {
1116
- if (config.build.watch)
1117
- ctx.setupWatcher(chokidar.watch([...ctx.options.dirs, ...ctx.options.subPackages]));
1118
- }
1119
- },
1120
- // Applet do not support custom route block, so we need to remove the route block here
1121
- async transform(code, id) {
1122
- if (!FILE_EXTENSIONS.find((ext) => id.endsWith(ext))) {
1123
- return null;
1124
- }
1125
- const sfc = parseSFC(code, { filename: id });
1126
- let macro;
1127
- if (sfc.scriptSetup) {
1128
- const ast = babelParse(sfc.scriptSetup.content, sfc.scriptSetup.lang || "js");
1129
- macro = findMacro(ast.body, sfc.filename);
1130
- }
1131
- if (!macro && sfc.script) {
1132
- const ast = babelParse(sfc.script.content, sfc.script.lang || "js");
1133
- macro = findMacro(ast.body, sfc.filename);
1134
- }
1135
- const routeBlock = sfc.customBlocks.find((block) => block.type === "route");
1136
- if (!macro && !routeBlock)
1137
- return null;
1138
- if (macro && routeBlock)
1139
- throw new Error(`\u4E0D\u652F\u6301\u6DF7\u5408\u4F7F\u7528 definePage() \u548C <route/> ${id}`);
1140
- const s = new MagicString(code);
1141
- if (macro)
1142
- s.remove(macro.start, macro.end);
1143
- if (routeBlock) {
1144
- console.log(lightYellow("\u8B66\u544A\uFF1A"), `${bold("<route/>")} \u6807\u7B7E\u5DF2\u5E9F\u5F03\uFF0C\u5C06\u5728\u4E0B\u4E00\u4E2A\u7248\u672C\u4E2D\u79FB\u9664\uFF0C\u8BF7\u4F7F\u7528 definePage() \u4EE3\u66FF\uFF1B${link("\u67E5\u770B\u8FC1\u79FB\u6307\u5357", "https://uni-helper.js.org/vite-plugin-uni-pages/definePage")}\u3002`);
1145
- console.log(dim(id));
1146
- console.log();
1147
- const routeBlockMatches = s.original.matchAll(
1148
- /<route[^>]*>([\s\S]*?)<\/route>/g
1149
- );
1150
- for (const match of routeBlockMatches) {
1151
- const index = match.index;
1152
- const length = match[0].length;
1153
- s.remove(index, index + length);
1154
- }
1155
- }
1156
- if (s.hasChanged()) {
1157
- return {
1158
- code: s.toString(),
1159
- map: s.generateMap({
1160
- source: id,
1161
- includeContent: true,
1162
- file: `${id}.map`
1163
- })
1164
- };
1165
- }
1166
- },
1167
- configureServer(server) {
1168
- ctx.setupViteServer(server);
1169
- },
1170
- resolveId(id) {
1171
- if (id === MODULE_ID_VIRTUAL)
1172
- return RESOLVED_MODULE_ID_VIRTUAL;
1173
- },
1174
- load(id) {
1175
- if (id === RESOLVED_MODULE_ID_VIRTUAL)
1176
- return ctx.virtualModule();
1177
- }
1178
- };
1241
+ let ctx;
1242
+ checkPagesJsonFileSync(path.join(process.env.VITE_ROOT_DIR || process.cwd(), userOptions.outDir ?? "src", OUTPUT_NAME));
1243
+ return {
1244
+ name: "vite-plugin-uni-pages",
1245
+ enforce: "pre",
1246
+ /**
1247
+ * Vite configResolved hook
1248
+ * Initialize PageContext, set logger, generate initial pages.json
1249
+ */
1250
+ async configResolved(config) {
1251
+ ctx = new PageContext(userOptions, config.root);
1252
+ if (config.plugins.some((v) => v.name === "vite-plugin-uni-platform")) ctx.withUniPlatform = true;
1253
+ const logger = createLogger(void 0, { prefix: "[vite-plugin-uni-pages]" });
1254
+ ctx.setLogger(logger);
1255
+ await ctx.updatePagesJSON();
1256
+ if (config.command === "build") {
1257
+ if (config.build.watch) ctx.setupWatcher(chokidar.watch([...ctx.options.dirs, ...ctx.options.subPackages]));
1258
+ }
1259
+ },
1260
+ /**
1261
+ * Code transform hook
1262
+ * Remove definePage macro calls from Vue SFC to avoid runtime errors
1263
+ */
1264
+ async transform(code, id) {
1265
+ if (!FILE_EXTENSIONS.some((ext) => id.endsWith(ext))) return null;
1266
+ const sfc = parseSFC(code, { filename: id });
1267
+ let macro;
1268
+ if (sfc.scriptSetup) macro = findMacro(babelParse(sfc.scriptSetup.content, sfc.scriptSetup.lang || "js").body, sfc.filename);
1269
+ if (!macro && sfc.script) macro = findMacro(babelParse(sfc.script.content, sfc.script.lang || "js").body, sfc.filename);
1270
+ if (!macro) return null;
1271
+ const s = new MagicString(code);
1272
+ s.remove(macro.start, macro.end);
1273
+ if (s.hasChanged()) return {
1274
+ code: s.toString(),
1275
+ map: s.generateMap({
1276
+ source: id,
1277
+ includeContent: true,
1278
+ file: `${id}.map`
1279
+ })
1280
+ };
1281
+ },
1282
+ /**
1283
+ * Configure server hook
1284
+ * Set up file watching and HMR support
1285
+ */
1286
+ configureServer(server) {
1287
+ ctx.setupViteServer(server);
1288
+ },
1289
+ /**
1290
+ * Module resolution hook
1291
+ * Resolve virtual module identifier to internal path
1292
+ */
1293
+ resolveId(id) {
1294
+ if (id === "virtual:uni-pages") return RESOLVED_MODULE_ID_VIRTUAL;
1295
+ },
1296
+ /**
1297
+ * Module load hook
1298
+ * Return the code content of the virtual module
1299
+ */
1300
+ load(id) {
1301
+ if (id === RESOLVED_MODULE_ID_VIRTUAL) return ctx.virtualModule();
1302
+ }
1303
+ };
1179
1304
  }
1180
-
1181
- export { FILE_EXTENSIONS, MODULE_ID_VIRTUAL, OUTPUT_NAME, Page, PageContext, RESOLVED_MODULE_ID_VIRTUAL, VitePluginUniPages, babelGenerate, checkPagesJsonFileSync, debug, VitePluginUniPages as default, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, getRouteBlock, getRouteSfcBlock, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseCustomBlock, parseSFC, resolveOptions, resolvePageDirs, sleep, tryPageMetaFromCustomBlock, tryPageMetaFromMacro, writeFileWithLock };
1305
+ //#endregion
1306
+ export { FILE_EXTENSIONS, MODULE_ID_VIRTUAL, OUTPUT_NAME, Page, PageContext, RESOLVED_MODULE_ID_VIRTUAL, VitePluginUniPages, VitePluginUniPages as default, babelGenerate, checkPagesJsonFileSync, debug, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, tryPageMetaFromMacro, writeFileWithLock };