@remix-run/assets 0.4.4 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +331 -72
- package/dist/assets.d.ts +4 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/lib/access.d.ts +35 -4
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +332 -11
- package/dist/lib/asset-server.d.ts +138 -11
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +281 -30
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/file-matcher.js +1 -1
- package/dist/lib/files/compiler.d.ts.map +1 -1
- package/dist/lib/files/compiler.js +9 -8
- package/dist/lib/files/config.js +1 -1
- package/dist/lib/hmr.d.ts +37 -0
- package/dist/lib/hmr.d.ts.map +1 -0
- package/dist/lib/hmr.js +357 -0
- package/dist/lib/injected-packages.d.ts +3 -2
- package/dist/lib/injected-packages.d.ts.map +1 -1
- package/dist/lib/injected-packages.js +43 -17
- package/dist/lib/inspection.d.ts +39 -0
- package/dist/lib/inspection.d.ts.map +1 -0
- package/dist/lib/inspection.js +160 -0
- package/dist/lib/loaders.d.ts +57 -0
- package/dist/lib/loaders.d.ts.map +1 -0
- package/dist/lib/loaders.js +1 -0
- package/dist/lib/module-store.d.ts +24 -0
- package/dist/lib/module-store.d.ts.map +1 -1
- package/dist/lib/module-store.js +136 -11
- package/dist/lib/routes.d.ts +11 -3
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +124 -80
- package/dist/lib/scripts/compiler.d.ts +24 -1
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +183 -29
- package/dist/lib/scripts/conditions.d.ts +2 -0
- package/dist/lib/scripts/conditions.d.ts.map +1 -0
- package/dist/lib/scripts/conditions.js +1 -0
- package/dist/lib/scripts/emit.d.ts +3 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -1
- package/dist/lib/scripts/emit.js +24 -5
- package/dist/lib/scripts/resolve.d.ts +4 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +75 -10
- package/dist/lib/scripts/transform.d.ts +9 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +222 -18
- package/dist/lib/source-maps.js +1 -1
- package/dist/lib/styles/compiler.d.ts +14 -1
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +78 -14
- package/dist/lib/styles/emit.js +4 -4
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +16 -15
- package/dist/lib/styles/transform.js +5 -5
- package/dist/lib/target.d.ts +1 -1
- package/dist/lib/target.d.ts.map +1 -1
- package/dist/lib/watch.js +1 -1
- package/dist/types/hmr.d.ts +36 -0
- package/package.json +16 -11
- package/src/assets.ts +4 -1
- package/src/lib/access.ts +445 -11
- package/src/lib/asset-server.ts +466 -27
- package/src/lib/compilation-error.ts +4 -3
- package/src/lib/files/compiler.ts +9 -5
- package/src/lib/hmr.ts +397 -0
- package/src/lib/injected-packages.ts +52 -16
- package/src/lib/inspection.ts +229 -0
- package/src/lib/loaders.ts +80 -0
- package/src/lib/module-store.ts +190 -9
- package/src/lib/routes.ts +158 -126
- package/src/lib/scripts/compiler.ts +248 -24
- package/src/lib/scripts/conditions.ts +1 -0
- package/src/lib/scripts/emit.ts +50 -5
- package/src/lib/scripts/resolve.ts +129 -7
- package/src/lib/scripts/transform.ts +290 -19
- package/src/lib/styles/compiler.ts +108 -8
- package/src/lib/styles/emit.ts +1 -1
- package/src/lib/styles/resolve.ts +19 -15
- package/src/lib/styles/transform.ts +2 -2
- package/src/types/hmr.d.ts +36 -0
package/dist/lib/hmr.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
export function createHmrClientSource(options) {
|
|
2
|
+
return `
|
|
3
|
+
const contexts = new Map()
|
|
4
|
+
const dataByPath = new Map()
|
|
5
|
+
|
|
6
|
+
class RemixHmrContext {
|
|
7
|
+
constructor(path) {
|
|
8
|
+
this.path = path
|
|
9
|
+
this.data = dataByPath.get(path) ?? {}
|
|
10
|
+
this.acceptCallbacks = []
|
|
11
|
+
this.acceptDependencyCallbacks = []
|
|
12
|
+
this.disposeCallbacks = []
|
|
13
|
+
this.customEventCallbacks = new Map()
|
|
14
|
+
this.invalidated = false
|
|
15
|
+
this.updating = false
|
|
16
|
+
dataByPath.set(path, this.data)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
accept(deps, callback) {
|
|
20
|
+
if (typeof deps === 'string') {
|
|
21
|
+
this.acceptDependencyCallbacks.push({
|
|
22
|
+
deps: [normalizeAcceptedDependency(this.path, deps)],
|
|
23
|
+
callback: callback ?? (() => {}),
|
|
24
|
+
})
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(deps)) {
|
|
29
|
+
this.acceptDependencyCallbacks.push({
|
|
30
|
+
deps: deps.map((dep) => normalizeAcceptedDependency(this.path, dep)),
|
|
31
|
+
callback: callback ?? (() => {}),
|
|
32
|
+
})
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.acceptCallbacks.push(deps ?? (() => {}))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dispose(callback) {
|
|
40
|
+
this.disposeCallbacks.push(callback)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
invalidate(message) {
|
|
44
|
+
this.invalidated = true
|
|
45
|
+
if (this.updating) {
|
|
46
|
+
if (message) console.debug(message)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
if (message) console.debug(message)
|
|
50
|
+
reloadPage()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
on(event, callback) {
|
|
54
|
+
let callbacks = this.customEventCallbacks.get(event)
|
|
55
|
+
if (!callbacks) {
|
|
56
|
+
callbacks = []
|
|
57
|
+
this.customEventCallbacks.set(event, callbacks)
|
|
58
|
+
}
|
|
59
|
+
callbacks.push(callback)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createHotContext(path) {
|
|
64
|
+
let context = new RemixHmrContext(path)
|
|
65
|
+
contexts.set(path, context)
|
|
66
|
+
return context
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let connected = false
|
|
70
|
+
let reconnectPending = false
|
|
71
|
+
let pageReloadTimer
|
|
72
|
+
let failedJavaScriptUpdates = new Map()
|
|
73
|
+
let stylesheetUpdatePromise = Promise.resolve()
|
|
74
|
+
|
|
75
|
+
let events = new EventSource(${JSON.stringify(options.eventPathname)})
|
|
76
|
+
|
|
77
|
+
events.onopen = () => {
|
|
78
|
+
console.debug('[remix] HMR connected')
|
|
79
|
+
if (reconnectPending) {
|
|
80
|
+
reconnectPending = false
|
|
81
|
+
console.log('[remix] HMR reconnected')
|
|
82
|
+
handleReconnect().catch((error) => {
|
|
83
|
+
console.error('[remix] HMR reconnect recovery failed', error)
|
|
84
|
+
reloadPage()
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
connected = true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
events.onerror = () => {
|
|
91
|
+
if (!connected) return
|
|
92
|
+
connected = false
|
|
93
|
+
reconnectPending = true
|
|
94
|
+
console.log('[remix] HMR connection lost, retrying...')
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
events.onmessage = (event) => {
|
|
98
|
+
let payload = JSON.parse(event.data)
|
|
99
|
+
handlePayload(payload).catch((error) => {
|
|
100
|
+
console.error('[remix] HMR update failed', error)
|
|
101
|
+
if (payload.type !== 'browser:update' || payload.updates.some((update) => update.type !== 'js')) {
|
|
102
|
+
reloadPage()
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function handlePayload(payload) {
|
|
108
|
+
if (payload.type === 'browser:reload') {
|
|
109
|
+
reloadPage()
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (payload.type === 'server:update') {
|
|
114
|
+
await retryFailedJavaScriptUpdates(payload)
|
|
115
|
+
await dispatchCustomEvent(payload.type, payload)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (payload.type === 'browser:update') {
|
|
120
|
+
for (let update of payload.updates) {
|
|
121
|
+
if (update.type === 'css') {
|
|
122
|
+
let updated = await queueStylesheetUpdate(update.path, payload.timestamp)
|
|
123
|
+
if (updated) console.debug('[remix] HMR updated stylesheet', update.path)
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
let updated = await updateJavaScriptModule(
|
|
129
|
+
update.path,
|
|
130
|
+
update.acceptedPath ?? update.path,
|
|
131
|
+
payload.timestamp,
|
|
132
|
+
)
|
|
133
|
+
failedJavaScriptUpdates.delete(update.path)
|
|
134
|
+
if (updated) console.debug('[remix] HMR accepted update', update.path)
|
|
135
|
+
} catch (error) {
|
|
136
|
+
failedJavaScriptUpdates.set(update.path, update.acceptedPath ?? update.path)
|
|
137
|
+
throw error
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function handleReconnect() {
|
|
144
|
+
let data = { timestamp: Date.now() }
|
|
145
|
+
await reloadCurrentStylesheets(data)
|
|
146
|
+
await retryFailedJavaScriptUpdates(data)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function retryFailedJavaScriptUpdates(data) {
|
|
150
|
+
if (failedJavaScriptUpdates.size === 0) return
|
|
151
|
+
|
|
152
|
+
let timestamp = getTimestamp(data)
|
|
153
|
+
for (let [path, acceptedPath] of Array.from(failedJavaScriptUpdates)) {
|
|
154
|
+
try {
|
|
155
|
+
let updated = await updateJavaScriptModule(path, acceptedPath, timestamp)
|
|
156
|
+
failedJavaScriptUpdates.delete(path)
|
|
157
|
+
if (updated) console.debug('[remix] HMR recovered update', path)
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('[remix] HMR recovery update failed', error)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function reloadCurrentStylesheets(data) {
|
|
165
|
+
let timestamp = getTimestamp(data)
|
|
166
|
+
let paths = new Set()
|
|
167
|
+
for (let link of document.querySelectorAll('link[rel="stylesheet"]')) {
|
|
168
|
+
if (link.dataset.remixHmrStylesheet === 'true') continue
|
|
169
|
+
let url = new URL(link.href)
|
|
170
|
+
if (url.origin !== location.origin) continue
|
|
171
|
+
paths.add(url.pathname)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (let path of paths) {
|
|
175
|
+
await queueStylesheetUpdate(path, timestamp)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getTimestamp(data) {
|
|
180
|
+
if (data && typeof data === 'object' && typeof data.timestamp === 'number') {
|
|
181
|
+
return data.timestamp
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return Date.now()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function updateJavaScriptModule(path, acceptedPath, timestamp) {
|
|
188
|
+
let previousContext = contexts.get(path)
|
|
189
|
+
if (!previousContext) return false
|
|
190
|
+
|
|
191
|
+
let isSelfUpdate = path === acceptedPath
|
|
192
|
+
let dependencyCallbacks = getAcceptDependencyCallbacks(previousContext, acceptedPath)
|
|
193
|
+
|
|
194
|
+
if (
|
|
195
|
+
isSelfUpdate ? previousContext.acceptCallbacks.length === 0 : dependencyCallbacks.length === 0
|
|
196
|
+
) {
|
|
197
|
+
console.log('[remix] HMR no accept handler, reloading page', path)
|
|
198
|
+
reloadPage()
|
|
199
|
+
return false
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (isSelfUpdate) {
|
|
203
|
+
for (let callback of previousContext.disposeCallbacks) {
|
|
204
|
+
await callback(previousContext.data)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let updatedModule = await import(withTimestamp(path, timestamp))
|
|
208
|
+
previousContext.invalidated = false
|
|
209
|
+
previousContext.updating = true
|
|
210
|
+
try {
|
|
211
|
+
for (let callback of previousContext.acceptCallbacks) {
|
|
212
|
+
await callback(updatedModule)
|
|
213
|
+
}
|
|
214
|
+
} finally {
|
|
215
|
+
previousContext.updating = false
|
|
216
|
+
}
|
|
217
|
+
if (previousContext.invalidated) {
|
|
218
|
+
await propagateInvalidatedJavaScriptModule(path, timestamp)
|
|
219
|
+
}
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let acceptedContext = contexts.get(acceptedPath)
|
|
224
|
+
if (acceptedContext) {
|
|
225
|
+
for (let callback of acceptedContext.disposeCallbacks) {
|
|
226
|
+
await callback(acceptedContext.data)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let updatedModule = await import(withTimestamp(acceptedPath, timestamp))
|
|
231
|
+
previousContext.invalidated = false
|
|
232
|
+
previousContext.updating = true
|
|
233
|
+
try {
|
|
234
|
+
for (let { deps, callback } of dependencyCallbacks) {
|
|
235
|
+
if (deps.length === 1) {
|
|
236
|
+
await callback(updatedModule)
|
|
237
|
+
} else {
|
|
238
|
+
await callback(deps.map((dep) => (dep === acceptedPath ? updatedModule : undefined)))
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} finally {
|
|
242
|
+
previousContext.updating = false
|
|
243
|
+
}
|
|
244
|
+
if (previousContext.invalidated) {
|
|
245
|
+
await propagateInvalidatedJavaScriptModule(path, timestamp)
|
|
246
|
+
}
|
|
247
|
+
return true
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function propagateInvalidatedJavaScriptModule(path, timestamp) {
|
|
251
|
+
let updated = false
|
|
252
|
+
for (let [importerPath, importerContext] of contexts) {
|
|
253
|
+
if (importerPath === path) continue
|
|
254
|
+
let callbacks = getAcceptDependencyCallbacks(importerContext, path)
|
|
255
|
+
if (callbacks.length === 0) continue
|
|
256
|
+
|
|
257
|
+
for (let callback of importerContext.disposeCallbacks) {
|
|
258
|
+
await callback(importerContext.data)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let updatedModule = await import(withTimestamp(path, timestamp))
|
|
262
|
+
importerContext.invalidated = false
|
|
263
|
+
importerContext.updating = true
|
|
264
|
+
try {
|
|
265
|
+
for (let { deps, callback } of callbacks) {
|
|
266
|
+
if (deps.length === 1) {
|
|
267
|
+
await callback(updatedModule)
|
|
268
|
+
} else {
|
|
269
|
+
await callback(deps.map((dep) => (dep === path ? updatedModule : undefined)))
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} finally {
|
|
273
|
+
importerContext.updating = false
|
|
274
|
+
}
|
|
275
|
+
updated = true
|
|
276
|
+
|
|
277
|
+
if (importerContext.invalidated) {
|
|
278
|
+
reloadPage()
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!updated) reloadPage()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getAcceptDependencyCallbacks(context, acceptedPath) {
|
|
287
|
+
return context.acceptDependencyCallbacks.filter(({ deps }) => deps.includes(acceptedPath))
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeAcceptedDependency(importerPath, dep) {
|
|
291
|
+
if (dep.startsWith('/')) return dep
|
|
292
|
+
return new URL(dep, new URL(importerPath, window.location.href)).pathname
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function dispatchCustomEvent(event, data) {
|
|
296
|
+
for (let context of contexts.values()) {
|
|
297
|
+
let callbacks = context.customEventCallbacks.get(event) ?? []
|
|
298
|
+
for (let callback of callbacks) {
|
|
299
|
+
await callback(data)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function reloadStylesheet(path, timestamp) {
|
|
305
|
+
let links = document.querySelectorAll('link[rel="stylesheet"]')
|
|
306
|
+
let updates = []
|
|
307
|
+
for (let link of links) {
|
|
308
|
+
let url = new URL(link.href)
|
|
309
|
+
if (url.pathname !== path) continue
|
|
310
|
+
if (link.dataset.remixHmrStylesheet === 'true') continue
|
|
311
|
+
|
|
312
|
+
updates.push(loadStylesheet(link, path, timestamp))
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (updates.length === 0) return false
|
|
316
|
+
return (await Promise.all(updates)).some(Boolean)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function queueStylesheetUpdate(path, timestamp) {
|
|
320
|
+
let update = stylesheetUpdatePromise.then(() => reloadStylesheet(path, timestamp))
|
|
321
|
+
stylesheetUpdatePromise = update.catch(() => {})
|
|
322
|
+
return update
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function loadStylesheet(link, path, timestamp) {
|
|
326
|
+
return new Promise((resolve) => {
|
|
327
|
+
let next = link.cloneNode()
|
|
328
|
+
next.dataset.remixHmrStylesheet = 'true'
|
|
329
|
+
next.href = withTimestamp(path, timestamp)
|
|
330
|
+
next.onload = () => {
|
|
331
|
+
delete next.dataset.remixHmrStylesheet
|
|
332
|
+
link.remove()
|
|
333
|
+
resolve(true)
|
|
334
|
+
}
|
|
335
|
+
next.onerror = () => {
|
|
336
|
+
next.remove()
|
|
337
|
+
resolve(false)
|
|
338
|
+
}
|
|
339
|
+
link.after(next)
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function withTimestamp(path, timestamp) {
|
|
344
|
+
let url = new URL(path, location.href)
|
|
345
|
+
url.searchParams.set('t', String(timestamp))
|
|
346
|
+
return url.pathname + url.search
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function reloadPage() {
|
|
350
|
+
if (pageReloadTimer) clearTimeout(pageReloadTimer)
|
|
351
|
+
pageReloadTimer = setTimeout(() => {
|
|
352
|
+
console.debug('[remix] HMR reloading page')
|
|
353
|
+
window.location.href = window.location.href
|
|
354
|
+
}, 20)
|
|
355
|
+
}
|
|
356
|
+
`.trimStart();
|
|
357
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export declare function isInjectedPackageFilePath(filePath: string): boolean;
|
|
2
|
-
export declare function
|
|
3
|
-
|
|
2
|
+
export declare function getInjectedPackageMountConfigs(): {
|
|
3
|
+
mounts: Record<string, string>;
|
|
4
4
|
rootDir: string;
|
|
5
5
|
}[];
|
|
6
|
+
export declare function getInjectedPackageRoots(): readonly string[];
|
|
6
7
|
export declare function getInjectedPackageNameForSpecifier(specifier: string): string | null;
|
|
7
8
|
export declare function mayContainInjectedPackageSpecifier(sourceText: string): boolean;
|
|
8
9
|
export declare function maskAuthoredInjectedPackageSpecifier(specifier: string): string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injected-packages.d.ts","sourceRoot":"","sources":["../../src/lib/injected-packages.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"injected-packages.d.ts","sourceRoot":"","sources":["../../src/lib/injected-packages.ts"],"names":[],"mappings":"AAiBA,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWnE;AAED,wBAAgB,8BAA8B,IAAI;IAChD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,OAAO,EAAE,MAAM,CAAA;CAChB,EAAE,CAYF;AAED,wBAAgB,uBAAuB,IAAI,SAAS,MAAM,EAAE,CAI3D;AAED,wBAAgB,kCAAkC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcnF;AAED,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE9E;AAED,wBAAgB,oCAAoC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASrF;AAED,wBAAgB,uCAAuC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYxF;AAWD,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { getFilePathDirectory, normalizeFilePath } from
|
|
3
|
+
import { getFilePathDirectory, normalizeFilePath } from './paths.js';
|
|
4
4
|
const injectedPackageNames = ['@oxc-project/runtime'];
|
|
5
|
+
const authoredInjectedPackageNames = ['@oxc-project/runtime'];
|
|
6
|
+
const generatedInjectedPackageSpecifiers = [];
|
|
5
7
|
const injectedPackagesBasePath = '/__@remix/injected';
|
|
6
8
|
const resolvedInjectedPackages = new Map();
|
|
7
9
|
export function isInjectedPackageFilePath(filePath) {
|
|
@@ -14,19 +16,28 @@ export function isInjectedPackageFilePath(filePath) {
|
|
|
14
16
|
}
|
|
15
17
|
return false;
|
|
16
18
|
}
|
|
17
|
-
export function
|
|
19
|
+
export function getInjectedPackageMountConfigs() {
|
|
18
20
|
return injectedPackageNames.map((packageName) => {
|
|
19
21
|
let { packageRoot } = getResolvedInjectedPackage(packageName);
|
|
22
|
+
let { fileRoot, routeRoot } = getInjectedPackageRoute(packageRoot, packageName);
|
|
20
23
|
return {
|
|
21
|
-
|
|
22
|
-
[
|
|
24
|
+
mounts: {
|
|
25
|
+
[getInjectedPackageMountPath(packageName)]: fileRoot,
|
|
23
26
|
},
|
|
24
|
-
rootDir:
|
|
27
|
+
rootDir: routeRoot,
|
|
25
28
|
};
|
|
26
29
|
});
|
|
27
30
|
}
|
|
31
|
+
export function getInjectedPackageRoots() {
|
|
32
|
+
return injectedPackageNames.map((packageName) => getResolvedInjectedPackage(packageName).packageRoot);
|
|
33
|
+
}
|
|
28
34
|
export function getInjectedPackageNameForSpecifier(specifier) {
|
|
29
|
-
for (let
|
|
35
|
+
for (let injectedSpecifier of generatedInjectedPackageSpecifiers) {
|
|
36
|
+
if (specifier === injectedSpecifier) {
|
|
37
|
+
return getPackageName(injectedSpecifier);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (let packageName of authoredInjectedPackageNames) {
|
|
30
41
|
if (specifier === packageName || specifier.startsWith(`${packageName}/`)) {
|
|
31
42
|
return packageName;
|
|
32
43
|
}
|
|
@@ -34,17 +45,19 @@ export function getInjectedPackageNameForSpecifier(specifier) {
|
|
|
34
45
|
return null;
|
|
35
46
|
}
|
|
36
47
|
export function mayContainInjectedPackageSpecifier(sourceText) {
|
|
37
|
-
return
|
|
48
|
+
return authoredInjectedPackageNames.some((packageName) => sourceText.includes(packageName));
|
|
38
49
|
}
|
|
39
50
|
export function maskAuthoredInjectedPackageSpecifier(specifier) {
|
|
40
|
-
let packageName
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
for (let packageName of authoredInjectedPackageNames) {
|
|
52
|
+
if (specifier !== packageName && !specifier.startsWith(`${packageName}/`))
|
|
53
|
+
continue;
|
|
54
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName);
|
|
55
|
+
return `${maskedPackageName}${specifier.slice(packageName.length)}`;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
45
58
|
}
|
|
46
59
|
export function restoreAuthoredInjectedPackageSpecifier(specifier) {
|
|
47
|
-
for (let packageName of
|
|
60
|
+
for (let packageName of authoredInjectedPackageNames) {
|
|
48
61
|
let maskedPackageName = getMaskedInjectedPackageName(packageName);
|
|
49
62
|
if (specifier === maskedPackageName) {
|
|
50
63
|
return packageName;
|
|
@@ -58,6 +71,10 @@ export function restoreAuthoredInjectedPackageSpecifier(specifier) {
|
|
|
58
71
|
function getMaskedInjectedPackageName(packageName) {
|
|
59
72
|
return `~${packageName.slice(1)}`;
|
|
60
73
|
}
|
|
74
|
+
function getPackageName(specifier) {
|
|
75
|
+
let parts = specifier.split('/');
|
|
76
|
+
return parts[0]?.startsWith('@') ? `${parts[0]}/${parts[1]}` : (parts[0] ?? specifier);
|
|
77
|
+
}
|
|
61
78
|
export function getInjectedPackageImporterPath() {
|
|
62
79
|
return normalizeFilePath(fileURLToPath(import.meta.url));
|
|
63
80
|
}
|
|
@@ -74,13 +91,22 @@ function getResolvedInjectedPackage(packageName) {
|
|
|
74
91
|
resolvedInjectedPackages.set(packageName, resolvedInjectedPackage);
|
|
75
92
|
return resolvedInjectedPackage;
|
|
76
93
|
}
|
|
77
|
-
function
|
|
78
|
-
return `${injectedPackagesBasePath}/${packageName}
|
|
94
|
+
function getInjectedPackageMountPath(packageName) {
|
|
95
|
+
return `${injectedPackagesBasePath}/${packageName}`;
|
|
79
96
|
}
|
|
80
|
-
function
|
|
97
|
+
function getInjectedPackageRoute(packageRoot, packageName) {
|
|
98
|
+
if (!packageRoot.endsWith(`/${packageName}`)) {
|
|
99
|
+
return {
|
|
100
|
+
fileRoot: packageRoot.slice(getFilePathDirectory(packageRoot).length + 1),
|
|
101
|
+
routeRoot: getFilePathDirectory(packageRoot),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
81
104
|
let routeRoot = packageRoot;
|
|
82
105
|
for (let _segment of packageName.split('/')) {
|
|
83
106
|
routeRoot = getFilePathDirectory(routeRoot);
|
|
84
107
|
}
|
|
85
|
-
return
|
|
108
|
+
return {
|
|
109
|
+
fileRoot: packageName,
|
|
110
|
+
routeRoot,
|
|
111
|
+
};
|
|
86
112
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AccessPolicy, AssetAccessDetails } from './access.ts';
|
|
2
|
+
import type { CompiledRoutes } from './routes.ts';
|
|
3
|
+
/** How the asset server handles an inspected file. */
|
|
4
|
+
export type AssetKind = 'file' | 'script' | 'style' | 'unsupported';
|
|
5
|
+
/** Browser-reachability result for an inspected asset. */
|
|
6
|
+
export type AssetStatus = 'denied' | 'missing' | 'not-allowed' | 'reachable' | 'unmapped' | 'unsupported';
|
|
7
|
+
/** Diagnostic information about a configured asset URL or file path. */
|
|
8
|
+
export interface AssetDetails {
|
|
9
|
+
/** Access-control decision and the rules responsible for it. */
|
|
10
|
+
access?: AssetAccessDetails;
|
|
11
|
+
/** Absolute mapped file path. */
|
|
12
|
+
filePath?: string;
|
|
13
|
+
/** Configured filesystem mount root that matched the asset. */
|
|
14
|
+
fileRoot?: string;
|
|
15
|
+
/** Browser-reachability result. */
|
|
16
|
+
status: AssetStatus;
|
|
17
|
+
/** How the asset server handles the file. */
|
|
18
|
+
type?: AssetKind;
|
|
19
|
+
/** Stable public URL pathname for the asset. */
|
|
20
|
+
url?: string;
|
|
21
|
+
/** Public mount root that matched the asset. */
|
|
22
|
+
urlRoot?: string;
|
|
23
|
+
}
|
|
24
|
+
interface AssetInspectorOptions {
|
|
25
|
+
accessPolicy: AccessPolicy;
|
|
26
|
+
allowFiles: readonly string[];
|
|
27
|
+
fileExtensions: readonly string[];
|
|
28
|
+
rootDir: string;
|
|
29
|
+
routes: CompiledRoutes;
|
|
30
|
+
}
|
|
31
|
+
export interface AssetInspector {
|
|
32
|
+
/** Returns diagnostic information for a public URL or file path. */
|
|
33
|
+
getAssetDetails(input: string): Promise<AssetDetails>;
|
|
34
|
+
/** Returns every file currently reachable through the configured asset server. */
|
|
35
|
+
getAssets(): Promise<AssetDetails[]>;
|
|
36
|
+
}
|
|
37
|
+
export declare function createAssetInspector(options: AssetInspectorOptions): AssetInspector;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=inspection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspection.d.ts","sourceRoot":"","sources":["../../src/lib/inspection.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAInE,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,aAAa,CAAA;AAOlE,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,aAAa,CAAA;AAEnE,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,SAAS,GACT,aAAa,GACb,WAAW,GACX,UAAU,GACV,aAAa,CAAA;AAEjB,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mCAAmC;IACnC,MAAM,EAAE,WAAW,CAAA;IACnB,6CAA6C;IAC7C,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,UAAU,qBAAqB;IAC7B,YAAY,EAAE,YAAY,CAAA;IAC1B,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7B,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,cAAc,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACrD,kFAAkF;IAClF,SAAS,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;CACrC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CA4BnF"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as fsPromises from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { parseFingerprintSuffix } from './fingerprint.js';
|
|
6
|
+
import { getInjectedPackageRoots } from './injected-packages.js';
|
|
7
|
+
import { isAbsoluteFilePath, normalizeFilePath, resolveFilePath } from './paths.js';
|
|
8
|
+
import { supportedScriptExtensions } from './scripts/resolve.js';
|
|
9
|
+
import { isStyleFilePath } from './styles/compiler.js';
|
|
10
|
+
const scriptExtensions = new Set(supportedScriptExtensions);
|
|
11
|
+
const globSyntaxPattern = /[*?[\]{}()!+@]/;
|
|
12
|
+
export function createAssetInspector(options) {
|
|
13
|
+
let fileExtensions = new Set(options.fileExtensions.map((extension) => extension.toLowerCase()));
|
|
14
|
+
return {
|
|
15
|
+
async getAssetDetails(input) {
|
|
16
|
+
let routeMatch = await resolveInput(input, options);
|
|
17
|
+
if (routeMatch === null)
|
|
18
|
+
return { status: 'unmapped' };
|
|
19
|
+
return inspectRouteMatch(routeMatch, options, fileExtensions);
|
|
20
|
+
},
|
|
21
|
+
async getAssets() {
|
|
22
|
+
let filePaths = await discoverFilePaths(options);
|
|
23
|
+
let assets = [];
|
|
24
|
+
for (let filePath of filePaths) {
|
|
25
|
+
let routeMatch = options.routes.matchFilePath(filePath);
|
|
26
|
+
if (routeMatch === null)
|
|
27
|
+
continue;
|
|
28
|
+
let details = await inspectRouteMatch(routeMatch, options, fileExtensions);
|
|
29
|
+
if (details.status === 'reachable')
|
|
30
|
+
assets.push(details);
|
|
31
|
+
}
|
|
32
|
+
assets.sort((left, right) => {
|
|
33
|
+
let urlOrder = (left.url ?? '').localeCompare(right.url ?? '');
|
|
34
|
+
return urlOrder === 0 ? (left.filePath ?? '').localeCompare(right.filePath ?? '') : urlOrder;
|
|
35
|
+
});
|
|
36
|
+
return assets;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function resolveInput(input, options) {
|
|
41
|
+
if (input.startsWith('file://')) {
|
|
42
|
+
return options.routes.matchFilePath(fileURLToPath(input));
|
|
43
|
+
}
|
|
44
|
+
if (/^[A-Za-z][A-Za-z\d+.-]*:\/\//.test(input)) {
|
|
45
|
+
let pathname = parseFingerprintSuffix(new URL(input).pathname).pathname;
|
|
46
|
+
return options.routes.matchUrlPathname(pathname);
|
|
47
|
+
}
|
|
48
|
+
let filePath = resolveFilePath(options.rootDir, input);
|
|
49
|
+
if (!input.startsWith('/') || isAbsoluteFilePath(input)) {
|
|
50
|
+
if (await pathExists(filePath))
|
|
51
|
+
return options.routes.matchFilePath(filePath);
|
|
52
|
+
if (!input.startsWith('/'))
|
|
53
|
+
return options.routes.matchFilePath(filePath);
|
|
54
|
+
}
|
|
55
|
+
let pathname = parseFingerprintSuffix(new URL(input, 'http://remix.run').pathname).pathname;
|
|
56
|
+
return options.routes.matchUrlPathname(pathname);
|
|
57
|
+
}
|
|
58
|
+
async function inspectRouteMatch(routeMatch, options, fileExtensions) {
|
|
59
|
+
let exists = await pathExists(routeMatch.filePath);
|
|
60
|
+
let identityPath = exists ? fs.realpathSync(routeMatch.filePath) : routeMatch.filePath;
|
|
61
|
+
let normalizedIdentityPath = normalizeFilePath(identityPath);
|
|
62
|
+
let access = options.accessPolicy.inspect(normalizedIdentityPath);
|
|
63
|
+
let type = getAssetKind(routeMatch.filePath, fileExtensions);
|
|
64
|
+
let details = {
|
|
65
|
+
access,
|
|
66
|
+
filePath: routeMatch.filePath,
|
|
67
|
+
fileRoot: routeMatch.fileRoot,
|
|
68
|
+
type,
|
|
69
|
+
url: routeMatch.urlPathname,
|
|
70
|
+
urlRoot: routeMatch.urlRoot,
|
|
71
|
+
};
|
|
72
|
+
if (!exists)
|
|
73
|
+
return { ...details, status: 'missing' };
|
|
74
|
+
if (!access.allowed) {
|
|
75
|
+
return { ...details, status: access.deniedBy === undefined ? 'not-allowed' : 'denied' };
|
|
76
|
+
}
|
|
77
|
+
if (type === 'unsupported')
|
|
78
|
+
return { ...details, status: 'unsupported' };
|
|
79
|
+
return { ...details, status: 'reachable' };
|
|
80
|
+
}
|
|
81
|
+
function getAssetKind(filePath, fileExtensions) {
|
|
82
|
+
let extension = path.extname(filePath).toLowerCase();
|
|
83
|
+
if (scriptExtensions.has(extension))
|
|
84
|
+
return 'script';
|
|
85
|
+
if (isStyleFilePath(filePath))
|
|
86
|
+
return 'style';
|
|
87
|
+
if (fileExtensions.has(extension))
|
|
88
|
+
return 'file';
|
|
89
|
+
return 'unsupported';
|
|
90
|
+
}
|
|
91
|
+
async function discoverFilePaths(options) {
|
|
92
|
+
let roots = new Set();
|
|
93
|
+
for (let pattern of options.allowFiles) {
|
|
94
|
+
roots.add(resolveDiscoveryRoot(options.rootDir, pattern));
|
|
95
|
+
}
|
|
96
|
+
for (let packageRoot of options.accessPolicy.getAllowedPackageRoots()) {
|
|
97
|
+
roots.add(packageRoot);
|
|
98
|
+
}
|
|
99
|
+
for (let packageRoot of getInjectedPackageRoots()) {
|
|
100
|
+
roots.add(packageRoot);
|
|
101
|
+
}
|
|
102
|
+
let filePaths = new Set();
|
|
103
|
+
for (let root of roots) {
|
|
104
|
+
await collectFiles(root, filePaths);
|
|
105
|
+
}
|
|
106
|
+
return [...filePaths];
|
|
107
|
+
}
|
|
108
|
+
function resolveDiscoveryRoot(rootDir, pattern) {
|
|
109
|
+
let dynamicIndex = pattern.search(globSyntaxPattern);
|
|
110
|
+
if (dynamicIndex === -1)
|
|
111
|
+
return resolveFilePath(rootDir, pattern);
|
|
112
|
+
let rawStaticPrefix = pattern.slice(0, dynamicIndex);
|
|
113
|
+
let staticPrefix = rawStaticPrefix.replace(/[/\\]+$/, '');
|
|
114
|
+
if (staticPrefix.length === 0)
|
|
115
|
+
return rootDir;
|
|
116
|
+
return resolveFilePath(rootDir, /[/\\]$/.test(rawStaticPrefix) ? staticPrefix : path.dirname(staticPrefix));
|
|
117
|
+
}
|
|
118
|
+
async function collectFiles(root, filePaths) {
|
|
119
|
+
let stat;
|
|
120
|
+
try {
|
|
121
|
+
stat = await fsPromises.stat(root);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (isPathNotFoundError(error))
|
|
125
|
+
return;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
if (stat.isFile()) {
|
|
129
|
+
filePaths.add(normalizeFilePath(root));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!stat.isDirectory())
|
|
133
|
+
return;
|
|
134
|
+
let entries = await fsPromises.readdir(root, { withFileTypes: true });
|
|
135
|
+
for (let entry of entries) {
|
|
136
|
+
let entryPath = path.join(root, entry.name);
|
|
137
|
+
if (entry.isDirectory()) {
|
|
138
|
+
await collectFiles(entryPath, filePaths);
|
|
139
|
+
}
|
|
140
|
+
else if (entry.isFile()) {
|
|
141
|
+
filePaths.add(normalizeFilePath(entryPath));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function pathExists(filePath) {
|
|
146
|
+
try {
|
|
147
|
+
await fsPromises.access(filePath);
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (isPathNotFoundError(error))
|
|
152
|
+
return false;
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function isPathNotFoundError(error) {
|
|
157
|
+
return (error instanceof Error &&
|
|
158
|
+
'code' in error &&
|
|
159
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR'));
|
|
160
|
+
}
|