Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,14 @@ export interface Options {
*/
routeBlockLang: 'json5' | 'jsonc' | 'json' | 'yaml' | 'yml'

onBeforeLoadUserConfig: (ctx: PageContext) => void
onAfterLoadUserConfig: (ctx: PageContext) => void
onBeforeScanPages: (ctx: PageContext) => void
onAfterScanPages: (ctx: PageContext) => void
onBeforeMergePageMetaData: (ctx: PageContext) => void
onAfterMergePageMetaData: (ctx: PageContext) => void
onBeforeWriteFile: (ctx: PageContext) => void
onAfterWriteFile: (ctx: PageContext) => void
onBeforeLoadUserConfig: (ctx: Context) => void
onAfterLoadUserConfig: (ctx: Context) => void
onBeforeScanPages: (ctx: Context) => void
onAfterScanPages: (ctx: Context) => void
onBeforeMergePageMetaData: (ctx: Context) => void
onAfterMergePageMetaData: (ctx: Context) => void
onBeforeWriteFile: (ctx: Context) => void
onAfterWriteFile: (ctx: Context) => void
}
```

Expand Down
5 changes: 2 additions & 3 deletions packages/core/client.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
declare module 'virtual:uni-pages' {
import type { SubPackage } from './src/config/types/index'
import type { PageMetaDatum } from './src/types'
import type { Page, SubPackage } from '.'

export const pages: PageMetaDatum[]
export const pages: Page[]
export const subPackages: SubPackage[]
}

Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { UserPagesJson } from './types'

export function defineUniPages(config: UserPagesJson) {
return config
}
7 changes: 0 additions & 7 deletions packages/core/src/config/index.ts

This file was deleted.

159 changes: 69 additions & 90 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { FSWatcher } from 'chokidar'
import type { CommentObject, CommentSymbol } from 'comment-json'
import type { Logger, ViteDevServer } from 'vite'
import type { TabBar, TabBarItem } from './config'
import type { PagesConfig } from './config/types'
import type { ExcludeIndexSignature, PageMetaDatum, PagePath, ResolvedOptions, SubPageMetaDatum, UserOptions } from './types'
import type { KnownKeys, Page, PagesJson, PathSet, ResolvedOptions, SubPackage, TabBar, TabBarItem, UserOptions } from './types'

import fs from 'node:fs'
import path from 'node:path'
Expand All @@ -18,26 +16,26 @@ import detectNewline from 'detect-newline'
import { loadConfig } from 'unconfig'
import { OUTPUT_NAME } from './constant'
import { writeDeclaration } from './declaration'
import { checkPagesJsonFileSync, getPageFiles, writeFileWithLock } from './files'
import { checkPagesJsonFileSync, getPathSets, writeFileWithLock } from './files'
import { resolveOptions } from './options'
import { Page } from './page'
import { PAGE_TYPE_KEY, PageFile } from './pageFile'
import {
debug,
invalidatePagesModule,
isTargetFile,
mergePageMetaDataArray,
} from './utils'

export class PageContext {
export class Context {
private _server: ViteDevServer | undefined

pagesGlobConfig: PagesConfig | undefined
pagesGlobConfig: PagesJson | undefined
pagesConfigSourcePaths: string[] = []

pages = new Map<string, Page>() // abs path -> Page
subPages = new Map<string, Map<string, Page>>() // root -> abs path -> page
pageMetaData: PageMetaDatum[] = []
subPageMetaData: SubPageMetaDatum[] = []
pageFiles = new Map<string, PageFile>() // abs path -> PageFile
subPageFiles = new Map<string, Map<string, PageFile>>() // root -> abs path -> page
pages: Page[] = []
subPackages: SubPackage[] = []

resolvedPagesJSONPath = ''
private resolvedPagesJSONIndent?: string // ' '
Expand Down Expand Up @@ -75,46 +73,46 @@ export class PageContext {

async loadUserPagesConfig() {
const configSource = this.options.configSource
const { config, sources } = await loadConfig<PagesConfig>({ cwd: this.root, sources: configSource, defaults: {} })
const { config, sources } = await loadConfig<PagesJson>({ cwd: this.root, sources: configSource, defaults: {} })
this.pagesGlobConfig = config.default || config
this.pagesConfigSourcePaths = sources
debug.options(this.pagesGlobConfig)
}

async scanPages() {
const pageDirFiles = this.options.dirs.map((dir) => {
return { dir, files: getPagePaths(dir, this.options) }
return { dir, files: getPathSets(dir, this.options) }
})

const paths = pageDirFiles.map(page => page.files).flat()
debug.pages(paths)

const pages = new Map<string, Page>()
const pageFiles = new Map<string, PageFile>()
for (const path of paths) {
const page = this.pages.get(path.absolutePath) || new Page(this, path)
pages.set(path.absolutePath, page)
const page = this.pageFiles.get(path.abs) || new PageFile(this, path)
pageFiles.set(path.abs, page)
}

this.pages = pages
this.pageFiles = pageFiles
}

async scanSubPages() {
const paths: Record<string, PagePath[]> = {}
const subPages = new Map<string, Map<string, Page>>()
const paths: Record<string, PathSet[]> = {}
const subPageFiles = new Map<string, Map<string, PageFile>>()
for (const dir of this.options.subPackages) {
const pagePaths = getPagePaths(dir, this.options)
const pagePaths = getPathSets(dir, this.options)
paths[dir] = pagePaths

const pages = new Map<string, Page>()
const pageFiles = new Map<string, PageFile>()
for (const path of pagePaths) {
const page = this.subPages.get(dir)?.get(path.absolutePath) || new Page(this, path)
pages.set(path.absolutePath, page)
const page = this.subPageFiles.get(dir)?.get(path.abs) || new PageFile(this, path)
pageFiles.set(path.abs, page)
}
subPages.set(dir, pages)
subPageFiles.set(dir, pageFiles)
}
debug.subPages(JSON.stringify(paths, null, 2))

this.subPages = subPages
this.subPageFiles = subPageFiles
}

setupViteServer(server: ViteDevServer) {
Expand Down Expand Up @@ -198,8 +196,8 @@ export class PageContext {
* @param overrides custom page config
* @returns pages rules
*/
async parsePages(pages: Map<string, Page>, type: 'main' | 'sub', overrides?: PageMetaDatum[]) {
const jobs: Promise<PageMetaDatum>[] = []
async parsePages(pages: Map<string, PageFile>, type: 'main' | 'sub', overrides?: Page[]) {
const jobs: Promise<Page>[] = []
for (const [_, page] of pages) {
jobs.push(page.getPageMeta())
}
Expand All @@ -215,7 +213,7 @@ export class PageContext {
result.reduce((map, page) => {
map.set(page.path, page)
return map
}, new Map<string, PageMetaDatum>()).values(),
}, new Map<string, Page>()).values(),
)

return type === 'main' ? this.setHomePage(parseMeta) : parseMeta
Expand All @@ -226,13 +224,13 @@ export class PageContext {
* @param result pages rules array
* @returns pages rules
*/
setHomePage(result: PageMetaDatum[]): PageMetaDatum[] {
const hasHome = result.some(({ type }) => type === 'home')
setHomePage(result: Page[]): Page[] {
const hasHome = result.some(p => (p as any)[PAGE_TYPE_KEY] === 'home')
if (!hasHome) {
const isFoundHome = result.some((item) => {
const isFound = this.options.homePage.find(v => (v === item.path))
if (isFound)
item.type = 'home'
(item as any)[PAGE_TYPE_KEY] = 'home'

return isFound
})
Expand All @@ -244,49 +242,45 @@ export class PageContext {
}
}

result.sort(page => (page.type === 'home' ? -1 : 0))
result.sort(page => ((page as any)[PAGE_TYPE_KEY] === 'home' ? -1 : 0))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Broken Array.sort comparator; home page ordering may be incorrect.

Comparator takes only one arg; must compare a and b.

-    result.sort(page => ((page as any)[PAGE_TYPE_KEY] === 'home' ? -1 : 0))
+    result.sort((a, b) =>
+      ((a as any)[PAGE_TYPE_KEY] === 'home' ? -1 : ((b as any)[PAGE_TYPE_KEY] === 'home' ? 1 : 0)),
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result.sort(page => ((page as any)[PAGE_TYPE_KEY] === 'home' ? -1 : 0))
result.sort((a, b) =>
((a as any)[PAGE_TYPE_KEY] === 'home'
? -1
: ((b as any)[PAGE_TYPE_KEY] === 'home' ? 1 : 0))),
)
🤖 Prompt for AI Agents
In packages/core/src/context.ts around line 245, the Array.sort comparator is
incorrect because it only takes one argument; replace it with a proper
two-argument comparator like result.sort((a, b) => { const aIsHome = (a as
any)[PAGE_TYPE_KEY] === 'home'; const bIsHome = (b as any)[PAGE_TYPE_KEY] ===
'home'; if (aIsHome && !bIsHome) return -1; if (!aIsHome && bIsHome) return 1;
return 0; }) so items with page type 'home' are ordered before others while
preserving relative order for ties.


return result
}

async mergePageMetaData() {
const pageMetaData = await this.parsePages(this.pages, 'main', this.pagesGlobConfig?.pages)
const pages = await this.parsePages(this.pageFiles, 'main', this.pagesGlobConfig?.pages)

this.pageMetaData = pageMetaData
debug.pages(this.pageMetaData)
this.pages = pages
debug.pages(this.pages)
}

async mergeSubPageMetaData() {
const subPageMaps: Record<string, PageMetaDatum[]> = {}
const subPackages = this.pagesGlobConfig?.subPackages || []
const subPageMaps = (this.pagesGlobConfig?.subPackages || []).reduce(
(map, item) => {
map[item.root] = item
return map
},
{} as Record<string, SubPackage>,
)

for (const [dir, pages] of this.subPages) {
for (const [dir, pfiles] of this.subPageFiles) {
const basePath = slash(path.join(this.options.root, this.options.outDir))
const root = slash(path.relative(basePath, path.join(this.options.root, dir)))

const globPackage = subPackages?.find(v => v.root === root)
subPageMaps[root] = await this.parsePages(pages, 'sub', globPackage?.pages)
subPageMaps[root] = subPageMaps[root].map(page => ({ ...page, path: slash(path.relative(root, page.path)) }))
const pkg = subPageMaps[root] || { root }
const pages = await this.parsePages(pfiles, 'sub', pkg.pages)
pkg.pages = pages.map(page => ({ ...page, path: slash(path.relative(root, page.path)) }))
subPageMaps[root] = pkg
}

// Inherit subPackages that do not exist in the config
for (const { root, pages } of subPackages) {
if (root && !subPageMaps[root])
subPageMaps[root] = pages || []
}

const subPageMetaData = Object.keys(subPageMaps)
.map(root => ({ root, pages: subPageMaps[root] }))
.filter(meta => meta.pages.length > 0)

this.subPageMetaData = subPageMetaData
debug.subPages(this.subPageMetaData)
this.subPackages = Object.values(subPageMaps).filter(pkg => pkg.pages.length > 0)
debug.subPages(this.subPackages)
}

private async getTabBarMerged(): Promise<TabBar | undefined> {
const tabBarItems: (TabBarItem & { index: number })[] = []
for (const [_, page] of this.pages) {
const tabbar = await page.getTabBar()
for (const [_, pf] of this.pageFiles) {
const tabbar = await pf.getTabBar()
if (tabbar) {
tabBarItems.push(tabbar)
}
Expand Down Expand Up @@ -320,20 +314,20 @@ export class PageContext {

async updatePagesJSON(filepath?: string) {
if (filepath) {
let page = this.pages.get(filepath)
if (!page) {
let subPage: Page | undefined
for (const [_, pages] of this.subPages) {
subPage = pages.get(filepath)
if (subPage) {
let pageFile = this.pageFiles.get(filepath)
if (!pageFile) {
let subPageFile: PageFile | undefined
for (const [_, pageFiles] of this.subPageFiles) {
subPageFile = pageFiles.get(filepath)
if (subPageFile) {
break
}
}
page = subPage
pageFile = subPageFile
}
if (page) {
await page.read()
if (!page.hasChanged()) {
if (pageFile) {
await pageFile.read()
if (!pageFile.hasChanged()) {
debug.cache(`The route block on page ${filepath} did not send any changes, skipping`)
return false
}
Expand All @@ -359,13 +353,13 @@ export class PageContext {

const pagesMap = new Map()
const pages = this.withUniPlatform
? this.pageMetaData.filter(v => !/\..*$/.test(v.path) || v.path.includes(platform)).map((v) => {
? this.pages.filter(v => !/\..*$/.test(v.path) || v.path.includes(platform)).map((v) => {
v.path = v.path.replace(/\..*$/, '')
return v
})
: this.pageMetaData
: this.pages
pages.forEach(v => pagesMap.set(v.path, v))
this.pageMetaData = [...pagesMap.values()]
this.pages = [...pagesMap.values()]

this.options.onBeforeWriteFile(this)

Expand Down Expand Up @@ -399,11 +393,11 @@ export class PageContext {
}

resolveRoutes() {
return cjStringify(this.pageMetaData, null, 2)
return cjStringify(this.pages, null, 2)
}

resolveSubRoutes() {
return cjStringify(this.subPageMetaData, null, 2)
return cjStringify(this.subPackages, null, 2)
}

generateDeclaration() {
Expand All @@ -424,15 +418,15 @@ export class PageContext {
const currentPlatform = platform.toUpperCase()

// pages
pageJson.pages = mergePlatformItems(oldPages as any, currentPlatform, this.pageMetaData, 'path')
pageJson.pages = mergePlatformItems(oldPages as any, currentPlatform, this.pages, 'path')

// subPackages
pageJson.subPackages = oldSubPackages || new CommentArray<CommentObject>()
const newSubPackages = new Map<string, SubPageMetaDatum>()
for (const item of this.subPageMetaData) {
const newSubPackages = new Map<string, SubPackage>()
for (const item of this.subPackages) {
newSubPackages.set(item.root, item)
}
for (const existing of pageJson.subPackages as unknown as SubPageMetaDatum[]) {
for (const existing of pageJson.subPackages as unknown as SubPackage[]) {
const sub = newSubPackages.get(existing.root)
if (sub) {
existing.pages = mergePlatformItems(existing.pages, currentPlatform, sub.pages, 'path') as any
Expand Down Expand Up @@ -495,22 +489,7 @@ export class PageContext {
}
}

function getPagePaths(dir: string, options: ResolvedOptions) {
const pagesDirPath = slash(path.resolve(options.root, dir))
const basePath = slash(path.join(options.root, options.outDir))
const files = getPageFiles(pagesDirPath, options)
debug.pages(dir, files)
const pagePaths = files
.map(file => slash(file))
.map(file => ({
relativePath: path.relative(basePath, slash(path.resolve(pagesDirPath, file))),
absolutePath: slash(path.resolve(pagesDirPath, file)),
}))

return pagePaths
}

function mergePlatformItems<T = any>(source: any[] | undefined, currentPlatform: string, items: T[], uniqueKeyName: keyof ExcludeIndexSignature<T>): CommentArray<CommentObject> {
function mergePlatformItems<T = any>(source: any[] | undefined, currentPlatform: string, items: T[], uniqueKeyName: KnownKeys<T>): CommentArray<CommentObject> {
const src = (source as CommentArray<CommentObject>) || new CommentArray<CommentObject>()
currentPlatform = currentPlatform.toUpperCase()

Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/declaration.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { PageContext } from './context'
import type { Context } from './context'
import { existsSync } from 'node:fs'
import { mkdir, readFile, writeFile as writeFile_ } from 'node:fs/promises'

import { dirname, join } from 'node:path'
import { normalizePath } from 'vite'

export function getDeclaration(ctx: PageContext) {
const subPagesPath = ctx.subPageMetaData.map((sub) => {
export function getDeclaration(ctx: Context) {
const subPagesPath = ctx.subPackages.map((sub) => {
return sub.pages.map(v => (`"/${normalizePath(join(sub.root, v.path))}"`))
}).flat()
const tabsPagesPath = ctx.pagesGlobConfig?.tabBar?.list?.map((v) => {
return `"/${v!.pagePath}"`
}) ?? []
const allPagesPath = [...ctx.pageMetaData.filter(page => !tabsPagesPath.includes(page.path)).map(v => `"/${v.path}"`), ...subPagesPath]
const allPagesPath = [...ctx.pages.filter(page => !tabsPagesPath.includes(page.path)).map(v => `"/${v.path}"`), ...subPagesPath]
const code = `/* eslint-disable */
Comment on lines +8 to 16
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

TabBar pages exclusion check is broken (format mismatch).

tabsPagesPath items are like "/pages/index" but you compare against page.path (no leading slash), so tab pages aren’t excluded.

-  const allPagesPath = [...ctx.pages.filter(page => !tabsPagesPath.includes(page.path)).map(v => `"/${v.path}"`), ...subPagesPath]
+  const allPagesPath = [
+    ...ctx.pages
+      .filter(page => !tabsPagesPath.includes(`"/${normalizePath(page.path)}"`))
+      .map(v => `"/${normalizePath(v.path)}"`),
+    ...subPagesPath,
+  ]
🤖 Prompt for AI Agents
In packages/core/src/declaration.ts around lines 8 to 16, the exclusion check
fails because tabsPagesPath entries are created as quoted, leading-slash strings
(e.g. '"/pages/index"') while page.path has no leading slash (e.g.
'pages/index'); update the construction so both formats match: remove the
surrounding quotes and the leading slash when building tabsPagesPath (or
alternately add a leading slash to page.path before comparing) so the contains
check works; ensure tabsPagesPath and the values used in the includes(...) call
use the same canonical form (no extra quotes, consistent leading slash
presence).

/* prettier-ignore */
// @ts-nocheck
Expand Down Expand Up @@ -51,7 +51,7 @@ async function writeFile(filePath: string, content: string) {
return await writeFile_(filePath, content, 'utf-8')
}

export async function writeDeclaration(ctx: PageContext, filepath: string) {
export async function writeDeclaration(ctx: Context, filepath: string) {
const originalContent = existsSync(filepath) ? await readFile(filepath, 'utf-8') : ''

const code = getDeclaration(ctx)
Expand Down
Loading