Skip to content

Commit 23ced95

Browse files
authored
fix(vue-tsc): handle concurrency when setting up plugin (#643)
1 parent e5b6f0f commit 23ced95

4 files changed

Lines changed: 99 additions & 21 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,7 @@ dist
110110
lib/
111111
playground-temp/
112112
packages/runtime/public/build/bundle.js
113-
docs/.vitepress/cache
113+
docs/.vitepress/cache
114+
115+
# vue-tsc fixture lock
116+
.vue-tsc-fixture.lock

packages/vite-plugin-checker/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@
3737
"npm-run-path": "^6.0.0",
3838
"picocolors": "^1.1.1",
3939
"picomatch": "^4.0.3",
40+
"proper-lockfile": "^4.1.2",
4041
"tiny-invariant": "^1.3.3",
4142
"tinyglobby": "^0.2.15",
4243
"vscode-uri": "^3.1.0"
4344
},
4445
"devDependencies": {
4546
"@types/eslint": "^8.56.12",
4647
"@types/picomatch": "^4.0.2",
48+
"@types/proper-lockfile": "^4.1.4",
4749
"@vue/language-core": "~3.2.6",
4850
"meow": "^13.2.0",
4951
"stylelint": "^16.22.0",

packages/vite-plugin-checker/src/checkers/vueTsc/prepareVueTsc.ts

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { access, cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
22
import { createRequire } from 'node:module'
33
import path, { dirname } from 'node:path'
44
import { fileURLToPath } from 'node:url'
5+
import lockfile from 'proper-lockfile'
56

67
const _require = createRequire(import.meta.url)
78

@@ -17,8 +18,30 @@ const proxyApiPath = _require.resolve(
1718
)
1819
const extraSupportedExtensions = ['.vue']
1920

21+
const LOCK_TIMEOUT_MS = 60000 // Max wait time
22+
const STALE_TIMEOUT_MS = 30000 // Auto-release stale locks
23+
24+
async function isFixtureValid(
25+
targetTsDir: string,
26+
vueTscFlagFile: string,
27+
currTsVersion: string,
28+
) {
29+
try {
30+
await access(targetTsDir)
31+
const targetTsVersion = JSON.parse(
32+
await readFile(path.resolve(targetTsDir, 'package.json'), 'utf8'),
33+
).version
34+
await access(vueTscFlagFile)
35+
const fixtureFlagContent = await readFile(vueTscFlagFile, 'utf8')
36+
return (
37+
targetTsVersion === currTsVersion && fixtureFlagContent === proxyApiPath
38+
)
39+
} catch {
40+
return false
41+
}
42+
}
43+
2044
export async function prepareVueTsc() {
21-
// 1. copy typescript to folder
2245
const targetTsDir = path.resolve(_dirname, 'typescript-vue-tsc')
2346
const vueTscFlagFile = path.resolve(targetTsDir, 'vue-tsc-resolve-path')
2447
const currTsVersion = _require('typescript/package.json').version
@@ -30,37 +53,49 @@ export async function prepareVueTsc() {
3053
)
3154
}
3255

33-
let shouldBuildFixture = true
56+
if (await isFixtureValid(targetTsDir, vueTscFlagFile, currTsVersion)) {
57+
return { targetTsDir }
58+
}
59+
60+
let release: Awaited<ReturnType<typeof lockfile.lock>>
61+
try {
62+
release = await lockfile.lock(_dirname, {
63+
lockfilePath: path.resolve(_dirname, '.vue-tsc-fixture.lock'),
64+
stale: STALE_TIMEOUT_MS,
65+
retries: {
66+
retries: Math.ceil(LOCK_TIMEOUT_MS / 1000),
67+
factor: 1,
68+
minTimeout: 1000,
69+
maxTimeout: 2000,
70+
randomize: true,
71+
},
72+
})
73+
} catch (err) {
74+
throw new Error(
75+
'[vite-plugin-checker] Failed to acquire lock for vue-tsc fixture preparation. ' +
76+
'Another process may be holding the lock.\n' +
77+
String(err),
78+
)
79+
}
80+
3481
try {
35-
await access(targetTsDir)
36-
const targetTsVersion = _require(
37-
path.resolve(targetTsDir, 'package.json'),
38-
).version
39-
// check fixture versions before re-use
40-
await access(vueTscFlagFile)
41-
const fixtureFlagContent = await readFile(vueTscFlagFile, 'utf8')
42-
if (
43-
targetTsVersion === currTsVersion &&
44-
fixtureFlagContent === proxyApiPath
45-
) {
46-
shouldBuildFixture = false
82+
// Double-check that the fixture is valid, another process may have built while trying to acquire the lock
83+
if (await isFixtureValid(targetTsDir, vueTscFlagFile, currTsVersion)) {
84+
return { targetTsDir }
4785
}
48-
} catch {
49-
// no matter what error, we should rebuild the fixture
50-
shouldBuildFixture = true
51-
}
5286

53-
if (shouldBuildFixture) {
5487
await rm(targetTsDir, { force: true, recursive: true })
5588
await mkdir(targetTsDir, { recursive: true })
5689
const sourceTsDir = path.resolve(_require.resolve('typescript'), '../..')
5790
await cp(sourceTsDir, targetTsDir, { recursive: true })
5891
await writeFile(vueTscFlagFile, proxyApiPath)
5992

60-
// 2. sync modification of lib/tsc.js with vue-tsc
93+
// sync modification of lib/tsc.js with vue-tsc
6194
await overrideTscJs(
6295
_require.resolve(path.resolve(targetTsDir, 'lib/typescript.js')),
6396
)
97+
} finally {
98+
await release()
6499
}
65100

66101
return { targetTsDir }

pnpm-lock.yaml

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)