Skip to content

Commit 696ae27

Browse files
committed
perf: 移除子进程重启的代码,将文件操作改为异步,并加锁
1 parent 21181c9 commit 696ae27

File tree

6 files changed

+146
-75
lines changed

6 files changed

+146
-75
lines changed

packages/core/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"dependencies": {
6060
"@babel/generator": "^7.28.3",
6161
"@babel/types": "^7.28.2",
62+
"@types/proper-lockfile": "^4.1.4",
6263
"@uni-helper/uni-env": "^0.1.4",
6364
"@vue/compiler-sfc": "3.4.21",
6465
"ast-kit": "1.4.3",
@@ -72,6 +73,7 @@
7273
"kolorist": "^1.8.0",
7374
"lodash.groupby": "^4.6.0",
7475
"magic-string": "^0.30.11",
76+
"proper-lockfile": "^4.1.2",
7577
"typescript": "5.9.2",
7678
"unconfig": "^7.3.2",
7779
"yaml": "^2.5.0"

packages/core/src/context.ts

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Logger, ViteDevServer } from 'vite'
33
import type { TabBar, TabBarItem } from './config'
44
import type { PagesConfig } from './config/types'
55
import type { PageMetaDatum, PagePath, ResolvedOptions, SubPageMetaDatum, UserOptions } from './types'
6+
import fs from 'node:fs'
67
import path from 'node:path'
78
import process from 'node:process'
89
import { slash } from '@antfu/utils'
@@ -11,11 +12,12 @@ import { stringify as cjStringify } from 'comment-json'
1112
import dbg from 'debug'
1213
import detectIndent from 'detect-indent'
1314
import detectNewline from 'detect-newline'
14-
import { loadConfig } from 'unconfig'
15+
import lockfile from 'proper-lockfile'
1516

17+
import { loadConfig } from 'unconfig'
1618
import { OUTPUT_NAME } from './constant'
1719
import { writeDeclaration } from './declaration'
18-
import { checkPagesJsonFile, getPageFiles, readFileSync, writeFileSync } from './files'
20+
import { checkPagesJsonFile, getPageFiles } from './files'
1921
import { resolveOptions } from './options'
2022
import { Page } from './page'
2123
import {
@@ -39,9 +41,9 @@ export class PageContext {
3941
subPageMetaData: SubPageMetaDatum[] = []
4042

4143
resolvedPagesJSONPath = ''
42-
resolvedPagesJSONIndent = ' '
43-
resolvedPagesJSONNewline = '\n'
44-
resolvedPagesJSONEofNewline = true
44+
private resolvedPagesJSONIndent?: string // ' '
45+
private resolvedPagesJSONNewline?: string // '\n'
46+
private resolvedPagesJSONEofNewline?: boolean // true
4547

4648
rawOptions: UserOptions
4749
root: string
@@ -63,10 +65,6 @@ export class PageContext {
6365
dbg.enable(`${prefix}${suffix}`)
6466
}
6567
this.resolvedPagesJSONPath = path.join(this.root, this.options.outDir, OUTPUT_NAME)
66-
const resolvedPagesJSONContent = readFileSync(this.resolvedPagesJSONPath)
67-
this.resolvedPagesJSONIndent = detectIndent(resolvedPagesJSONContent).indent || ' '
68-
this.resolvedPagesJSONNewline = detectNewline(resolvedPagesJSONContent) || '\n'
69-
this.resolvedPagesJSONEofNewline = (resolvedPagesJSONContent.at(-1) ?? '\n') === this.resolvedPagesJSONNewline
7068
debug.options(this.options)
7169
}
7270

@@ -337,7 +335,7 @@ export class PageContext {
337335
}
338336
}
339337

340-
checkPagesJsonFile(this.resolvedPagesJSONPath)
338+
await checkPagesJsonFile(this.resolvedPagesJSONPath)
341339
this.options.onBeforeLoadUserConfig(this)
342340
await this.loadUserPagesConfig()
343341
this.options.onAfterLoadUserConfig(this)
@@ -376,17 +374,26 @@ export class PageContext {
376374
const pagesJson = cjStringify(
377375
data,
378376
null,
379-
this.options.minify ? undefined : this.resolvedPagesJSONIndent,
377+
this.options.minify ? undefined : await this.getIndent(),
380378
) + (
381-
this.resolvedPagesJSONEofNewline ? this.resolvedPagesJSONNewline : ''
379+
await this.getEndOfLine() ? await this.getNewline() : ''
382380
)
383381
this.generateDeclaration()
384382
if (lsatPagesJson === pagesJson) {
385383
debug.pages('PagesJson Not have change')
386384
return false
387385
}
388386

389-
writeFileSync(this.resolvedPagesJSONPath, pagesJson)
387+
// 获取文件锁,如果文件不存在则创建
388+
const relase = await lockfile.lock(this.resolvedPagesJSONPath, { realpath: false })
389+
390+
try {
391+
await fs.promises.writeFile(this.resolvedPagesJSONPath, pagesJson, { encoding: 'utf-8' }) // 执行写入操作
392+
}
393+
finally {
394+
await relase() // 释放文件锁
395+
}
396+
390397
lsatPagesJson = pagesJson
391398

392399
this.options.onAfterWriteFile(this)
@@ -414,6 +421,37 @@ export class PageContext {
414421
debug.declaration('generating')
415422
return writeDeclaration(this, this.options.dts)
416423
}
424+
425+
private async readInfoFromPagesJSON(): Promise<void> {
426+
const resolvedPagesJSONContent = await fs.promises.readFile(this.resolvedPagesJSONPath, { encoding: 'utf-8' }).catch(() => '')
427+
this.resolvedPagesJSONIndent = detectIndent(resolvedPagesJSONContent).indent || ' '
428+
this.resolvedPagesJSONNewline = detectNewline(resolvedPagesJSONContent) || '\n'
429+
this.resolvedPagesJSONEofNewline = (resolvedPagesJSONContent.at(-1) ?? '\n') === this.resolvedPagesJSONNewline
430+
}
431+
432+
private async getIndent() {
433+
if (!this.resolvedPagesJSONIndent) {
434+
await this.readInfoFromPagesJSON()
435+
}
436+
437+
return this.resolvedPagesJSONIndent!
438+
}
439+
440+
private async getNewline() {
441+
if (!this.resolvedPagesJSONNewline) {
442+
await this.readInfoFromPagesJSON()
443+
}
444+
445+
return this.resolvedPagesJSONNewline!
446+
}
447+
448+
private async getEndOfLine() {
449+
if (!this.resolvedPagesJSONEofNewline) {
450+
await this.readInfoFromPagesJSON()
451+
}
452+
453+
return this.resolvedPagesJSONEofNewline!
454+
}
417455
}
418456

419457
function getPagePaths(dir: string, options: ResolvedOptions) {

packages/core/src/files.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { ResolvedOptions } from './types'
22
import fs from 'node:fs'
3-
import { stringify as cjStringify } from 'comment-json'
43
import fg from 'fast-glob'
54

65
import { FILE_EXTENSIONS } from './constant'
@@ -23,23 +22,46 @@ export function getPageFiles(path: string, options: ResolvedOptions): string[] {
2322
return files
2423
}
2524

26-
export function checkPagesJsonFile(path: string) {
27-
if (!fs.existsSync(path)) {
28-
writeFileSync(path, cjStringify({ pages: [{ path: '' }] }, null, 2))
29-
return false
25+
/**
26+
* 检查指定路径的pages.json文件,如果文件不存在或不是有效文件则创建一个空的pages.json文件
27+
* @param path - 要检查的文件路径
28+
* @returns Promise<void> - 无返回值的异步函数
29+
*/
30+
export async function checkPagesJsonFile(path: fs.PathLike): Promise<boolean> {
31+
const createEmptyFile = (path: fs.PathLike) => {
32+
return fs.promises.writeFile(path, JSON.stringify({ pages: [{ path: '' }] }, null, 2), { encoding: 'utf-8' }).then(() => true).catch(() => false)
33+
}
34+
35+
const unlink = (path: fs.PathLike) => {
36+
return fs.promises.unlink(path).then(() => true).catch(() => false)
3037
}
31-
return true
32-
}
3338

34-
export function readFileSync(path: string) {
3539
try {
36-
return fs.readFileSync(path, { encoding: 'utf-8' })
40+
const stat = await fs.promises.stat(path)
41+
42+
if (!stat.isFile()) {
43+
// 不是文件,尝试删除
44+
if (!await unlink(path)) {
45+
return false
46+
}
47+
48+
return createEmptyFile(path) // 创建空文件
49+
}
50+
// 是文件
51+
52+
const access = await fs.promises.access(path, fs.constants.W_OK | fs.constants.R_OK).then(() => true).catch(() => false)
53+
if (!access) {
54+
// 无权限,尝试删除
55+
if (!await unlink(path)) {
56+
return false
57+
}
58+
59+
return createEmptyFile(path) // 创建空文件
60+
}
61+
return true
3762
}
3863
catch {
39-
return ''
64+
// stat 出错,证明没此文件
65+
return createEmptyFile(path) // 创建空文件
4066
}
4167
}
42-
43-
export function writeFileSync(path: string, content: string) {
44-
fs.writeFileSync(path, content, { encoding: 'utf-8' })
45-
}

packages/core/src/index.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { CallExpression } from '@babel/types'
22
import type { Plugin } from 'vite'
33
import type { UserOptions } from './types'
4-
import { spawn } from 'node:child_process'
54
import path from 'node:path'
65
import process from 'node:process'
76
import { babelParse } from 'ast-kit'
@@ -29,22 +28,7 @@ export * from './page'
2928
export * from './types'
3029
export * from './utils'
3130

32-
async function restart() {
33-
return new Promise((resolve) => {
34-
const build = spawn(process.argv.shift()!, process.argv, {
35-
cwd: process.env.VITE_ROOT_DIR || process.cwd(),
36-
detached: true,
37-
env: process.env,
38-
})
39-
build.stdout?.pipe(process.stdout)
40-
build.stderr?.pipe(process.stderr)
41-
build.on('close', (code) => {
42-
resolve(process.exit(code!))
43-
})
44-
})
45-
}
46-
47-
export function VitePluginUniPages(userOptions: UserOptions = {}): Plugin {
31+
export async function VitePluginUniPages(userOptions: UserOptions = {}): Promise<Plugin> {
4832
let ctx: PageContext
4933

5034
// TODO: check if the pages.json file is valid
@@ -53,7 +37,7 @@ export function VitePluginUniPages(userOptions: UserOptions = {}): Plugin {
5337
userOptions.outDir ?? 'src',
5438
OUTPUT_NAME,
5539
)
56-
const isValidated = checkPagesJsonFile(resolvedPagesJSONPath)
40+
await checkPagesJsonFile(resolvedPagesJSONPath)
5741

5842
return {
5943
name: 'vite-plugin-uni-pages',
@@ -71,13 +55,6 @@ export function VitePluginUniPages(userOptions: UserOptions = {}): Plugin {
7155
await ctx.updatePagesJSON()
7256

7357
if (config.command === 'build') {
74-
if (!isValidated) {
75-
ctx.logger?.warn('In build mode, if `pages.json` does not exist, the plugin cannot create the complete `pages.json` before the uni-app, so it restarts the build.', {
76-
timestamp: true,
77-
})
78-
await restart()
79-
}
80-
8158
if (config.build.watch)
8259
ctx.setupWatcher(chokidar.watch([...ctx.options.dirs, ...ctx.options.subPackages]))
8360
}

packages/core/src/page.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { SFCDescriptor, SFCParseOptions } from '@vue/compiler-sfc'
22
import type { TabBarItem } from './config'
33
import type { PageContext } from './context'
44
import type { PageMetaDatum, PagePath, RouteBlockLang, UserPageMeta } from './types'
5-
import { readFileSync } from 'node:fs'
5+
import fs from 'node:fs'
66
import { extname } from 'node:path'
77
import * as t from '@babel/types'
88
import { parse as VueParser } from '@vue/compiler-sfc'
@@ -89,7 +89,7 @@ export class Page {
8989

9090
private async readPageMetaFromFile(): Promise<UserPageMeta> {
9191
try {
92-
const content = readFileSync(this.path.absolutePath, 'utf-8')
92+
const content = await fs.promises.readFile(this.path.absolutePath, { encoding: 'utf-8' })
9393
const sfc = parseSFC(content, { filename: this.path.absolutePath })
9494

9595
const meta = await tryPageMetaFromMacro(sfc)

0 commit comments

Comments
 (0)