Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions packages/core/src/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export const RESOLVED_MODULE_ID_VIRTUAL = `\0${MODULE_ID_VIRTUAL}`
export const OUTPUT_NAME = 'pages.json'

export const FILE_EXTENSIONS = ['vue', 'nvue', 'uvue']
export const EMPTY_PAGES_JSON_CONTENTS = '{ "pages": [{ "path": "" }] }'
29 changes: 26 additions & 3 deletions packages/core/src/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { ResolvedOptions } from './types'
import fs from 'node:fs'
import fg from 'fast-glob'

import { FILE_EXTENSIONS } from './constant'
import { normalizePath } from 'vite'
import { EMPTY_PAGES_JSON_CONTENTS, FILE_EXTENSIONS } from './constant'
import { extsToGlob } from './utils'

/**
Expand All @@ -27,9 +28,9 @@ export function getPageFiles(path: string, options: ResolvedOptions): string[] {
* @param path - 要检查的文件路径
* @returns Promise<void> - 无返回值的异步函数
*/
export async function checkPagesJsonFile(path: fs.PathLike): Promise<boolean> {
export async function checkPagesJsonFile(path: fs.PathLike, contents: string = EMPTY_PAGES_JSON_CONTENTS): Promise<boolean> {
const createEmptyFile = (path: fs.PathLike) => {
return fs.promises.writeFile(path, JSON.stringify({ pages: [{ path: '' }] }, null, 2), { encoding: 'utf-8' }).then(() => true).catch(() => false)
return fs.promises.writeFile(path, contents, { encoding: 'utf-8' }).then(() => true).catch(() => false)
}

const unlink = (path: fs.PathLike) => {
Expand Down Expand Up @@ -65,3 +66,25 @@ export async function checkPagesJsonFile(path: fs.PathLike): Promise<boolean> {
return createEmptyFile(path) // 创建空文件
}
}

export function setupPagesJsonFile(path: string) {
const _readFileSync = fs.readFileSync
fs.readFileSync = new Proxy(fs.readFileSync, {
apply(target, thisArg, argArray) {
if (typeof argArray[0] === 'string' && normalizePath(argArray[0]) === normalizePath(path)) {
try {
const data = _readFileSync.apply(thisArg, argArray as any)
fs.readFileSync = _readFileSync
return data
}
catch {
checkPagesJsonFile(path).then(() => {
fs.readFileSync = _readFileSync
})
}
return EMPTY_PAGES_JSON_CONTENTS
}
return Reflect.apply(target, thisArg, argArray)
},
})
}
Comment on lines +70 to +90
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Make the fs.readFileSync proxy robust (relative paths, Buffer vs string, idempotency)

  • Match absolute and relative paths; normalize with process.cwd().
  • Return Buffer when caller expects Buffer.
  • Prevent double‑patching across multiple plugin instances.

Apply this diff:

-export function setupPagesJsonFile(path: string) {
-  const _readFileSync = fs.readFileSync
-  fs.readFileSync = new Proxy(fs.readFileSync, {
-    apply(target, thisArg, argArray) {
-      if (typeof argArray[0] === 'string' && normalizePath(argArray[0]) === normalizePath(path)) {
-        try {
-          const data = _readFileSync.apply(thisArg, argArray as any)
-          fs.readFileSync = _readFileSync
-          return data
-        }
-        catch {
-          checkPagesJsonFile(path).then(() => {
-            fs.readFileSync = _readFileSync
-          })
-        }
-        return EMPTY_PAGES_JSON_CONTENTS
-      }
-      return Reflect.apply(target, thisArg, argArray)
-    },
-  })
-}
+export function setupPagesJsonFile(path: string) {
+  // idempotency guard
+  if ((fs as any).__uniPagesRFSProxyInstalled)
+    return
+  const _readFileSync = fs.readFileSync
+  ;(fs as any).__uniPagesRFSProxyInstalled = true
+  fs.readFileSync = new Proxy(fs.readFileSync, {
+    apply(target, thisArg, argArray) {
+      const req0 = argArray[0]
+      if (typeof req0 === 'string') {
+        // normalize to absolute for comparison
+        const reqAbs = nodePath.isAbsolute(req0) ? req0 : nodePath.resolve(process.cwd(), req0)
+        if (normalizePath(reqAbs) === normalizePath(path)) {
+          try {
+            const data = _readFileSync.apply(thisArg, argArray as any)
+            fs.readFileSync = _readFileSync
+            ;(fs as any).__uniPagesRFSProxyInstalled = false
+            return data
+          }
+          catch {
+            // ensure file asynchronously, then unpatch
+            checkPagesJsonFile(path).then(() => {
+              fs.readFileSync = _readFileSync
+              ;(fs as any).__uniPagesRFSProxyInstalled = false
+            })
+          }
+          // respect caller’s expected return type
+          const opt = argArray[1]
+          const wantsString =
+            typeof opt === 'string' ||
+            (opt && typeof opt === 'object' && typeof opt.encoding === 'string' && opt.encoding !== null)
+          return wantsString ? EMPTY_PAGES_JSON_CONTENTS : Buffer.from(EMPTY_PAGES_JSON_CONTENTS)
+        }
+      }
+      return Reflect.apply(target, thisArg, argArray)
+    },
+  })
+}

And add this import near the top of the file:

import nodePath from 'node:path'

Run this to check for other call sites of checkPagesJsonFile potentially relying on its old return type or contents:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts -C2 '\bcheckPagesJsonFile\s*\('

Length of output: 1075


Make the fs.readFileSync proxy robust (idempotent, resolve relative paths, preserve Buffer/string)

Apply the suggested diff (adds an idempotency guard __uniPagesRFSProxyInstalled, uses node:path.resolve(process.cwd(), ...) for comparison, and returns Buffer when caller expects Buffer). Also add import nodePath from 'node:path'.

Verified: checkPagesJsonFile is declared at packages/core/src/files.ts:31 as Promise and is awaited at packages/core/src/context.ts:338; no other callers found.

🤖 Prompt for AI Agents
In packages/core/src/files.ts around lines 70 to 90, the fs.readFileSync proxy
needs to be made idempotent, resolve relative paths with node:path, and preserve
Buffer/string return types: add `import nodePath from 'node:path'` at top,
introduce a module-scoped guard `__uniPagesRFSProxyInstalled` and return early
if set so the proxy is installed only once, compare targets using
`nodePath.resolve(process.cwd(), path)` (resolve both argArray[0] and the
configured path) to handle relative paths, and when the original readFileSync
returns a Buffer/string forward that exact value to the caller (do not coerce to
string); ensure the fallback EMPTY_PAGES_JSON_CONTENTS is returned as a Buffer
if the caller expected a Buffer. Set the guard after installing the proxy.

7 changes: 5 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Plugin } from 'vite'
import type { UserOptions } from './types'
import path from 'node:path'
import process from 'node:process'
import { isH5 } from '@uni-helper/uni-env'
import { babelParse } from 'ast-kit'
import chokidar from 'chokidar'
import { bold, dim, lightYellow, link } from 'kolorist'
Expand All @@ -15,7 +16,7 @@ import {
RESOLVED_MODULE_ID_VIRTUAL,
} from './constant'
import { PageContext } from './context'
import { checkPagesJsonFile } from './files'
import { setupPagesJsonFile } from './files'
import { findMacro, parseSFC } from './page'

export * from './config'
Expand All @@ -37,7 +38,9 @@ export async function VitePluginUniPages(userOptions: UserOptions = {}): Promise
userOptions.outDir ?? 'src',
OUTPUT_NAME,
)
await checkPagesJsonFile(resolvedPagesJSONPath)
if (isH5) {
setupPagesJsonFile(resolvedPagesJSONPath)
}

return {
name: 'vite-plugin-uni-pages',
Expand Down
Loading