1
1
import type { FSWatcher } from 'chokidar'
2
+ import type { CommentObject , CommentSymbol } from 'comment-json'
2
3
import type { Logger , ViteDevServer } from 'vite'
3
4
import type { TabBar , TabBarItem } from './config'
4
5
import type { PagesConfig } from './config/types'
5
6
import type { PageMetaDatum , PagePath , ResolvedOptions , SubPageMetaDatum , UserOptions } from './types'
7
+
6
8
import fs from 'node:fs'
7
9
import path from 'node:path'
8
10
import process from 'node:process'
9
11
import { slash } from '@antfu/utils'
10
12
import { platform } from '@uni-helper/uni-env'
11
- import { stringify as cjStringify } from 'comment-json'
13
+ import { parse as cjParse , stringify as cjStringify , CommentArray } from 'comment-json'
12
14
import dbg from 'debug'
13
15
import detectIndent from 'detect-indent'
16
+
14
17
import detectNewline from 'detect-newline'
15
18
import lockfile from 'proper-lockfile'
16
-
17
19
import { loadConfig } from 'unconfig'
18
20
import { OUTPUT_NAME } from './constant'
19
21
import { writeDeclaration } from './declaration'
20
- import { checkPagesJsonFile , getPageFiles } from './files'
22
+ import { checkPagesJsonFileSync , getPageFiles , writeFileWithLock } from './files'
21
23
import { resolveOptions } from './options'
22
24
import { Page } from './page'
23
25
import {
@@ -27,8 +29,6 @@ import {
27
29
mergePageMetaDataArray ,
28
30
} from './utils'
29
31
30
- let lsatPagesJson = ''
31
-
32
32
export class PageContext {
33
33
private _server : ViteDevServer | undefined
34
34
@@ -52,6 +52,8 @@ export class PageContext {
52
52
53
53
withUniPlatform = false
54
54
55
+ private lastPagesJson = ''
56
+
55
57
constructor ( userOptions : UserOptions , viteRoot : string = process . cwd ( ) ) {
56
58
this . rawOptions = userOptions
57
59
this . root = slash ( viteRoot )
@@ -335,7 +337,7 @@ export class PageContext {
335
337
}
336
338
}
337
339
338
- await checkPagesJsonFile ( this . resolvedPagesJSONPath )
340
+ checkPagesJsonFileSync ( this . resolvedPagesJSONPath )
339
341
this . options . onBeforeLoadUserConfig ( this )
340
342
await this . loadUserPagesConfig ( )
341
343
this . options . onAfterLoadUserConfig ( this )
@@ -364,12 +366,7 @@ export class PageContext {
364
366
365
367
this . options . onBeforeWriteFile ( this )
366
368
367
- const data = {
368
- ...this . pagesGlobConfig ,
369
- pages : this . pageMetaData ,
370
- subPackages : this . subPageMetaData ,
371
- tabBar : await this . getTabBarMerged ( ) ,
372
- }
369
+ const data = await this . genratePagesJSON ( )
373
370
374
371
const pagesJson = cjStringify (
375
372
data ,
@@ -379,22 +376,14 @@ export class PageContext {
379
376
await this . getEndOfLine ( ) ? await this . getNewline ( ) : ''
380
377
)
381
378
this . generateDeclaration ( )
382
- if ( lsatPagesJson === pagesJson ) {
379
+ if ( this . lastPagesJson === pagesJson ) {
383
380
debug . pages ( 'PagesJson Not have change' )
384
381
return false
385
382
}
386
383
387
- // 获取文件锁,如果文件不存在则创建
388
- const relase = await lockfile . lock ( this . resolvedPagesJSONPath , { realpath : false } )
384
+ await writeFileWithLock ( this . resolvedPagesJSONPath , pagesJson )
389
385
390
- try {
391
- await fs . promises . writeFile ( this . resolvedPagesJSONPath , pagesJson , { encoding : 'utf-8' } ) // 执行写入操作
392
- }
393
- finally {
394
- await relase ( ) // 释放文件锁
395
- }
396
-
397
- lsatPagesJson = pagesJson
386
+ this . lastPagesJson = pagesJson
398
387
399
388
this . options . onAfterWriteFile ( this )
400
389
return true
@@ -422,6 +411,87 @@ export class PageContext {
422
411
return writeDeclaration ( this , this . options . dts )
423
412
}
424
413
414
+ private async writePagesJSONFile ( pagesJson : string , retry = 3 ) : Promise < void > {
415
+ if ( retry <= 0 ) {
416
+ debug . error ( `${ this . resolvedPagesJSONPath } 获取文件锁失败,写入失败` )
417
+ return
418
+ }
419
+
420
+ let relase : ( ) => Promise < void > | undefined
421
+
422
+ try {
423
+ try {
424
+ // 获取文件锁
425
+ relase = await lockfile . lock ( this . resolvedPagesJSONPath , { realpath : false } )
426
+ }
427
+ catch {
428
+ // 获取文件锁失败
429
+ return this . writePagesJSONFile ( pagesJson , retry - 1 )
430
+ }
431
+ await fs . promises . writeFile ( this . resolvedPagesJSONPath , pagesJson , { encoding : 'utf-8' } ) // 执行写入操作
432
+ }
433
+ finally {
434
+ // eslint-disable-next-line ts/ban-ts-comment
435
+ // @ts -expect-error'
436
+ if ( relase ) {
437
+ await relase ( ) // 释放文件锁
438
+ }
439
+ }
440
+ }
441
+
442
+ private async genratePagesJSON ( ) {
443
+ const content = await fs . promises . readFile ( this . resolvedPagesJSONPath , { encoding : 'utf-8' } ) . catch ( ( ) => '' )
444
+
445
+ let pageJson = cjParse ( content || '{}' ) as CommentObject
446
+
447
+ const { pages : _ , subPackages : __ , tabBar : ___ , ...others } = this . pagesGlobConfig || { }
448
+
449
+ pageJson = Object . assign ( pageJson , {
450
+ ...others ,
451
+ } )
452
+
453
+ const currentPlatform = platform . toUpperCase ( )
454
+
455
+ // pages
456
+ pageJson . pages = mergePlatformItems ( pageJson ?. pages as any , currentPlatform , this . pageMetaData , 'path' )
457
+
458
+ // subPackages
459
+ pageJson . subPackages = pageJson ?. subPackages || new CommentArray < CommentObject > ( )
460
+ const newSubPackages = new Map < string , SubPageMetaDatum > ( )
461
+ for ( const item of this . subPageMetaData ) {
462
+ newSubPackages . set ( item . root , item )
463
+ }
464
+ for ( const existing of pageJson . subPackages as unknown as SubPageMetaDatum [ ] ) {
465
+ const sub = newSubPackages . get ( existing . root )
466
+ if ( sub ) {
467
+ existing . pages = mergePlatformItems ( existing . pages as unknown as CommentArray < CommentObject > , currentPlatform , sub . pages , 'path' ) as any
468
+ newSubPackages . delete ( existing . root )
469
+ }
470
+ }
471
+ for ( const [ _ , newSub ] of newSubPackages ) {
472
+ ( pageJson . subPackages as unknown as Array < any > ) . push ( {
473
+ root : newSub . root ,
474
+ pages : mergePlatformItems ( undefined , currentPlatform , newSub . pages , 'path' ) ,
475
+ } )
476
+ }
477
+
478
+ // tabbar
479
+ const tabBar = await this . getTabBarMerged ( )
480
+ if ( tabBar ) {
481
+ const list = mergePlatformItems ( ( pageJson ?. tabBar as any ) ?. list as any , currentPlatform , tabBar . list ! , 'pagePath' )
482
+
483
+ if ( ( pageJson ?. tabBar as any ) ?. list ) {
484
+ ( pageJson ! . tabBar as any ) ! . list = list
485
+ }
486
+ else {
487
+ ( pageJson as any ) . tabBar = ( pageJson as any ) . tabBar || { } ;
488
+ ( pageJson as any ) . tabBar . list = list
489
+ }
490
+ }
491
+
492
+ return pageJson
493
+ }
494
+
425
495
private async readInfoFromPagesJSON ( ) : Promise < void > {
426
496
const resolvedPagesJSONContent = await fs . promises . readFile ( this . resolvedPagesJSONPath , { encoding : 'utf-8' } ) . catch ( ( ) => '' )
427
497
this . resolvedPagesJSONIndent = detectIndent ( resolvedPagesJSONContent ) . indent || ' '
@@ -468,3 +538,154 @@ function getPagePaths(dir: string, options: ResolvedOptions) {
468
538
469
539
return pagePaths
470
540
}
541
+
542
+ function mergePlatformItems < T = any > ( source : CommentArray < CommentObject > | undefined , currentPlatform : string , items : T [ ] , uniqueKeyName : keyof T ) : CommentArray < CommentObject > {
543
+ const src = source || new CommentArray < CommentObject > ( )
544
+ currentPlatform = currentPlatform . toUpperCase ( )
545
+
546
+ // 1. 从 CommentArray 里抽取第一个注释并获取 platforms 作为 lastPlatforms
547
+ let lastPlatforms : string [ ] = [ ]
548
+ for ( const comment of ( src [ Symbol . for ( 'before:0' ) as CommentSymbol ] || [ ] ) ) {
549
+ const trimed = comment . value . trim ( )
550
+ if ( trimed . startsWith ( 'GENERATED BY UNI-PAGES, PLATFORM:' ) ) {
551
+ // 移除当前 platform
552
+ lastPlatforms = trimed . split ( ':' ) [ 1 ] . split ( '||' ) . map ( s => s . trim ( ) ) . filter ( s => s !== currentPlatform ) . sort ( )
553
+ }
554
+ }
555
+
556
+ // 2. 遍历 source,对每个元素进行判断,然后以 uniqueKey 元素的值作为 key 添加到新的 tmpMap 中
557
+ const tmpMap = new Map < string , Array < {
558
+ item : CommentObject
559
+ platforms : string [ ]
560
+ platformStr : string
561
+ } > > ( )
562
+
563
+ for ( let i = 0 ; i < src . length ; i ++ ) {
564
+ const item = src [ i ] as CommentObject
565
+ const uniqueKey = ( item as any ) [ uniqueKeyName ]
566
+
567
+ if ( ! uniqueKey ) {
568
+ continue
569
+ }
570
+
571
+ // 检查是否有条件编译注释
572
+ const beforeComments = src [ Symbol . for ( `before:${ i } ` ) as CommentSymbol ]
573
+ // const afterComments = src[Symbol.for(`after:${i}`) as CommentSymbol]
574
+
575
+ const ifdefComment = beforeComments ?. find ( c => c . value . trim ( ) . startsWith ( '#ifdef' ) )
576
+ // const endifComment = afterComments?.find(c => c.value.trim().startsWith('#endif'))
577
+
578
+ let platforms : string [ ] = [ ...lastPlatforms ]
579
+
580
+ if ( ifdefComment ) {
581
+ const match = ifdefComment . value . match ( / # i f d e f \s + ( .+ ) / )
582
+ if ( match ) {
583
+ // 移除当前 platform
584
+ platforms = match [ 1 ] . split ( '||' ) . map ( p => p . trim ( ) ) . filter ( s => s !== currentPlatform ) . sort ( )
585
+ }
586
+ }
587
+
588
+ // 如果 platforms 除了当前 platform 外为空,则跳过
589
+ if ( platforms . length === 0 ) {
590
+ continue
591
+ }
592
+
593
+ const existing = tmpMap . get ( uniqueKey ) || [ ]
594
+ existing . push ( { item, platforms, platformStr : platforms . join ( ' || ' ) } )
595
+ tmpMap . set ( uniqueKey , existing )
596
+ }
597
+
598
+ // 3. 将 items 合并到 tmpMap 中
599
+ for ( const item of items ) {
600
+ const newItem = item as any
601
+ const uniqueKey = item [ uniqueKeyName ] as string
602
+
603
+ if ( ! uniqueKey ) {
604
+ continue
605
+ }
606
+
607
+ if ( ! tmpMap . has ( uniqueKey ) ) {
608
+ // 如果不存在,则添加到 newMap 中
609
+ tmpMap . set ( uniqueKey , [ {
610
+ item : newItem ,
611
+ platforms : [ currentPlatform ] ,
612
+ platformStr : currentPlatform ,
613
+ } ] )
614
+ continue
615
+ }
616
+
617
+ // 如果存在,判断元素是否相等
618
+ const existing = tmpMap . get ( uniqueKey ) !
619
+
620
+ const newItemStr = JSON . stringify ( newItem )
621
+ const equalObj = existing . find ( val => JSON . stringify ( val . item ) === newItemStr )
622
+ if ( equalObj ) {
623
+ equalObj . platforms . push ( currentPlatform )
624
+ equalObj . platformStr = equalObj . platforms . join ( ' || ' )
625
+ }
626
+ else {
627
+ existing . push ( {
628
+ item : newItem ,
629
+ platforms : [ currentPlatform ] ,
630
+ platformStr : currentPlatform ,
631
+ } )
632
+ }
633
+ }
634
+
635
+ // 4. 遍历 tmpMap,生成 result:CommentArray<CommentObject>
636
+ const result = new CommentArray < CommentObject > ( )
637
+
638
+ // 检查平台的使用频率,将使用频率高的平台作为默认平台
639
+ const platformUsage : Record < string , number > = { }
640
+ tmpMap . forEach ( ( val ) => {
641
+ Object . values ( val ) . forEach ( ( v ) => {
642
+ platformUsage [ v . platformStr ] = ( platformUsage [ v . platformStr ] || 0 ) + 1
643
+ } )
644
+ } )
645
+ const defaultPlatformStr = Object . keys ( platformUsage ) . reduce ( ( a , b ) => platformUsage [ a ] > platformUsage [ b ] ? a : b )
646
+
647
+ // 为 result 添加 Symbol.for(`before:0`) 添加生成标识注释
648
+ result [ Symbol . for ( 'before:0' ) as CommentSymbol ] = [ {
649
+ type : 'LineComment' ,
650
+ value : ` GENERATED BY UNI-PAGES, PLATFORM: ${ defaultPlatformStr } ` ,
651
+ inline : false ,
652
+ loc : {
653
+ start : { line : 0 , column : 0 } ,
654
+ end : { line : 0 , column : 0 } ,
655
+ } ,
656
+ } ]
657
+
658
+ // 按照插入顺序处理元素
659
+ for ( const [ _ , list ] of tmpMap ) {
660
+ for ( const { item, platformStr } of list ) {
661
+ result . push ( item )
662
+
663
+ // 检查 platforms 是否和 defaultPlatformStr 一致。(platforms、defaultPlatforms 已预先排序)
664
+ if ( platformStr !== defaultPlatformStr ) {
665
+ // 存在平台信息且与默认平台不同,添加条件编译注释
666
+ result [ Symbol . for ( `before:${ result . length - 1 } ` ) as CommentSymbol ] = [ {
667
+ type : 'LineComment' ,
668
+ value : ` #ifdef ${ platformStr } ` ,
669
+ inline : false ,
670
+ loc : {
671
+ start : { line : 0 , column : 0 } ,
672
+ end : { line : 0 , column : 0 } ,
673
+ } ,
674
+ } ]
675
+
676
+ result [ Symbol . for ( `after:${ result . length - 1 } ` ) as CommentSymbol ] = [ {
677
+ type : 'LineComment' ,
678
+ value : ' #endif' ,
679
+ inline : false ,
680
+ loc : {
681
+ start : { line : 0 , column : 0 } ,
682
+ end : { line : 0 , column : 0 } ,
683
+ } ,
684
+ } ]
685
+ }
686
+ }
687
+ }
688
+
689
+ // 5. 返回 result:CommentArray<CommentObject>
690
+ return result
691
+ }
0 commit comments