Skip to content

Commit c1d0a64

Browse files
committed
feat: support pages.json condition compilation
1 parent b4dfdcc commit c1d0a64

File tree

5 files changed

+344
-51
lines changed

5 files changed

+344
-51
lines changed

.vscode/launch.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@
2727
"runtimeExecutable": "pnpm",
2828
"runtimeArgs": ["run", "dev:mp-weixin"],
2929
"console": "integratedTerminal",
30-
"internalConsoleOptions": "neverOpen"
30+
"internalConsoleOptions": "neverOpen",
31+
"env": {
32+
"NODE_ENV": "development"
33+
}
3134
}
3235
]
3336
}

packages/core/src/context.ts

Lines changed: 244 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
import type { FSWatcher } from 'chokidar'
2+
import type { CommentObject, CommentSymbol } from 'comment-json'
23
import type { Logger, ViteDevServer } from 'vite'
34
import type { TabBar, TabBarItem } from './config'
45
import type { PagesConfig } from './config/types'
56
import type { PageMetaDatum, PagePath, ResolvedOptions, SubPageMetaDatum, UserOptions } from './types'
7+
68
import fs from 'node:fs'
79
import path from 'node:path'
810
import process from 'node:process'
911
import { slash } from '@antfu/utils'
1012
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'
1214
import dbg from 'debug'
1315
import detectIndent from 'detect-indent'
16+
1417
import detectNewline from 'detect-newline'
1518
import lockfile from 'proper-lockfile'
16-
1719
import { loadConfig } from 'unconfig'
1820
import { OUTPUT_NAME } from './constant'
1921
import { writeDeclaration } from './declaration'
20-
import { checkPagesJsonFile, getPageFiles } from './files'
22+
import { checkPagesJsonFileSync, getPageFiles, writeFileWithLock } from './files'
2123
import { resolveOptions } from './options'
2224
import { Page } from './page'
2325
import {
@@ -27,8 +29,6 @@ import {
2729
mergePageMetaDataArray,
2830
} from './utils'
2931

30-
let lsatPagesJson = ''
31-
3232
export class PageContext {
3333
private _server: ViteDevServer | undefined
3434

@@ -52,6 +52,8 @@ export class PageContext {
5252

5353
withUniPlatform = false
5454

55+
private lastPagesJson = ''
56+
5557
constructor(userOptions: UserOptions, viteRoot: string = process.cwd()) {
5658
this.rawOptions = userOptions
5759
this.root = slash(viteRoot)
@@ -335,7 +337,7 @@ export class PageContext {
335337
}
336338
}
337339

338-
await checkPagesJsonFile(this.resolvedPagesJSONPath)
340+
checkPagesJsonFileSync(this.resolvedPagesJSONPath)
339341
this.options.onBeforeLoadUserConfig(this)
340342
await this.loadUserPagesConfig()
341343
this.options.onAfterLoadUserConfig(this)
@@ -364,12 +366,7 @@ export class PageContext {
364366

365367
this.options.onBeforeWriteFile(this)
366368

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()
373370

374371
const pagesJson = cjStringify(
375372
data,
@@ -379,22 +376,14 @@ export class PageContext {
379376
await this.getEndOfLine() ? await this.getNewline() : ''
380377
)
381378
this.generateDeclaration()
382-
if (lsatPagesJson === pagesJson) {
379+
if (this.lastPagesJson === pagesJson) {
383380
debug.pages('PagesJson Not have change')
384381
return false
385382
}
386383

387-
// 获取文件锁,如果文件不存在则创建
388-
const relase = await lockfile.lock(this.resolvedPagesJSONPath, { realpath: false })
384+
await writeFileWithLock(this.resolvedPagesJSONPath, pagesJson)
389385

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
398387

399388
this.options.onAfterWriteFile(this)
400389
return true
@@ -422,6 +411,87 @@ export class PageContext {
422411
return writeDeclaration(this, this.options.dts)
423412
}
424413

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+
425495
private async readInfoFromPagesJSON(): Promise<void> {
426496
const resolvedPagesJSONContent = await fs.promises.readFile(this.resolvedPagesJSONPath, { encoding: 'utf-8' }).catch(() => '')
427497
this.resolvedPagesJSONIndent = detectIndent(resolvedPagesJSONContent).indent || ' '
@@ -468,3 +538,154 @@ function getPagePaths(dir: string, options: ResolvedOptions) {
468538

469539
return pagePaths
470540
}
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(/#ifdef\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

Comments
 (0)