@uni-helper/vite-plugin-uni-pages 0.4.2 → 0.4.4

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/README.md CHANGED
@@ -246,6 +246,19 @@ export default defineConfig({
246
246
 
247
247
  更多上下文参考 <https://github.com/uni-helper/vite-plugin-uni-pages/issues/271>。
248
248
 
249
+ ## 多终端并发开发
250
+
251
+ 同一项目里同时运行到多个平台时(例如,两个终端分别运行 `pnpm run dev:mp-weixin` 和 `pnpm run dev:mp-alipay`),多个进程会读写同一个 `pages.json`。
252
+
253
+ 本插件默认会:
254
+
255
+ - 通过文件锁串行化对 `pages.json` 的「读取已有内容 → 合并当前平台配置 → 写回」整个流程,避免并发写入互相覆盖;
256
+ - 保留其他平台已经写入的条件编译块(`#ifdef H5` / `#ifndef MP-WEIXIN` 等),只更新当前平台对应的条目。
257
+
258
+ 这样每个终端都能保留各自的平台配置。条件编译注释基于 `comment-json` 写入,最终 `pages.json` 同时包含所有平台的分支。
259
+
260
+ > 注意:每个终端只生成「当前平台」的条目并合并到 `pages.json`,并不会主动列出所有平台。`pages.json` 里出现哪些平台的条件编译块,取决于哪些终端正在运行、以及历史写入过的内容。若需要在单次构建中就输出全部平台的条件编译语句,请关注后续基于 [`pages-json`](https://github.com/uni-ku/pages-json) 条件编译的方案。
261
+
249
262
  ## 感谢
250
263
 
251
264
  - [hannoeru/vite-plugin-pages](https://github.com/hannoeru/vite-plugin-pages)
package/client.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /// <reference path="./global.d.ts" />
2
2
 
3
3
  declare module 'virtual:uni-pages' {
4
- import type { SubPackage } from './src/config/index'
5
- import type { PageMetaDatum } from './src/types'
4
+ import type { PageMetaDatum, SubPackage } from '@uni-helper/uni-pages-types'
6
5
 
7
6
  export const pages: PageMetaDatum[]
8
7
  export const subPackages: SubPackage[]
package/dist/index.cjs CHANGED
@@ -45,12 +45,12 @@ detect_indent = __toESM(detect_indent, 1);
45
45
  let detect_newline = require("detect-newline");
46
46
  detect_newline = __toESM(detect_newline, 1);
47
47
  let unconfig = require("unconfig");
48
+ let write_file_atomic = require("write-file-atomic");
49
+ write_file_atomic = __toESM(write_file_atomic, 1);
48
50
  let node_fs_promises = require("node:fs/promises");
49
51
  let proper_lockfile = require("proper-lockfile");
50
52
  proper_lockfile = __toESM(proper_lockfile, 1);
51
53
  let tinyglobby = require("tinyglobby");
52
- let write_file_atomic = require("write-file-atomic");
53
- write_file_atomic = __toESM(write_file_atomic, 1);
54
54
  let node_module = require("node:module");
55
55
  let node_vm = require("node:vm");
56
56
  node_vm = __toESM(node_vm, 1);
@@ -316,6 +316,23 @@ function stripType(item) {
316
316
  const { type, ...rest } = item;
317
317
  return rest;
318
318
  }
319
+ /**
320
+ * Remove the internal `type` marker (`'home'` / `'page'`) from every element of
321
+ * a `CommentArray` *in place* and return the same array.
322
+ *
323
+ * Unlike `items.map(stripType)`, this preserves the `Symbol.for('before:N')` /
324
+ * `Symbol.for('after:N')` comment slots that `comment-json` attaches to the
325
+ * array, so multi-platform `#ifdef` / `#endif` conditional-compilation blocks
326
+ * survive serialization into pages.json. `.map()` rebuilds the array and drops
327
+ * those symbol-keyed comments.
328
+ *
329
+ * @param items - CommentArray (or plain array) of page/tabBar metadata
330
+ * @returns The same array reference, with `type` removed from each element
331
+ */
332
+ function stripTypeInPlace(items) {
333
+ for (const item of items) if (item && "type" in item) delete item.type;
334
+ return items;
335
+ }
319
336
  //#endregion
320
337
  //#region src/files.ts
321
338
  /**
@@ -383,17 +400,22 @@ function checkPagesJsonFileSync(path) {
383
400
  }
384
401
  }
385
402
  /**
386
- * Safely write file using file lock
387
- * Avoid data corruption caused by concurrent writes through file lock
388
- * Use atomic write to ensure file write integrity
403
+ * Run a task while holding an exclusive file lock.
389
404
  *
390
- * @param path - File path
391
- * @param content - File content
405
+ * Unlike {@link writeFileWithLock}, this protects the whole read-modify-write
406
+ * critical section. The lock is held from the moment `task` starts until it
407
+ * resolves, so concurrent processes cannot observe or overwrite a half-written
408
+ * state. This is required by pages.json generation, where the new content
409
+ * depends on the current content (other platforms' `#ifdef` blocks).
410
+ *
411
+ * @param path - File path used as the lock target
412
+ * @param task - Async work to run inside the lock; return value is forwarded
392
413
  * @param retry - Number of retries when lock acquisition fails, defaults to 3
414
+ * @returns The value resolved by `task`, or `undefined` if the lock could not be acquired
393
415
  */
394
- async function writeFileWithLock(path, content, retry = 3) {
416
+ async function withFileLock(path, task, retry = 3) {
395
417
  if (retry <= 0) {
396
- debug$1.error(`${path} Failed to acquire file lock, write failed`);
418
+ debug$1.error(`${path} Failed to acquire file lock, task aborted`);
397
419
  return;
398
420
  }
399
421
  let release;
@@ -402,13 +424,31 @@ async function writeFileWithLock(path, content, retry = 3) {
402
424
  release = await proper_lockfile.default.lock(path, { realpath: false });
403
425
  } catch {
404
426
  await sleep(500);
405
- return writeFileWithLock(path, content, retry - 1);
427
+ return withFileLock(path, task, retry - 1);
406
428
  }
407
- await (0, write_file_atomic.default)(path, content);
429
+ return await task();
408
430
  } finally {
409
431
  if (release) await release();
410
432
  }
411
433
  }
434
+ /**
435
+ * Safely write file using file lock
436
+ * Avoid data corruption caused by concurrent writes through file lock
437
+ * Use atomic write to ensure file write integrity
438
+ *
439
+ * Note: this only makes the *write* atomic. Callers that need to read the
440
+ * current content before computing the new one must use {@link withFileLock}
441
+ * so the read and write belong to the same critical section.
442
+ *
443
+ * @param path - File path
444
+ * @param content - File content
445
+ * @param retry - Number of retries when lock acquisition fails, defaults to 3
446
+ */
447
+ async function writeFileWithLock(path, content, retry = 3) {
448
+ return withFileLock(path, async () => {
449
+ await (0, write_file_atomic.default)(path, content);
450
+ }, retry);
451
+ }
412
452
  //#endregion
413
453
  //#region src/options.ts
414
454
  /**
@@ -976,16 +1016,19 @@ var PageContext = class {
976
1016
  }) : this.pageMetaData).forEach((v) => pagesMap.set(v.path, v));
977
1017
  this.pageMetaData = [...pagesMap.values()];
978
1018
  this.options.onBeforeWriteFile(this);
979
- const pagesJson = (0, comment_json.stringify)(await this.genratePagesJSON(), null, this.options.minify ? void 0 : await this.getIndent()) + (await this.getEndOfLine() ? await this.getNewline() : "");
1019
+ const updated = await withFileLock(this.resolvedPagesJSONPath, async () => {
1020
+ const pagesJson = (0, comment_json.stringify)(await this.genratePagesJSON(), null, this.options.minify ? void 0 : await this.getIndent()) + (await this.getEndOfLine() ? await this.getNewline() : "");
1021
+ if (this.lastPagesJson === pagesJson) {
1022
+ debug$1.pages("PagesJson Not have change");
1023
+ return false;
1024
+ }
1025
+ await (0, write_file_atomic.default)(this.resolvedPagesJSONPath, pagesJson);
1026
+ this.lastPagesJson = pagesJson;
1027
+ return true;
1028
+ });
980
1029
  this.generateDeclaration();
981
- if (this.lastPagesJson === pagesJson) {
982
- debug$1.pages("PagesJson Not have change");
983
- return false;
984
- }
985
- await writeFileWithLock(this.resolvedPagesJSONPath, pagesJson);
986
- this.lastPagesJson = pagesJson;
987
- this.options.onAfterWriteFile(this);
988
- return true;
1030
+ if (updated) this.options.onAfterWriteFile(this);
1031
+ return updated ?? false;
989
1032
  }
990
1033
  /**
991
1034
  * Generate virtual module content
@@ -1050,7 +1093,7 @@ var PageContext = class {
1050
1093
  const { pages: oldPages, subPackages: oldSubPackages, tabBar: oldTabBar } = (0, comment_json.parse)(await node_fs.default.promises.readFile(this.resolvedPagesJSONPath, { encoding: "utf-8" }).catch(() => "") || "{}");
1051
1094
  const { pages: _pages, subPackages: _subPackages, tabBar: _tabBar, ...pageJson } = this.pagesGlobConfig || {};
1052
1095
  const currentPlatform = _uni_helper_uni_env.platform.toUpperCase();
1053
- pageJson.pages = mergePlatformItems(oldPages, currentPlatform, this.pageMetaData, "path").map(stripType);
1096
+ pageJson.pages = stripTypeInPlace(mergePlatformItems(oldPages, currentPlatform, this.pageMetaData, "path"));
1054
1097
  if (pageJson.pages && pageJson.pages.length > 0) {
1055
1098
  const pagesArray = pageJson.pages;
1056
1099
  const homeIndex = pagesArray.findIndex((page) => page.type === "home");
@@ -1065,7 +1108,7 @@ var PageContext = class {
1065
1108
  for (const existing of pageJson.subPackages) {
1066
1109
  const sub = newSubPackages.get(existing.root);
1067
1110
  if (sub) {
1068
- existing.pages = mergePlatformItems(existing.pages, currentPlatform, sub.pages, "path").map(stripType);
1111
+ existing.pages = stripTypeInPlace(mergePlatformItems(existing.pages, currentPlatform, sub.pages, "path"));
1069
1112
  if (sub.plugins) existing.plugins = sub.plugins;
1070
1113
  newSubPackages.delete(existing.root);
1071
1114
  }
@@ -1073,7 +1116,7 @@ var PageContext = class {
1073
1116
  for (const [_, newSub] of newSubPackages) {
1074
1117
  const subPackage = {
1075
1118
  root: newSub.root,
1076
- pages: mergePlatformItems(void 0, currentPlatform, newSub.pages, "path").map(stripType)
1119
+ pages: stripTypeInPlace(mergePlatformItems(void 0, currentPlatform, newSub.pages, "path"))
1077
1120
  };
1078
1121
  if (newSub.plugins) subPackage.plugins = newSub.plugins;
1079
1122
  pageJson.subPackages.push(subPackage);
@@ -1369,5 +1412,7 @@ exports.resolveOptions = resolveOptions;
1369
1412
  exports.resolvePageDirs = resolvePageDirs;
1370
1413
  exports.sleep = sleep;
1371
1414
  exports.stripType = stripType;
1415
+ exports.stripTypeInPlace = stripTypeInPlace;
1372
1416
  exports.tryPageMetaFromMacro = tryPageMetaFromMacro;
1417
+ exports.withFileLock = withFileLock;
1373
1418
  exports.writeFileWithLock = writeFileWithLock;
package/dist/index.d.cts CHANGED
@@ -337,6 +337,20 @@ declare function sleep(ms: number): Promise<unknown>;
337
337
  /** Babel code generator for converting AST back to code */
338
338
  declare const babelGenerate: typeof babelGenerator;
339
339
  declare function stripType<T extends Record<string, unknown>>(item: T): Omit<T, 'type'>;
340
+ /**
341
+ * Remove the internal `type` marker (`'home'` / `'page'`) from every element of
342
+ * a `CommentArray` *in place* and return the same array.
343
+ *
344
+ * Unlike `items.map(stripType)`, this preserves the `Symbol.for('before:N')` /
345
+ * `Symbol.for('after:N')` comment slots that `comment-json` attaches to the
346
+ * array, so multi-platform `#ifdef` / `#endif` conditional-compilation blocks
347
+ * survive serialization into pages.json. `.map()` rebuilds the array and drops
348
+ * those symbol-keyed comments.
349
+ *
350
+ * @param items - CommentArray (or plain array) of page/tabBar metadata
351
+ * @returns The same array reference, with `type` removed from each element
352
+ */
353
+ declare function stripTypeInPlace<T extends Record<string, unknown>>(items: T[]): T[];
340
354
  //#endregion
341
355
  //#region src/types.d.ts
342
356
  /** Debug log type, corresponding to methods in the debug object */
@@ -506,16 +520,35 @@ declare function getPageFiles(path: string, options: ResolvedOptions): string[];
506
520
  * @returns boolean - Whether the operation was successful
507
521
  */
508
522
  declare function checkPagesJsonFileSync(path: fs.PathLike): boolean;
523
+ /**
524
+ * Run a task while holding an exclusive file lock.
525
+ *
526
+ * Unlike {@link writeFileWithLock}, this protects the whole read-modify-write
527
+ * critical section. The lock is held from the moment `task` starts until it
528
+ * resolves, so concurrent processes cannot observe or overwrite a half-written
529
+ * state. This is required by pages.json generation, where the new content
530
+ * depends on the current content (other platforms' `#ifdef` blocks).
531
+ *
532
+ * @param path - File path used as the lock target
533
+ * @param task - Async work to run inside the lock; return value is forwarded
534
+ * @param retry - Number of retries when lock acquisition fails, defaults to 3
535
+ * @returns The value resolved by `task`, or `undefined` if the lock could not be acquired
536
+ */
537
+ declare function withFileLock<T>(path: string, task: () => Promise<T>, retry?: number): Promise<T | undefined>;
509
538
  /**
510
539
  * Safely write file using file lock
511
540
  * Avoid data corruption caused by concurrent writes through file lock
512
541
  * Use atomic write to ensure file write integrity
513
542
  *
543
+ * Note: this only makes the *write* atomic. Callers that need to read the
544
+ * current content before computing the new one must use {@link withFileLock}
545
+ * so the read and write belong to the same critical section.
546
+ *
514
547
  * @param path - File path
515
548
  * @param content - File content
516
549
  * @param retry - Number of retries when lock acquisition fails, defaults to 3
517
550
  */
518
- declare function writeFileWithLock(path: string, content: string, retry?: number): Promise<void>;
551
+ declare function writeFileWithLock(path: string, content: string, retry?: number): Promise<void | undefined>;
519
552
  //#endregion
520
553
  //#region src/options.d.ts
521
554
  /**
@@ -532,7 +565,7 @@ declare function resolveOptions(userOptions: UserOptions, viteRoot?: string): Re
532
565
  */
533
566
  declare function resolvePageDirs(dir: string, root: string, exclude: string[]): string[];
534
567
  declare namespace index_d_exports {
535
- export { ConfigSource, DefinePage, ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, MaybeCallable, MaybePromise, MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, SubPageMetaDatum, UserOptions, UserPageMeta, UserTabBarItem, VitePluginUniPages, babelGenerate, checkPagesJsonFileSync, debug, debugType, VitePluginUniPages as default, definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, tryPageMetaFromMacro, writeFileWithLock };
568
+ export { ConfigSource, DefinePage, ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, MaybeCallable, MaybePromise, MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, SubPageMetaDatum, UserOptions, UserPageMeta, UserTabBarItem, VitePluginUniPages, babelGenerate, checkPagesJsonFileSync, debug, debugType, VitePluginUniPages as default, definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, stripTypeInPlace, tryPageMetaFromMacro, withFileLock, writeFileWithLock };
536
569
  }
537
570
  /**
538
571
  * vite-plugin-uni-pages plugin main entry
@@ -548,4 +581,4 @@ declare namespace index_d_exports {
548
581
  */
549
582
  declare function VitePluginUniPages(userOptions?: UserOptions): Plugin;
550
583
  //#endregion
551
- export { ConfigSource, type DefinePage, type ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, type MaybeCallable, type MaybePromise, type MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, type PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, type SubPageMetaDatum, UserOptions, type UserPageMeta, type UserTabBarItem, VitePluginUniPages, VitePluginUniPages as default, babelGenerate, checkPagesJsonFileSync, debug, debugType, type definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, tryPageMetaFromMacro, writeFileWithLock };
584
+ export { ConfigSource, type DefinePage, type ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, type MaybeCallable, type MaybePromise, type MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, type PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, type SubPageMetaDatum, UserOptions, type UserPageMeta, type UserTabBarItem, VitePluginUniPages, VitePluginUniPages as default, babelGenerate, checkPagesJsonFileSync, debug, debugType, type definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, stripTypeInPlace, tryPageMetaFromMacro, withFileLock, writeFileWithLock };
package/dist/index.d.mts CHANGED
@@ -338,6 +338,20 @@ declare function sleep(ms: number): Promise<unknown>;
338
338
  /** Babel code generator for converting AST back to code */
339
339
  declare const babelGenerate: typeof babelGenerator;
340
340
  declare function stripType<T extends Record<string, unknown>>(item: T): Omit<T, 'type'>;
341
+ /**
342
+ * Remove the internal `type` marker (`'home'` / `'page'`) from every element of
343
+ * a `CommentArray` *in place* and return the same array.
344
+ *
345
+ * Unlike `items.map(stripType)`, this preserves the `Symbol.for('before:N')` /
346
+ * `Symbol.for('after:N')` comment slots that `comment-json` attaches to the
347
+ * array, so multi-platform `#ifdef` / `#endif` conditional-compilation blocks
348
+ * survive serialization into pages.json. `.map()` rebuilds the array and drops
349
+ * those symbol-keyed comments.
350
+ *
351
+ * @param items - CommentArray (or plain array) of page/tabBar metadata
352
+ * @returns The same array reference, with `type` removed from each element
353
+ */
354
+ declare function stripTypeInPlace<T extends Record<string, unknown>>(items: T[]): T[];
341
355
  //#endregion
342
356
  //#region src/types.d.ts
343
357
  /** Debug log type, corresponding to methods in the debug object */
@@ -507,16 +521,35 @@ declare function getPageFiles(path: string, options: ResolvedOptions): string[];
507
521
  * @returns boolean - Whether the operation was successful
508
522
  */
509
523
  declare function checkPagesJsonFileSync(path: fs.PathLike): boolean;
524
+ /**
525
+ * Run a task while holding an exclusive file lock.
526
+ *
527
+ * Unlike {@link writeFileWithLock}, this protects the whole read-modify-write
528
+ * critical section. The lock is held from the moment `task` starts until it
529
+ * resolves, so concurrent processes cannot observe or overwrite a half-written
530
+ * state. This is required by pages.json generation, where the new content
531
+ * depends on the current content (other platforms' `#ifdef` blocks).
532
+ *
533
+ * @param path - File path used as the lock target
534
+ * @param task - Async work to run inside the lock; return value is forwarded
535
+ * @param retry - Number of retries when lock acquisition fails, defaults to 3
536
+ * @returns The value resolved by `task`, or `undefined` if the lock could not be acquired
537
+ */
538
+ declare function withFileLock<T>(path: string, task: () => Promise<T>, retry?: number): Promise<T | undefined>;
510
539
  /**
511
540
  * Safely write file using file lock
512
541
  * Avoid data corruption caused by concurrent writes through file lock
513
542
  * Use atomic write to ensure file write integrity
514
543
  *
544
+ * Note: this only makes the *write* atomic. Callers that need to read the
545
+ * current content before computing the new one must use {@link withFileLock}
546
+ * so the read and write belong to the same critical section.
547
+ *
515
548
  * @param path - File path
516
549
  * @param content - File content
517
550
  * @param retry - Number of retries when lock acquisition fails, defaults to 3
518
551
  */
519
- declare function writeFileWithLock(path: string, content: string, retry?: number): Promise<void>;
552
+ declare function writeFileWithLock(path: string, content: string, retry?: number): Promise<void | undefined>;
520
553
  //#endregion
521
554
  //#region src/options.d.ts
522
555
  /**
@@ -533,7 +566,7 @@ declare function resolveOptions(userOptions: UserOptions, viteRoot?: string): Re
533
566
  */
534
567
  declare function resolvePageDirs(dir: string, root: string, exclude: string[]): string[];
535
568
  declare namespace index_d_exports {
536
- export { ConfigSource, DefinePage, ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, MaybeCallable, MaybePromise, MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, SubPageMetaDatum, UserOptions, UserPageMeta, UserTabBarItem, VitePluginUniPages, babelGenerate, checkPagesJsonFileSync, debug, debugType, VitePluginUniPages as default, definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, tryPageMetaFromMacro, writeFileWithLock };
569
+ export { ConfigSource, DefinePage, ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, MaybeCallable, MaybePromise, MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, SubPageMetaDatum, UserOptions, UserPageMeta, UserTabBarItem, VitePluginUniPages, babelGenerate, checkPagesJsonFileSync, debug, debugType, VitePluginUniPages as default, definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, stripTypeInPlace, tryPageMetaFromMacro, withFileLock, writeFileWithLock };
537
570
  }
538
571
  /**
539
572
  * vite-plugin-uni-pages plugin main entry
@@ -549,4 +582,4 @@ declare namespace index_d_exports {
549
582
  */
550
583
  declare function VitePluginUniPages(userOptions?: UserOptions): Plugin;
551
584
  //#endregion
552
- export { ConfigSource, type DefinePage, type ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, type MaybeCallable, type MaybePromise, type MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, type PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, type SubPageMetaDatum, UserOptions, type UserPageMeta, type UserTabBarItem, VitePluginUniPages, VitePluginUniPages as default, babelGenerate, checkPagesJsonFileSync, debug, debugType, type definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, tryPageMetaFromMacro, writeFileWithLock };
585
+ export { ConfigSource, type DefinePage, type ExcludeIndexSignature, FILE_EXTENSIONS, MODULE_ID_VIRTUAL, type MaybeCallable, type MaybePromise, type MaybePromiseCallable, OUTPUT_NAME, Options, Page, PageContext, type PageMetaDatum, PagePath, RESOLVED_MODULE_ID_VIRTUAL, ResolvedOptions, SubPackageConfig, type SubPageMetaDatum, UserOptions, type UserPageMeta, type UserTabBarItem, VitePluginUniPages, VitePluginUniPages as default, babelGenerate, checkPagesJsonFileSync, debug, debugType, type definePage, defineUniPages, extsToGlob, findImports, findMacro, getPageFiles, invalidatePagesModule, isTargetFile, mergePageMetaDataArray, parseCode, parseSFC, resolveOptions, resolvePageDirs, sleep, stripType, stripTypeInPlace, tryPageMetaFromMacro, withFileLock, writeFileWithLock };
package/dist/index.mjs CHANGED
@@ -12,10 +12,10 @@ import dbg from "debug";
12
12
  import detectIndent from "detect-indent";
13
13
  import detectNewline from "detect-newline";
14
14
  import { loadConfig } from "unconfig";
15
+ import writeFileAtomic from "write-file-atomic";
15
16
  import { mkdir, readFile, writeFile } from "node:fs/promises";
16
17
  import lockfile from "proper-lockfile";
17
18
  import { globSync } from "tinyglobby";
18
- import writeFileAtomic from "write-file-atomic";
19
19
  import vm from "node:vm";
20
20
  import babelGenerator from "@babel/generator";
21
21
  import groupBy from "lodash.groupby";
@@ -275,6 +275,23 @@ function stripType(item) {
275
275
  const { type, ...rest } = item;
276
276
  return rest;
277
277
  }
278
+ /**
279
+ * Remove the internal `type` marker (`'home'` / `'page'`) from every element of
280
+ * a `CommentArray` *in place* and return the same array.
281
+ *
282
+ * Unlike `items.map(stripType)`, this preserves the `Symbol.for('before:N')` /
283
+ * `Symbol.for('after:N')` comment slots that `comment-json` attaches to the
284
+ * array, so multi-platform `#ifdef` / `#endif` conditional-compilation blocks
285
+ * survive serialization into pages.json. `.map()` rebuilds the array and drops
286
+ * those symbol-keyed comments.
287
+ *
288
+ * @param items - CommentArray (or plain array) of page/tabBar metadata
289
+ * @returns The same array reference, with `type` removed from each element
290
+ */
291
+ function stripTypeInPlace(items) {
292
+ for (const item of items) if (item && "type" in item) delete item.type;
293
+ return items;
294
+ }
278
295
  //#endregion
279
296
  //#region src/files.ts
280
297
  /**
@@ -342,17 +359,22 @@ function checkPagesJsonFileSync(path) {
342
359
  }
343
360
  }
344
361
  /**
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
362
+ * Run a task while holding an exclusive file lock.
348
363
  *
349
- * @param path - File path
350
- * @param content - File content
364
+ * Unlike {@link writeFileWithLock}, this protects the whole read-modify-write
365
+ * critical section. The lock is held from the moment `task` starts until it
366
+ * resolves, so concurrent processes cannot observe or overwrite a half-written
367
+ * state. This is required by pages.json generation, where the new content
368
+ * depends on the current content (other platforms' `#ifdef` blocks).
369
+ *
370
+ * @param path - File path used as the lock target
371
+ * @param task - Async work to run inside the lock; return value is forwarded
351
372
  * @param retry - Number of retries when lock acquisition fails, defaults to 3
373
+ * @returns The value resolved by `task`, or `undefined` if the lock could not be acquired
352
374
  */
353
- async function writeFileWithLock(path, content, retry = 3) {
375
+ async function withFileLock(path, task, retry = 3) {
354
376
  if (retry <= 0) {
355
- debug.error(`${path} Failed to acquire file lock, write failed`);
377
+ debug.error(`${path} Failed to acquire file lock, task aborted`);
356
378
  return;
357
379
  }
358
380
  let release;
@@ -361,13 +383,31 @@ async function writeFileWithLock(path, content, retry = 3) {
361
383
  release = await lockfile.lock(path, { realpath: false });
362
384
  } catch {
363
385
  await sleep(500);
364
- return writeFileWithLock(path, content, retry - 1);
386
+ return withFileLock(path, task, retry - 1);
365
387
  }
366
- await writeFileAtomic(path, content);
388
+ return await task();
367
389
  } finally {
368
390
  if (release) await release();
369
391
  }
370
392
  }
393
+ /**
394
+ * Safely write file using file lock
395
+ * Avoid data corruption caused by concurrent writes through file lock
396
+ * Use atomic write to ensure file write integrity
397
+ *
398
+ * Note: this only makes the *write* atomic. Callers that need to read the
399
+ * current content before computing the new one must use {@link withFileLock}
400
+ * so the read and write belong to the same critical section.
401
+ *
402
+ * @param path - File path
403
+ * @param content - File content
404
+ * @param retry - Number of retries when lock acquisition fails, defaults to 3
405
+ */
406
+ async function writeFileWithLock(path, content, retry = 3) {
407
+ return withFileLock(path, async () => {
408
+ await writeFileAtomic(path, content);
409
+ }, retry);
410
+ }
371
411
  //#endregion
372
412
  //#region src/options.ts
373
413
  /**
@@ -935,16 +975,19 @@ var PageContext = class {
935
975
  }) : this.pageMetaData).forEach((v) => pagesMap.set(v.path, v));
936
976
  this.pageMetaData = [...pagesMap.values()];
937
977
  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() : "");
978
+ const updated = await withFileLock(this.resolvedPagesJSONPath, async () => {
979
+ const pagesJson = stringify(await this.genratePagesJSON(), null, this.options.minify ? void 0 : await this.getIndent()) + (await this.getEndOfLine() ? await this.getNewline() : "");
980
+ if (this.lastPagesJson === pagesJson) {
981
+ debug.pages("PagesJson Not have change");
982
+ return false;
983
+ }
984
+ await writeFileAtomic(this.resolvedPagesJSONPath, pagesJson);
985
+ this.lastPagesJson = pagesJson;
986
+ return true;
987
+ });
939
988
  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;
989
+ if (updated) this.options.onAfterWriteFile(this);
990
+ return updated ?? false;
948
991
  }
949
992
  /**
950
993
  * Generate virtual module content
@@ -1009,7 +1052,7 @@ var PageContext = class {
1009
1052
  const { pages: oldPages, subPackages: oldSubPackages, tabBar: oldTabBar } = parse(await fs.promises.readFile(this.resolvedPagesJSONPath, { encoding: "utf-8" }).catch(() => "") || "{}");
1010
1053
  const { pages: _pages, subPackages: _subPackages, tabBar: _tabBar, ...pageJson } = this.pagesGlobConfig || {};
1011
1054
  const currentPlatform = platform.toUpperCase();
1012
- pageJson.pages = mergePlatformItems(oldPages, currentPlatform, this.pageMetaData, "path").map(stripType);
1055
+ pageJson.pages = stripTypeInPlace(mergePlatformItems(oldPages, currentPlatform, this.pageMetaData, "path"));
1013
1056
  if (pageJson.pages && pageJson.pages.length > 0) {
1014
1057
  const pagesArray = pageJson.pages;
1015
1058
  const homeIndex = pagesArray.findIndex((page) => page.type === "home");
@@ -1024,7 +1067,7 @@ var PageContext = class {
1024
1067
  for (const existing of pageJson.subPackages) {
1025
1068
  const sub = newSubPackages.get(existing.root);
1026
1069
  if (sub) {
1027
- existing.pages = mergePlatformItems(existing.pages, currentPlatform, sub.pages, "path").map(stripType);
1070
+ existing.pages = stripTypeInPlace(mergePlatformItems(existing.pages, currentPlatform, sub.pages, "path"));
1028
1071
  if (sub.plugins) existing.plugins = sub.plugins;
1029
1072
  newSubPackages.delete(existing.root);
1030
1073
  }
@@ -1032,7 +1075,7 @@ var PageContext = class {
1032
1075
  for (const [_, newSub] of newSubPackages) {
1033
1076
  const subPackage = {
1034
1077
  root: newSub.root,
1035
- pages: mergePlatformItems(void 0, currentPlatform, newSub.pages, "path").map(stripType)
1078
+ pages: stripTypeInPlace(mergePlatformItems(void 0, currentPlatform, newSub.pages, "path"))
1036
1079
  };
1037
1080
  if (newSub.plugins) subPackage.plugins = newSub.plugins;
1038
1081
  pageJson.subPackages.push(subPackage);
@@ -1303,4 +1346,4 @@ function VitePluginUniPages(userOptions = {}) {
1303
1346
  };
1304
1347
  }
1305
1348
  //#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 };
1349
+ 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, stripTypeInPlace, tryPageMetaFromMacro, withFileLock, writeFileWithLock };
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /// <reference path="./client.d.ts" />
2
2
 
3
- export type * from './dist/index.d.mts'
4
- export type { default } from './dist/index.d.mts'
3
+ export * from './dist/index.d.mts'
4
+ export { default } from './dist/index.d.mts'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uni-helper/vite-plugin-uni-pages",
3
3
  "type": "module",
4
- "version": "0.4.2",
4
+ "version": "0.4.4",
5
5
  "description": "File system based routing for uni-app applications using Vite",
6
6
  "author": "KeJun",
7
7
  "maintainers": [
@@ -27,11 +27,11 @@
27
27
  "exports": {
28
28
  ".": {
29
29
  "import": {
30
- "types": "./dist/index.d.mts",
30
+ "types": "./index.d.ts",
31
31
  "default": "./dist/index.mjs"
32
32
  },
33
33
  "require": {
34
- "types": "./dist/index.d.cts",
34
+ "types": "./index.d.ts",
35
35
  "default": "./dist/index.cjs"
36
36
  }
37
37
  },
@@ -72,7 +72,7 @@
72
72
  "unconfig": "^7.5.0",
73
73
  "write-file-atomic": "^7.0.1",
74
74
  "yaml": "^2.9.0",
75
- "@uni-helper/uni-pages-types": "0.4.2"
75
+ "@uni-helper/uni-pages-types": "0.4.4"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@antfu/utils": "^9.3.0",