Skip to content

Commit d46f4c2

Browse files
committed
feat(monaco): replace with unpkg and load on demand (deps)
1 parent 04daf03 commit d46f4c2

File tree

1 file changed

+104
-94
lines changed

1 file changed

+104
-94
lines changed

src/monaco/resource.ts

Lines changed: 104 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* base on @volar/jsdelivr
33
* MIT License https://github.com/volarjs/volar.js/blob/master/packages/jsdelivr/LICENSE
44
*/
5-
import type { FileSystem, FileType } from '@volar/language-service'
5+
import type { FileStat, FileSystem, FileType } from '@volar/language-service'
66
import type { URI } from 'vscode-uri'
77

88
const textCache = new Map<string, Promise<string | undefined>>()
@@ -20,17 +20,8 @@ export function createNpmFileSystem(
2020
onFetch?: (path: string, content: string) => void,
2121
): FileSystem {
2222
const fetchResults = new Map<string, Promise<string | undefined>>()
23-
const flatResults = new Map<
24-
string,
25-
Promise<
26-
{
27-
name: string
28-
size: number
29-
time: string
30-
hash: string
31-
}[]
32-
>
33-
>()
23+
const statCache = new Map<string, { type: FileType }>()
24+
const dirCache = new Map<string, [string, FileType][]>()
3425

3526
return {
3627
async stat(uri) {
@@ -65,7 +56,16 @@ export function createNpmFileSystem(
6556
}
6657

6758
async function _stat(path: string) {
68-
const [modName, pkgName, pkgVersion, pkgFilePath] = resolvePackageName(path)
59+
if (statCache.has(path)) {
60+
return {
61+
...statCache.get(path),
62+
ctime: -1,
63+
mtime: -1,
64+
size: -1,
65+
} as FileStat
66+
}
67+
68+
const [modName, pkgName, , pkgFilePath] = resolvePackageName(path)
6969
if (!pkgName) {
7070
if (modName.startsWith('@')) {
7171
return {
@@ -82,72 +82,111 @@ export function createNpmFileSystem(
8282
return
8383
}
8484

85-
if (!pkgFilePath) {
86-
// perf: skip flat request
87-
return {
88-
type: 2 satisfies FileType.Directory,
89-
ctime: -1,
90-
mtime: -1,
91-
size: -1,
85+
if (!pkgFilePath || pkgFilePath === '/') {
86+
const result = {
87+
type: 2 as FileType.Directory,
9288
}
89+
statCache.set(path, result)
90+
return { ...result, ctime: -1, mtime: -1, size: -1 }
9391
}
9492

95-
if (!flatResults.has(modName)) {
96-
flatResults.set(modName, flat(pkgName, pkgVersion))
97-
}
93+
try {
94+
const parentDir = path.substring(0, path.lastIndexOf('/'))
95+
const fileName = path.substring(path.lastIndexOf('/') + 1)
9896

99-
const flatResult = await flatResults.get(modName)!
100-
const filePath = path.slice(modName.length)
101-
const file = flatResult.find((file) => file.name === filePath)
102-
if (file) {
103-
return {
104-
type: 1 satisfies FileType.File,
105-
ctime: new Date(file.time).valueOf(),
106-
mtime: new Date(file.time).valueOf(),
107-
size: file.size,
108-
}
109-
} else if (
110-
flatResult.some((file) => file.name.startsWith(filePath + '/'))
111-
) {
112-
return {
113-
type: 2 satisfies FileType.Directory,
114-
ctime: -1,
115-
mtime: -1,
116-
size: -1,
97+
const dirContent = await _readDirectory(parentDir)
98+
const fileEntry = dirContent.find(([name]) => name === fileName)
99+
100+
if (fileEntry) {
101+
const result = {
102+
type: fileEntry[1] as FileType,
103+
}
104+
statCache.set(path, result)
105+
return { ...result, ctime: -1, mtime: -1, size: -1 }
117106
}
107+
108+
return
109+
} catch {
110+
return
118111
}
119112
}
120113

121114
async function _readDirectory(path: string): Promise<[string, FileType][]> {
122-
const [modName, pkgName, pkgVersion] = resolvePackageName(path)
115+
if (dirCache.has(path)) {
116+
return dirCache.get(path)!
117+
}
118+
119+
const [, pkgName, pkgVersion, pkgPath] = resolvePackageName(path)
120+
123121
if (!pkgName || !(await isValidPackageName(pkgName))) {
124122
return []
125123
}
126124

127-
if (!flatResults.has(modName)) {
128-
flatResults.set(modName, flat(pkgName, pkgVersion))
125+
const resolvedVersion = pkgVersion || 'latest'
126+
127+
let actualVersion = resolvedVersion
128+
if (resolvedVersion === 'latest') {
129+
try {
130+
const data = await fetchJson<{ version: string }>(
131+
`https://unpkg.com/${pkgName}@${resolvedVersion}/package.json`,
132+
)
133+
if (data?.version) {
134+
actualVersion = data.version
135+
}
136+
} catch {
137+
// ignore
138+
}
129139
}
130140

131-
const flatResult = await flatResults.get(modName)!
132-
const dirPath = path.slice(modName.length)
133-
const files = flatResult
134-
.filter((f) => f.name.substring(0, f.name.lastIndexOf('/')) === dirPath)
135-
.map((f) => f.name.slice(dirPath.length + 1))
136-
const dirs = flatResult
137-
.filter(
138-
(f) =>
139-
f.name.startsWith(dirPath + '/') &&
140-
f.name.substring(dirPath.length + 1).split('/').length >= 2,
141-
)
142-
.map((f) => f.name.slice(dirPath.length + 1).split('/')[0])
143-
144-
return [
145-
...files.map<[string, FileType]>((f) => [f, 1 satisfies FileType.File]),
146-
...[...new Set(dirs)].map<[string, FileType]>((f) => [
147-
f,
148-
2 satisfies FileType.Directory,
149-
]),
150-
]
141+
const endpoint = `https://unpkg.com/${pkgName}@${actualVersion}/${pkgPath}/?meta`
142+
143+
try {
144+
const data = await fetchJson<{
145+
files: {
146+
path: string
147+
type: 'file' | 'directory'
148+
size?: number
149+
}[]
150+
}>(endpoint)
151+
152+
if (!data?.files) {
153+
return []
154+
}
155+
156+
const result: [string, FileType][] = data.files.map((file) => {
157+
const type =
158+
file.type === 'directory'
159+
? (2 as FileType.Directory)
160+
: (1 as FileType.File)
161+
162+
const fullPath = file.path
163+
statCache.set(fullPath, { type })
164+
165+
return [_getNameFromPath(file.path), type]
166+
})
167+
168+
dirCache.set(path, result)
169+
return result
170+
} catch {
171+
return []
172+
}
173+
}
174+
175+
function _getNameFromPath(path: string): string {
176+
if (!path) return ''
177+
178+
const trimmedPath = path.endsWith('/') ? path.slice(0, -1) : path
179+
180+
const lastSlashIndex = trimmedPath.lastIndexOf('/')
181+
182+
if (
183+
lastSlashIndex === -1 ||
184+
(lastSlashIndex === 0 && trimmedPath.length === 1)
185+
) {
186+
return trimmedPath
187+
}
188+
189+
return trimmedPath.slice(lastSlashIndex + 1)
151190
}
152191

153192
async function _readFile(path: string): Promise<string | undefined> {
@@ -163,7 +202,7 @@ export function createNpmFileSystem(
163202
if ((await _stat(path))?.type !== (1 satisfies FileType.File)) {
164203
return
165204
}
166-
const text = await fetchText(`https://cdn.jsdelivr.net/npm/${path}`)
205+
const text = await fetchText(`https://unpkg.com/${path}`)
167206
if (text !== undefined) {
168207
onFetch?.(path, text)
169208
}
@@ -175,35 +214,6 @@ export function createNpmFileSystem(
175214
return await fetchResults.get(path)!
176215
}
177216

178-
async function flat(pkgName: string, version: string | undefined) {
179-
version ??= 'latest'
180-
181-
// resolve latest tag
182-
if (version === 'latest') {
183-
const data = await fetchJson<{ version: string | null }>(
184-
`https://data.jsdelivr.com/v1/package/resolve/npm/${pkgName}@${version}`,
185-
)
186-
if (!data?.version) {
187-
return []
188-
}
189-
version = data.version
190-
}
191-
192-
const flat = await fetchJson<{
193-
files: {
194-
name: string
195-
size: number
196-
time: string
197-
hash: string
198-
}[]
199-
}>(`https://data.jsdelivr.com/v1/package/npm/${pkgName}@${version}/flat`)
200-
if (!flat) {
201-
return []
202-
}
203-
204-
return flat.files
205-
}
206-
207217
async function isValidPackageName(pkgName: string) {
208218
// ignore @aaa/node_modules
209219
if (pkgName.endsWith('/node_modules')) {

0 commit comments

Comments
 (0)