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