@uni-helper/vite-plugin-uni-pages 0.4.3 → 0.4.6
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 +13 -0
- package/dist/index.cjs +68 -23
- package/dist/index.d.cts +36 -3
- package/dist/index.d.mts +45 -12
- package/dist/index.mjs +76 -33
- package/package.json +3 -3
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/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
|
-
*
|
|
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
|
-
* @
|
|
391
|
-
*
|
|
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
|
|
416
|
+
async function withFileLock(path, task, retry = 3) {
|
|
395
417
|
if (retry <= 0) {
|
|
396
|
-
debug$1.error(`${path} Failed to acquire file lock,
|
|
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
|
|
427
|
+
return withFileLock(path, task, retry - 1);
|
|
406
428
|
}
|
|
407
|
-
await (
|
|
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
|
|
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.
|
|
982
|
-
|
|
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")
|
|
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")
|
|
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")
|
|
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
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { FSWatcher } from "chokidar";
|
|
3
3
|
import { Logger, Plugin, ViteDevServer } from "vite";
|
|
4
4
|
import fs from "node:fs";
|
|
5
|
-
import
|
|
5
|
+
import Debug from "debug";
|
|
6
6
|
import { LoadConfigSource } from "unconfig";
|
|
7
7
|
import babelGenerator from "@babel/generator";
|
|
8
8
|
import * as t from "@babel/types";
|
|
@@ -286,14 +286,14 @@ declare function invalidatePagesModule(server: ViteDevServer): void;
|
|
|
286
286
|
* Enable via environment variable DEBUG=vite-plugin-uni-pages:*
|
|
287
287
|
*/
|
|
288
288
|
declare const debug: {
|
|
289
|
-
/** HMR related logs */hmr:
|
|
290
|
-
options:
|
|
291
|
-
pages:
|
|
292
|
-
subPages:
|
|
293
|
-
error:
|
|
294
|
-
cache:
|
|
295
|
-
declaration:
|
|
296
|
-
definePage:
|
|
289
|
+
/** HMR related logs */hmr: Debug.Debugger; /** Configuration options related logs */
|
|
290
|
+
options: Debug.Debugger; /** Main package page scanning logs */
|
|
291
|
+
pages: Debug.Debugger; /** Sub-package page scanning logs */
|
|
292
|
+
subPages: Debug.Debugger; /** Error logs */
|
|
293
|
+
error: Debug.Debugger; /** Cache related logs */
|
|
294
|
+
cache: Debug.Debugger; /** Declaration file generation logs */
|
|
295
|
+
declaration: Debug.Debugger; /** definePage macro parsing logs */
|
|
296
|
+
definePage: Debug.Debugger;
|
|
297
297
|
};
|
|
298
298
|
/**
|
|
299
299
|
* Convert file extensions array to glob pattern
|
|
@@ -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
|
@@ -8,14 +8,14 @@ import { createLogger, normalizePath } from "vite";
|
|
|
8
8
|
import fs, { existsSync } from "node:fs";
|
|
9
9
|
import { platform } from "@uni-helper/uni-env";
|
|
10
10
|
import { CommentArray, parse, stringify } from "comment-json";
|
|
11
|
-
import
|
|
11
|
+
import Debug 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";
|
|
@@ -135,21 +135,21 @@ function invalidatePagesModule(server) {
|
|
|
135
135
|
*/
|
|
136
136
|
const debug = {
|
|
137
137
|
/** HMR related logs */
|
|
138
|
-
hmr:
|
|
138
|
+
hmr: Debug("vite-plugin-uni-pages:hmr"),
|
|
139
139
|
/** Configuration options related logs */
|
|
140
|
-
options:
|
|
140
|
+
options: Debug("vite-plugin-uni-pages:options"),
|
|
141
141
|
/** Main package page scanning logs */
|
|
142
|
-
pages:
|
|
142
|
+
pages: Debug("vite-plugin-uni-pages:pages"),
|
|
143
143
|
/** Sub-package page scanning logs */
|
|
144
|
-
subPages:
|
|
144
|
+
subPages: Debug("vite-plugin-uni-pages:subPages"),
|
|
145
145
|
/** Error logs */
|
|
146
|
-
error:
|
|
146
|
+
error: Debug("vite-plugin-uni-pages:error"),
|
|
147
147
|
/** Cache related logs */
|
|
148
|
-
cache:
|
|
148
|
+
cache: Debug("vite-plugin-uni-pages:cache"),
|
|
149
149
|
/** Declaration file generation logs */
|
|
150
|
-
declaration:
|
|
150
|
+
declaration: Debug("vite-plugin-uni-pages:declaration"),
|
|
151
151
|
/** definePage macro parsing logs */
|
|
152
|
-
definePage:
|
|
152
|
+
definePage: Debug("vite-plugin-uni-pages:definePage")
|
|
153
153
|
};
|
|
154
154
|
/**
|
|
155
155
|
* Convert file extensions array to glob pattern
|
|
@@ -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
|
-
*
|
|
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
|
-
* @
|
|
350
|
-
*
|
|
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
|
|
375
|
+
async function withFileLock(path, task, retry = 3) {
|
|
354
376
|
if (retry <= 0) {
|
|
355
|
-
debug.error(`${path} Failed to acquire file lock,
|
|
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
|
|
386
|
+
return withFileLock(path, task, retry - 1);
|
|
365
387
|
}
|
|
366
|
-
await
|
|
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
|
/**
|
|
@@ -672,7 +712,7 @@ var PageContext = class {
|
|
|
672
712
|
if (debugOption) {
|
|
673
713
|
const prefix = "vite-plugin-uni-pages:";
|
|
674
714
|
const suffix = typeof debugOption === "boolean" ? "*" : debugOption;
|
|
675
|
-
|
|
715
|
+
Debug.enable(`${prefix}${suffix}`);
|
|
676
716
|
}
|
|
677
717
|
this.resolvedPagesJSONPath = path.join(this.root, this.options.outDir, OUTPUT_NAME);
|
|
678
718
|
debug.options(this.options);
|
|
@@ -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
|
|
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.
|
|
941
|
-
|
|
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")
|
|
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")
|
|
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")
|
|
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/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.
|
|
4
|
+
"version": "0.4.6",
|
|
5
5
|
"description": "File system based routing for uni-app applications using Vite",
|
|
6
6
|
"author": "KeJun",
|
|
7
7
|
"maintainers": [
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@babel/generator": "^7.29.7",
|
|
56
56
|
"@babel/types": "^7.29.7",
|
|
57
57
|
"@types/proper-lockfile": "^4.1.4",
|
|
58
|
-
"@uni-helper/uni-env": "^0.
|
|
58
|
+
"@uni-helper/uni-env": "^0.3.1",
|
|
59
59
|
"@vue/compiler-sfc": "~3.4.38",
|
|
60
60
|
"ast-kit": "^1.4.3",
|
|
61
61
|
"chokidar": "^3.6.0",
|
|
@@ -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.
|
|
75
|
+
"@uni-helper/uni-pages-types": "0.4.6"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@antfu/utils": "^9.3.0",
|