@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
|
@@ -2,22 +2,23 @@ type AssetServerCompilationErrorCode =
|
|
|
2
2
|
| 'FILE_NOT_FOUND'
|
|
3
3
|
| 'FILE_NOT_ALLOWED'
|
|
4
4
|
| 'FILE_NOT_SUPPORTED'
|
|
5
|
-
| '
|
|
5
|
+
| 'FILE_OUTSIDE_MOUNTS'
|
|
6
6
|
| 'FILE_TRANSFORM_QUERY_INVALID'
|
|
7
7
|
| 'FILE_TRANSFORM_NOT_SUPPORTED'
|
|
8
8
|
| 'FILE_TRANSFORM_RESULT_INVALID'
|
|
9
9
|
| 'FILE_TRANSFORM_FAILED'
|
|
10
10
|
| 'COMMONJS_NOT_SUPPORTED'
|
|
11
11
|
| 'TRANSFORM_FAILED'
|
|
12
|
+
| 'LOADER_FAILED'
|
|
12
13
|
| 'EMIT_FAILED'
|
|
13
14
|
| 'IMPORT_RESOLUTION_FAILED'
|
|
14
15
|
| 'IMPORT_NOT_SUPPORTED'
|
|
15
16
|
| 'IMPORT_NOT_ALLOWED'
|
|
16
|
-
| '
|
|
17
|
+
| 'IMPORT_OUTSIDE_MOUNTS'
|
|
17
18
|
| 'URL_RESOLUTION_FAILED'
|
|
18
19
|
| 'URL_NOT_SUPPORTED'
|
|
19
20
|
| 'URL_NOT_ALLOWED'
|
|
20
|
-
| '
|
|
21
|
+
| 'URL_OUTSIDE_MOUNTS'
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Internal error used by the request-time asset compilation pipeline.
|
|
@@ -554,17 +554,21 @@ export function resolveServedFileOrThrow(filePath: string, args: ResolveArgs): R
|
|
|
554
554
|
}
|
|
555
555
|
|
|
556
556
|
if (!args.isAllowed(identityPath)) {
|
|
557
|
-
throw createAssetServerCompilationError(
|
|
558
|
-
|
|
559
|
-
|
|
557
|
+
throw createAssetServerCompilationError(
|
|
558
|
+
`File "${identityPath}" is not allowed by the asset server access configuration. ` +
|
|
559
|
+
`Add a matching allowFiles or allowPackages rule, or remove a conflicting denyFiles rule.`,
|
|
560
|
+
{
|
|
561
|
+
code: 'FILE_NOT_ALLOWED',
|
|
562
|
+
},
|
|
563
|
+
)
|
|
560
564
|
}
|
|
561
565
|
|
|
562
566
|
let stableUrlPathname = args.routes.toUrlPathname(identityPath)
|
|
563
567
|
if (!stableUrlPathname) {
|
|
564
568
|
throw createAssetServerCompilationError(
|
|
565
|
-
`File ${identityPath} is outside all configured
|
|
569
|
+
`File ${identityPath} is outside all configured mounts.`,
|
|
566
570
|
{
|
|
567
|
-
code: '
|
|
571
|
+
code: 'FILE_OUTSIDE_MOUNTS',
|
|
568
572
|
},
|
|
569
573
|
)
|
|
570
574
|
}
|
package/src/lib/hmr.ts
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Messages consumed by the browser HMR client.
|
|
3
|
+
*
|
|
4
|
+
* Server runtimes use `server:update` to request reconciliation with freshly rendered server
|
|
5
|
+
* output. Asset tooling uses `browser:update` for updates accepted by JavaScript or CSS boundaries,
|
|
6
|
+
* and falls back to `browser:reload` when the page must reload.
|
|
7
|
+
*/
|
|
8
|
+
export type HmrPayload =
|
|
9
|
+
| {
|
|
10
|
+
/** Indicates that server-rendered output may have changed. */
|
|
11
|
+
type: 'server:update'
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
/** JavaScript and CSS modules that the browser should update. */
|
|
15
|
+
updates: Array<
|
|
16
|
+
| {
|
|
17
|
+
/** Importing module whose dependency-accept handler accepts this update. */
|
|
18
|
+
acceptedPath?: string
|
|
19
|
+
/** Public URL of the changed JavaScript module. */
|
|
20
|
+
path: string
|
|
21
|
+
/** Identifies a JavaScript module update. */
|
|
22
|
+
type: 'js'
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
/** Public URL of the changed stylesheet. */
|
|
26
|
+
path: string
|
|
27
|
+
/** Identifies a stylesheet update. */
|
|
28
|
+
type: 'css'
|
|
29
|
+
}
|
|
30
|
+
>
|
|
31
|
+
/** Update time used to bypass browser module and stylesheet caches. */
|
|
32
|
+
timestamp: number
|
|
33
|
+
/** Indicates that the listed browser modules should update in place. */
|
|
34
|
+
type: 'browser:update'
|
|
35
|
+
}
|
|
36
|
+
| {
|
|
37
|
+
/** Indicates that no HMR boundary accepted the change and the page must reload. */
|
|
38
|
+
type: 'browser:reload'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createHmrClientSource(options: { eventPathname: string }): string {
|
|
42
|
+
return `
|
|
43
|
+
const contexts = new Map()
|
|
44
|
+
const dataByPath = new Map()
|
|
45
|
+
|
|
46
|
+
class RemixHmrContext {
|
|
47
|
+
constructor(path) {
|
|
48
|
+
this.path = path
|
|
49
|
+
this.data = dataByPath.get(path) ?? {}
|
|
50
|
+
this.acceptCallbacks = []
|
|
51
|
+
this.acceptDependencyCallbacks = []
|
|
52
|
+
this.disposeCallbacks = []
|
|
53
|
+
this.customEventCallbacks = new Map()
|
|
54
|
+
this.invalidated = false
|
|
55
|
+
this.updating = false
|
|
56
|
+
dataByPath.set(path, this.data)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
accept(deps, callback) {
|
|
60
|
+
if (typeof deps === 'string') {
|
|
61
|
+
this.acceptDependencyCallbacks.push({
|
|
62
|
+
deps: [normalizeAcceptedDependency(this.path, deps)],
|
|
63
|
+
callback: callback ?? (() => {}),
|
|
64
|
+
})
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (Array.isArray(deps)) {
|
|
69
|
+
this.acceptDependencyCallbacks.push({
|
|
70
|
+
deps: deps.map((dep) => normalizeAcceptedDependency(this.path, dep)),
|
|
71
|
+
callback: callback ?? (() => {}),
|
|
72
|
+
})
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.acceptCallbacks.push(deps ?? (() => {}))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
dispose(callback) {
|
|
80
|
+
this.disposeCallbacks.push(callback)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
invalidate(message) {
|
|
84
|
+
this.invalidated = true
|
|
85
|
+
if (this.updating) {
|
|
86
|
+
if (message) console.debug(message)
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
if (message) console.debug(message)
|
|
90
|
+
reloadPage()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
on(event, callback) {
|
|
94
|
+
let callbacks = this.customEventCallbacks.get(event)
|
|
95
|
+
if (!callbacks) {
|
|
96
|
+
callbacks = []
|
|
97
|
+
this.customEventCallbacks.set(event, callbacks)
|
|
98
|
+
}
|
|
99
|
+
callbacks.push(callback)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function createHotContext(path) {
|
|
104
|
+
let context = new RemixHmrContext(path)
|
|
105
|
+
contexts.set(path, context)
|
|
106
|
+
return context
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let connected = false
|
|
110
|
+
let reconnectPending = false
|
|
111
|
+
let pageReloadTimer
|
|
112
|
+
let failedJavaScriptUpdates = new Map()
|
|
113
|
+
let stylesheetUpdatePromise = Promise.resolve()
|
|
114
|
+
|
|
115
|
+
let events = new EventSource(${JSON.stringify(options.eventPathname)})
|
|
116
|
+
|
|
117
|
+
events.onopen = () => {
|
|
118
|
+
console.debug('[remix] HMR connected')
|
|
119
|
+
if (reconnectPending) {
|
|
120
|
+
reconnectPending = false
|
|
121
|
+
console.log('[remix] HMR reconnected')
|
|
122
|
+
handleReconnect().catch((error) => {
|
|
123
|
+
console.error('[remix] HMR reconnect recovery failed', error)
|
|
124
|
+
reloadPage()
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
connected = true
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
events.onerror = () => {
|
|
131
|
+
if (!connected) return
|
|
132
|
+
connected = false
|
|
133
|
+
reconnectPending = true
|
|
134
|
+
console.log('[remix] HMR connection lost, retrying...')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
events.onmessage = (event) => {
|
|
138
|
+
let payload = JSON.parse(event.data)
|
|
139
|
+
handlePayload(payload).catch((error) => {
|
|
140
|
+
console.error('[remix] HMR update failed', error)
|
|
141
|
+
if (payload.type !== 'browser:update' || payload.updates.some((update) => update.type !== 'js')) {
|
|
142
|
+
reloadPage()
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function handlePayload(payload) {
|
|
148
|
+
if (payload.type === 'browser:reload') {
|
|
149
|
+
reloadPage()
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (payload.type === 'server:update') {
|
|
154
|
+
await retryFailedJavaScriptUpdates(payload)
|
|
155
|
+
await dispatchCustomEvent(payload.type, payload)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (payload.type === 'browser:update') {
|
|
160
|
+
for (let update of payload.updates) {
|
|
161
|
+
if (update.type === 'css') {
|
|
162
|
+
let updated = await queueStylesheetUpdate(update.path, payload.timestamp)
|
|
163
|
+
if (updated) console.debug('[remix] HMR updated stylesheet', update.path)
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
let updated = await updateJavaScriptModule(
|
|
169
|
+
update.path,
|
|
170
|
+
update.acceptedPath ?? update.path,
|
|
171
|
+
payload.timestamp,
|
|
172
|
+
)
|
|
173
|
+
failedJavaScriptUpdates.delete(update.path)
|
|
174
|
+
if (updated) console.debug('[remix] HMR accepted update', update.path)
|
|
175
|
+
} catch (error) {
|
|
176
|
+
failedJavaScriptUpdates.set(update.path, update.acceptedPath ?? update.path)
|
|
177
|
+
throw error
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleReconnect() {
|
|
184
|
+
let data = { timestamp: Date.now() }
|
|
185
|
+
await reloadCurrentStylesheets(data)
|
|
186
|
+
await retryFailedJavaScriptUpdates(data)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function retryFailedJavaScriptUpdates(data) {
|
|
190
|
+
if (failedJavaScriptUpdates.size === 0) return
|
|
191
|
+
|
|
192
|
+
let timestamp = getTimestamp(data)
|
|
193
|
+
for (let [path, acceptedPath] of Array.from(failedJavaScriptUpdates)) {
|
|
194
|
+
try {
|
|
195
|
+
let updated = await updateJavaScriptModule(path, acceptedPath, timestamp)
|
|
196
|
+
failedJavaScriptUpdates.delete(path)
|
|
197
|
+
if (updated) console.debug('[remix] HMR recovered update', path)
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('[remix] HMR recovery update failed', error)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function reloadCurrentStylesheets(data) {
|
|
205
|
+
let timestamp = getTimestamp(data)
|
|
206
|
+
let paths = new Set()
|
|
207
|
+
for (let link of document.querySelectorAll('link[rel="stylesheet"]')) {
|
|
208
|
+
if (link.dataset.remixHmrStylesheet === 'true') continue
|
|
209
|
+
let url = new URL(link.href)
|
|
210
|
+
if (url.origin !== location.origin) continue
|
|
211
|
+
paths.add(url.pathname)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (let path of paths) {
|
|
215
|
+
await queueStylesheetUpdate(path, timestamp)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getTimestamp(data) {
|
|
220
|
+
if (data && typeof data === 'object' && typeof data.timestamp === 'number') {
|
|
221
|
+
return data.timestamp
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return Date.now()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function updateJavaScriptModule(path, acceptedPath, timestamp) {
|
|
228
|
+
let previousContext = contexts.get(path)
|
|
229
|
+
if (!previousContext) return false
|
|
230
|
+
|
|
231
|
+
let isSelfUpdate = path === acceptedPath
|
|
232
|
+
let dependencyCallbacks = getAcceptDependencyCallbacks(previousContext, acceptedPath)
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
isSelfUpdate ? previousContext.acceptCallbacks.length === 0 : dependencyCallbacks.length === 0
|
|
236
|
+
) {
|
|
237
|
+
console.log('[remix] HMR no accept handler, reloading page', path)
|
|
238
|
+
reloadPage()
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isSelfUpdate) {
|
|
243
|
+
for (let callback of previousContext.disposeCallbacks) {
|
|
244
|
+
await callback(previousContext.data)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let updatedModule = await import(withTimestamp(path, timestamp))
|
|
248
|
+
previousContext.invalidated = false
|
|
249
|
+
previousContext.updating = true
|
|
250
|
+
try {
|
|
251
|
+
for (let callback of previousContext.acceptCallbacks) {
|
|
252
|
+
await callback(updatedModule)
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
previousContext.updating = false
|
|
256
|
+
}
|
|
257
|
+
if (previousContext.invalidated) {
|
|
258
|
+
await propagateInvalidatedJavaScriptModule(path, timestamp)
|
|
259
|
+
}
|
|
260
|
+
return true
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let acceptedContext = contexts.get(acceptedPath)
|
|
264
|
+
if (acceptedContext) {
|
|
265
|
+
for (let callback of acceptedContext.disposeCallbacks) {
|
|
266
|
+
await callback(acceptedContext.data)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let updatedModule = await import(withTimestamp(acceptedPath, timestamp))
|
|
271
|
+
previousContext.invalidated = false
|
|
272
|
+
previousContext.updating = true
|
|
273
|
+
try {
|
|
274
|
+
for (let { deps, callback } of dependencyCallbacks) {
|
|
275
|
+
if (deps.length === 1) {
|
|
276
|
+
await callback(updatedModule)
|
|
277
|
+
} else {
|
|
278
|
+
await callback(deps.map((dep) => (dep === acceptedPath ? updatedModule : undefined)))
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
previousContext.updating = false
|
|
283
|
+
}
|
|
284
|
+
if (previousContext.invalidated) {
|
|
285
|
+
await propagateInvalidatedJavaScriptModule(path, timestamp)
|
|
286
|
+
}
|
|
287
|
+
return true
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function propagateInvalidatedJavaScriptModule(path, timestamp) {
|
|
291
|
+
let updated = false
|
|
292
|
+
for (let [importerPath, importerContext] of contexts) {
|
|
293
|
+
if (importerPath === path) continue
|
|
294
|
+
let callbacks = getAcceptDependencyCallbacks(importerContext, path)
|
|
295
|
+
if (callbacks.length === 0) continue
|
|
296
|
+
|
|
297
|
+
for (let callback of importerContext.disposeCallbacks) {
|
|
298
|
+
await callback(importerContext.data)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let updatedModule = await import(withTimestamp(path, timestamp))
|
|
302
|
+
importerContext.invalidated = false
|
|
303
|
+
importerContext.updating = true
|
|
304
|
+
try {
|
|
305
|
+
for (let { deps, callback } of callbacks) {
|
|
306
|
+
if (deps.length === 1) {
|
|
307
|
+
await callback(updatedModule)
|
|
308
|
+
} else {
|
|
309
|
+
await callback(deps.map((dep) => (dep === path ? updatedModule : undefined)))
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} finally {
|
|
313
|
+
importerContext.updating = false
|
|
314
|
+
}
|
|
315
|
+
updated = true
|
|
316
|
+
|
|
317
|
+
if (importerContext.invalidated) {
|
|
318
|
+
reloadPage()
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!updated) reloadPage()
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function getAcceptDependencyCallbacks(context, acceptedPath) {
|
|
327
|
+
return context.acceptDependencyCallbacks.filter(({ deps }) => deps.includes(acceptedPath))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function normalizeAcceptedDependency(importerPath, dep) {
|
|
331
|
+
if (dep.startsWith('/')) return dep
|
|
332
|
+
return new URL(dep, new URL(importerPath, window.location.href)).pathname
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function dispatchCustomEvent(event, data) {
|
|
336
|
+
for (let context of contexts.values()) {
|
|
337
|
+
let callbacks = context.customEventCallbacks.get(event) ?? []
|
|
338
|
+
for (let callback of callbacks) {
|
|
339
|
+
await callback(data)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function reloadStylesheet(path, timestamp) {
|
|
345
|
+
let links = document.querySelectorAll('link[rel="stylesheet"]')
|
|
346
|
+
let updates = []
|
|
347
|
+
for (let link of links) {
|
|
348
|
+
let url = new URL(link.href)
|
|
349
|
+
if (url.pathname !== path) continue
|
|
350
|
+
if (link.dataset.remixHmrStylesheet === 'true') continue
|
|
351
|
+
|
|
352
|
+
updates.push(loadStylesheet(link, path, timestamp))
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (updates.length === 0) return false
|
|
356
|
+
return (await Promise.all(updates)).some(Boolean)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function queueStylesheetUpdate(path, timestamp) {
|
|
360
|
+
let update = stylesheetUpdatePromise.then(() => reloadStylesheet(path, timestamp))
|
|
361
|
+
stylesheetUpdatePromise = update.catch(() => {})
|
|
362
|
+
return update
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function loadStylesheet(link, path, timestamp) {
|
|
366
|
+
return new Promise((resolve) => {
|
|
367
|
+
let next = link.cloneNode()
|
|
368
|
+
next.dataset.remixHmrStylesheet = 'true'
|
|
369
|
+
next.href = withTimestamp(path, timestamp)
|
|
370
|
+
next.onload = () => {
|
|
371
|
+
delete next.dataset.remixHmrStylesheet
|
|
372
|
+
link.remove()
|
|
373
|
+
resolve(true)
|
|
374
|
+
}
|
|
375
|
+
next.onerror = () => {
|
|
376
|
+
next.remove()
|
|
377
|
+
resolve(false)
|
|
378
|
+
}
|
|
379
|
+
link.after(next)
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function withTimestamp(path, timestamp) {
|
|
384
|
+
let url = new URL(path, location.href)
|
|
385
|
+
url.searchParams.set('t', String(timestamp))
|
|
386
|
+
return url.pathname + url.search
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function reloadPage() {
|
|
390
|
+
if (pageReloadTimer) clearTimeout(pageReloadTimer)
|
|
391
|
+
pageReloadTimer = setTimeout(() => {
|
|
392
|
+
console.debug('[remix] HMR reloading page')
|
|
393
|
+
window.location.href = window.location.href
|
|
394
|
+
}, 20)
|
|
395
|
+
}
|
|
396
|
+
`.trimStart()
|
|
397
|
+
}
|
|
@@ -9,6 +9,8 @@ type ResolvedInjectedPackage = {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const injectedPackageNames = ['@oxc-project/runtime'] as const
|
|
12
|
+
const authoredInjectedPackageNames = ['@oxc-project/runtime'] as const
|
|
13
|
+
const generatedInjectedPackageSpecifiers = [] as const
|
|
12
14
|
const injectedPackagesBasePath = '/__@remix/injected'
|
|
13
15
|
|
|
14
16
|
const resolvedInjectedPackages = new Map<string, ResolvedInjectedPackage>()
|
|
@@ -26,24 +28,37 @@ export function isInjectedPackageFilePath(filePath: string): boolean {
|
|
|
26
28
|
return false
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
export function
|
|
30
|
-
|
|
31
|
+
export function getInjectedPackageMountConfigs(): {
|
|
32
|
+
mounts: Record<string, string>
|
|
31
33
|
rootDir: string
|
|
32
34
|
}[] {
|
|
33
35
|
return injectedPackageNames.map((packageName) => {
|
|
34
36
|
let { packageRoot } = getResolvedInjectedPackage(packageName)
|
|
37
|
+
let { fileRoot, routeRoot } = getInjectedPackageRoute(packageRoot, packageName)
|
|
35
38
|
|
|
36
39
|
return {
|
|
37
|
-
|
|
38
|
-
[
|
|
40
|
+
mounts: {
|
|
41
|
+
[getInjectedPackageMountPath(packageName)]: fileRoot,
|
|
39
42
|
},
|
|
40
|
-
rootDir:
|
|
43
|
+
rootDir: routeRoot,
|
|
41
44
|
}
|
|
42
45
|
})
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
export function getInjectedPackageRoots(): readonly string[] {
|
|
49
|
+
return injectedPackageNames.map(
|
|
50
|
+
(packageName) => getResolvedInjectedPackage(packageName).packageRoot,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
export function getInjectedPackageNameForSpecifier(specifier: string): string | null {
|
|
46
|
-
for (let
|
|
55
|
+
for (let injectedSpecifier of generatedInjectedPackageSpecifiers) {
|
|
56
|
+
if (specifier === injectedSpecifier) {
|
|
57
|
+
return getPackageName(injectedSpecifier)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (let packageName of authoredInjectedPackageNames) {
|
|
47
62
|
if (specifier === packageName || specifier.startsWith(`${packageName}/`)) {
|
|
48
63
|
return packageName
|
|
49
64
|
}
|
|
@@ -53,19 +68,22 @@ export function getInjectedPackageNameForSpecifier(specifier: string): string |
|
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
export function mayContainInjectedPackageSpecifier(sourceText: string): boolean {
|
|
56
|
-
return
|
|
71
|
+
return authoredInjectedPackageNames.some((packageName) => sourceText.includes(packageName))
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
export function maskAuthoredInjectedPackageSpecifier(specifier: string): string | null {
|
|
60
|
-
let packageName
|
|
61
|
-
|
|
75
|
+
for (let packageName of authoredInjectedPackageNames) {
|
|
76
|
+
if (specifier !== packageName && !specifier.startsWith(`${packageName}/`)) continue
|
|
77
|
+
|
|
78
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName)
|
|
79
|
+
return `${maskedPackageName}${specifier.slice(packageName.length)}`
|
|
80
|
+
}
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
return `${maskedPackageName}${specifier.slice(packageName.length)}`
|
|
82
|
+
return null
|
|
65
83
|
}
|
|
66
84
|
|
|
67
85
|
export function restoreAuthoredInjectedPackageSpecifier(specifier: string): string | null {
|
|
68
|
-
for (let packageName of
|
|
86
|
+
for (let packageName of authoredInjectedPackageNames) {
|
|
69
87
|
let maskedPackageName = getMaskedInjectedPackageName(packageName)
|
|
70
88
|
if (specifier === maskedPackageName) {
|
|
71
89
|
return packageName
|
|
@@ -82,6 +100,11 @@ function getMaskedInjectedPackageName(packageName: string): string {
|
|
|
82
100
|
return `~${packageName.slice(1)}`
|
|
83
101
|
}
|
|
84
102
|
|
|
103
|
+
function getPackageName(specifier: string): string {
|
|
104
|
+
let parts = specifier.split('/')
|
|
105
|
+
return parts[0]?.startsWith('@') ? `${parts[0]}/${parts[1]}` : (parts[0] ?? specifier)
|
|
106
|
+
}
|
|
107
|
+
|
|
85
108
|
export function getInjectedPackageImporterPath(): string {
|
|
86
109
|
return normalizeFilePath(fileURLToPath(import.meta.url))
|
|
87
110
|
}
|
|
@@ -102,16 +125,29 @@ function getResolvedInjectedPackage(packageName: string): ResolvedInjectedPackag
|
|
|
102
125
|
return resolvedInjectedPackage
|
|
103
126
|
}
|
|
104
127
|
|
|
105
|
-
function
|
|
106
|
-
return `${injectedPackagesBasePath}/${packageName}
|
|
128
|
+
function getInjectedPackageMountPath(packageName: string): string {
|
|
129
|
+
return `${injectedPackagesBasePath}/${packageName}`
|
|
107
130
|
}
|
|
108
131
|
|
|
109
|
-
function
|
|
132
|
+
function getInjectedPackageRoute(
|
|
133
|
+
packageRoot: string,
|
|
134
|
+
packageName: string,
|
|
135
|
+
): { fileRoot: string; routeRoot: string } {
|
|
136
|
+
if (!packageRoot.endsWith(`/${packageName}`)) {
|
|
137
|
+
return {
|
|
138
|
+
fileRoot: packageRoot.slice(getFilePathDirectory(packageRoot).length + 1),
|
|
139
|
+
routeRoot: getFilePathDirectory(packageRoot),
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
110
143
|
let routeRoot = packageRoot
|
|
111
144
|
|
|
112
145
|
for (let _segment of packageName.split('/')) {
|
|
113
146
|
routeRoot = getFilePathDirectory(routeRoot)
|
|
114
147
|
}
|
|
115
148
|
|
|
116
|
-
return
|
|
149
|
+
return {
|
|
150
|
+
fileRoot: packageName,
|
|
151
|
+
routeRoot,
|
|
152
|
+
}
|
|
117
153
|
}
|