@remix-run/assets 0.3.0 → 0.4.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 +206 -8
- package/dist/assets.d.ts +1 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +1 -0
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +7 -1
- package/dist/lib/asset-server.d.ts +29 -6
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +176 -17
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/files/compiler.d.ts +71 -0
- package/dist/lib/files/compiler.d.ts.map +1 -0
- package/dist/lib/files/compiler.js +552 -0
- package/dist/lib/files/config.d.ts +101 -0
- package/dist/lib/files/config.d.ts.map +1 -0
- package/dist/lib/files/config.js +219 -0
- package/dist/lib/files/store.d.ts +31 -0
- package/dist/lib/files/store.d.ts.map +1 -0
- package/dist/lib/files/store.js +63 -0
- package/dist/lib/fingerprint.d.ts +3 -3
- package/dist/lib/fingerprint.d.ts.map +1 -1
- package/dist/lib/fingerprint.js +5 -5
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +28 -20
- package/dist/lib/scripts/compiler.d.ts +1 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +30 -5
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +22 -2
- package/dist/lib/scripts/transform.js +1 -1
- package/dist/lib/styles/compiler.d.ts +4 -0
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +2 -0
- package/dist/lib/styles/emit.d.ts +3 -0
- package/dist/lib/styles/emit.d.ts.map +1 -1
- package/dist/lib/styles/emit.js +36 -1
- package/dist/lib/styles/resolve.d.ts +8 -1
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +85 -32
- package/dist/lib/watch.d.ts +2 -0
- package/dist/lib/watch.d.ts.map +1 -1
- package/dist/lib/watch.js +25 -8
- package/package.json +6 -4
- package/src/assets.ts +1 -0
- package/src/lib/access.ts +8 -1
- package/src/lib/asset-server.ts +273 -28
- package/src/lib/compilation-error.ts +9 -0
- package/src/lib/files/compiler.ts +885 -0
- package/src/lib/files/config.ts +479 -0
- package/src/lib/files/store.ts +109 -0
- package/src/lib/fingerprint.ts +8 -7
- package/src/lib/routes.ts +36 -33
- package/src/lib/scripts/compiler.ts +43 -5
- package/src/lib/scripts/resolve.ts +35 -3
- package/src/lib/scripts/transform.ts +1 -1
- package/src/lib/styles/compiler.ts +9 -0
- package/src/lib/styles/emit.ts +75 -1
- package/src/lib/styles/resolve.ts +132 -35
- package/src/lib/watch.ts +34 -12
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import * as path from 'node:path'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
createAssetServerCompilationError,
|
|
@@ -22,10 +23,17 @@ type ResolvedDependency =
|
|
|
22
23
|
}
|
|
23
24
|
| {
|
|
24
25
|
depPath: string
|
|
25
|
-
kind: '
|
|
26
|
+
kind: 'style'
|
|
26
27
|
placeholder: string
|
|
27
28
|
suffix: string
|
|
28
29
|
}
|
|
30
|
+
| {
|
|
31
|
+
depPath: string
|
|
32
|
+
kind: 'file'
|
|
33
|
+
placeholder: string
|
|
34
|
+
requestTransform: readonly string[] | null
|
|
35
|
+
suffix: string
|
|
36
|
+
}
|
|
29
37
|
|
|
30
38
|
export type ResolvedStyle = {
|
|
31
39
|
dependencies: ResolvedDependency[]
|
|
@@ -41,6 +49,7 @@ export type ResolvedStyle = {
|
|
|
41
49
|
|
|
42
50
|
export type ResolveArgs = {
|
|
43
51
|
isAllowed(absolutePath: string): boolean
|
|
52
|
+
isServedFilePath(filePath: string): boolean
|
|
44
53
|
isWatchIgnored(filePath: string): boolean
|
|
45
54
|
routes: CompiledRoutes
|
|
46
55
|
}
|
|
@@ -58,6 +67,12 @@ type ResolveResult = {
|
|
|
58
67
|
}
|
|
59
68
|
)
|
|
60
69
|
|
|
70
|
+
type ParsedRelativeUrl = {
|
|
71
|
+
hash: string
|
|
72
|
+
resolvedPath: string
|
|
73
|
+
search: string
|
|
74
|
+
}
|
|
75
|
+
|
|
61
76
|
export async function resolveStyle(
|
|
62
77
|
record: StyleRecord,
|
|
63
78
|
transformed: TransformedStyle,
|
|
@@ -71,7 +86,7 @@ export async function resolveStyle(
|
|
|
71
86
|
let trackedFile =
|
|
72
87
|
unresolved.type === 'import'
|
|
73
88
|
? getTrackedImportFilePath(unresolved.url, transformed.resolvedPath)
|
|
74
|
-
:
|
|
89
|
+
: getTrackedUrlFilePath(unresolved.url, transformed.resolvedPath)
|
|
75
90
|
if (trackedFile && !args.isWatchIgnored(trackedFile)) {
|
|
76
91
|
trackedFiles.add(trackedFile)
|
|
77
92
|
}
|
|
@@ -85,14 +100,22 @@ export async function resolveStyle(
|
|
|
85
100
|
unresolved.placeholder,
|
|
86
101
|
args,
|
|
87
102
|
)
|
|
88
|
-
: resolveUrlDependency(
|
|
103
|
+
: resolveUrlDependency(
|
|
104
|
+
unresolved.url,
|
|
105
|
+
transformed.resolvedPath,
|
|
106
|
+
unresolved.placeholder,
|
|
107
|
+
args,
|
|
108
|
+
)
|
|
89
109
|
|
|
90
110
|
dependencies.push(resolved)
|
|
91
111
|
|
|
92
|
-
if (resolved.kind === '
|
|
112
|
+
if (resolved.kind === 'style' || resolved.kind === 'file') {
|
|
93
113
|
if (!args.isWatchIgnored(resolved.depPath)) {
|
|
94
114
|
trackedFiles.add(resolved.depPath)
|
|
95
115
|
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (resolved.kind === 'style') {
|
|
96
119
|
deps.add(resolved.depPath)
|
|
97
120
|
}
|
|
98
121
|
} catch (error) {
|
|
@@ -172,16 +195,8 @@ function resolveImportDependency(
|
|
|
172
195
|
}
|
|
173
196
|
}
|
|
174
197
|
|
|
175
|
-
let
|
|
176
|
-
if (
|
|
177
|
-
return {
|
|
178
|
-
kind: 'external',
|
|
179
|
-
placeholder,
|
|
180
|
-
replacement: url,
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (pathname.startsWith('/')) {
|
|
198
|
+
let parsedUrl = parseRelativeUrl(url, importerPath)
|
|
199
|
+
if (parsedUrl === null) {
|
|
185
200
|
return {
|
|
186
201
|
kind: 'external',
|
|
187
202
|
placeholder,
|
|
@@ -189,7 +204,8 @@ function resolveImportDependency(
|
|
|
189
204
|
}
|
|
190
205
|
}
|
|
191
206
|
|
|
192
|
-
let
|
|
207
|
+
let { hash, resolvedPath, search } = parsedUrl
|
|
208
|
+
let resolvedFilePath = resolvedPath
|
|
193
209
|
let identityPath = resolveExistingFilePath(resolvedFilePath)
|
|
194
210
|
if (!identityPath || !isStyleFilePath(identityPath)) {
|
|
195
211
|
throw createAssetServerCompilationError(
|
|
@@ -222,17 +238,82 @@ function resolveImportDependency(
|
|
|
222
238
|
|
|
223
239
|
return {
|
|
224
240
|
depPath: identityPath,
|
|
225
|
-
kind: '
|
|
241
|
+
kind: 'style',
|
|
226
242
|
placeholder,
|
|
227
|
-
suffix
|
|
243
|
+
suffix: `${search}${hash}`,
|
|
228
244
|
}
|
|
229
245
|
}
|
|
230
246
|
|
|
231
|
-
function resolveUrlDependency(
|
|
247
|
+
function resolveUrlDependency(
|
|
248
|
+
url: string,
|
|
249
|
+
importerPath: string,
|
|
250
|
+
placeholder: string,
|
|
251
|
+
args: ResolveArgs,
|
|
252
|
+
): ResolvedDependency {
|
|
253
|
+
if (isExternalUrl(url)) {
|
|
254
|
+
return {
|
|
255
|
+
kind: 'external',
|
|
256
|
+
placeholder,
|
|
257
|
+
replacement: url,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let parsedUrl = parseRelativeUrl(url, importerPath)
|
|
262
|
+
if (parsedUrl === null) {
|
|
263
|
+
return {
|
|
264
|
+
kind: 'external',
|
|
265
|
+
placeholder,
|
|
266
|
+
replacement: url,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let { hash, resolvedPath, search } = parsedUrl
|
|
271
|
+
let resolvedFilePath = resolvedPath
|
|
272
|
+
let identityPath = resolveExistingFilePath(resolvedFilePath)
|
|
273
|
+
if (!identityPath) {
|
|
274
|
+
throw createAssetServerCompilationError(`Failed to resolve url("${url}") in ${importerPath}.`, {
|
|
275
|
+
code: 'URL_RESOLUTION_FAILED',
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (!args.isServedFilePath(identityPath)) {
|
|
280
|
+
throw createAssetServerCompilationError(
|
|
281
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is not a supported file asset. ` +
|
|
282
|
+
`Add the file extension to files.extensions to serve this asset.`,
|
|
283
|
+
{
|
|
284
|
+
code: 'URL_NOT_SUPPORTED',
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!args.isAllowed(identityPath)) {
|
|
290
|
+
throw createAssetServerCompilationError(
|
|
291
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server allow/deny configuration. ` +
|
|
292
|
+
`Add a matching allow rule for this file path or remove a conflicting deny rule for this file path.`,
|
|
293
|
+
{
|
|
294
|
+
code: 'URL_NOT_ALLOWED',
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!args.routes.toUrlPathname(identityPath)) {
|
|
300
|
+
throw createAssetServerCompilationError(
|
|
301
|
+
`URL "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured fileMap entries. ` +
|
|
302
|
+
`Add a matching fileMap entry for this file path.`,
|
|
303
|
+
{
|
|
304
|
+
code: 'URL_OUTSIDE_FILE_MAP',
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let parsedRequest = parseResolvedFileRequest({ hash, search })
|
|
310
|
+
|
|
232
311
|
return {
|
|
233
|
-
|
|
312
|
+
depPath: identityPath,
|
|
313
|
+
kind: 'file',
|
|
234
314
|
placeholder,
|
|
235
|
-
|
|
315
|
+
requestTransform: parsedRequest.transform,
|
|
316
|
+
suffix: parsedRequest.suffix,
|
|
236
317
|
}
|
|
237
318
|
}
|
|
238
319
|
|
|
@@ -245,34 +326,50 @@ function resolveExistingFilePath(filePath: string): string | null {
|
|
|
245
326
|
}
|
|
246
327
|
}
|
|
247
328
|
|
|
248
|
-
function
|
|
249
|
-
|
|
329
|
+
function parseRelativeUrl(url: string, importerPath: string): ParsedRelativeUrl | null {
|
|
330
|
+
if (url.length === 0 || url.startsWith('?') || url.startsWith('/') || isExternalUrl(url)) {
|
|
331
|
+
return null
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let parsed = new URL(url, pathToFileURL(importerPath))
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
hash: parsed.hash,
|
|
338
|
+
resolvedPath: normalizeFilePath(fileURLToPath(parsed)),
|
|
339
|
+
search: parsed.search,
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function parseResolvedFileRequest(url: Pick<ParsedRelativeUrl, 'hash' | 'search'>): {
|
|
250
344
|
suffix: string
|
|
345
|
+
transform: readonly string[] | null
|
|
251
346
|
} {
|
|
252
|
-
let
|
|
253
|
-
let hashIndex = url.indexOf('#')
|
|
254
|
-
let endIndex = [queryIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0]
|
|
347
|
+
let { hash, search } = url
|
|
255
348
|
|
|
256
|
-
if (
|
|
349
|
+
if (search.length === 0) {
|
|
257
350
|
return {
|
|
258
|
-
|
|
259
|
-
|
|
351
|
+
suffix: hash,
|
|
352
|
+
transform: null,
|
|
260
353
|
}
|
|
261
354
|
}
|
|
262
355
|
|
|
356
|
+
let searchParams = new URLSearchParams(search.slice(1))
|
|
357
|
+
let transform = searchParams.getAll('transform')
|
|
358
|
+
searchParams.delete('transform')
|
|
359
|
+
let remainingSearch = searchParams.toString()
|
|
360
|
+
|
|
263
361
|
return {
|
|
264
|
-
|
|
265
|
-
|
|
362
|
+
suffix: `${remainingSearch.length > 0 ? `?${remainingSearch}` : ''}${hash}`,
|
|
363
|
+
transform: transform.length > 0 ? transform : null,
|
|
266
364
|
}
|
|
267
365
|
}
|
|
268
366
|
|
|
269
367
|
function getTrackedImportFilePath(specifier: string, importerPath: string): string | null {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return normalizeFilePath(path.resolve(path.dirname(importerPath), pathname))
|
|
273
|
-
}
|
|
368
|
+
return parseRelativeUrl(specifier, importerPath)?.resolvedPath ?? null
|
|
369
|
+
}
|
|
274
370
|
|
|
275
|
-
|
|
371
|
+
function getTrackedUrlFilePath(url: string, importerPath: string): string | null {
|
|
372
|
+
return parseRelativeUrl(url, importerPath)?.resolvedPath ?? null
|
|
276
373
|
}
|
|
277
374
|
|
|
278
375
|
function isStyleFilePath(filePath: string): boolean {
|
package/src/lib/watch.ts
CHANGED
|
@@ -17,7 +17,10 @@ export type ChokidarWatcher = ReturnType<typeof chokidar.watch>
|
|
|
17
17
|
export type AssetServerWatcher = {
|
|
18
18
|
close(): Promise<void>
|
|
19
19
|
getWatchedTargets(): readonly string[]
|
|
20
|
-
updateWatchedDirectories(
|
|
20
|
+
updateWatchedDirectories(
|
|
21
|
+
delta: { add: readonly string[]; remove: readonly string[] },
|
|
22
|
+
options?: { includeAncestors?: boolean },
|
|
23
|
+
): void
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export function createAssetServerWatcher(options: AssetServerWatcherOptions): AssetServerWatcher {
|
|
@@ -27,7 +30,8 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
27
30
|
...resolveChokidarWatchOptions(options),
|
|
28
31
|
})
|
|
29
32
|
options.onChokidarWatcherCreated?.(watcher)
|
|
30
|
-
let
|
|
33
|
+
let watchedResolutionDirectories = new Set<string>()
|
|
34
|
+
let watchedFileDirectories = new Set<string>()
|
|
31
35
|
let watchedTargets = new Set<string>()
|
|
32
36
|
|
|
33
37
|
for (let event of ['add', 'change', 'unlink'] as const) {
|
|
@@ -46,8 +50,12 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
46
50
|
getWatchedTargets() {
|
|
47
51
|
return [...watchedTargets]
|
|
48
52
|
},
|
|
49
|
-
updateWatchedDirectories(delta) {
|
|
50
|
-
let
|
|
53
|
+
updateWatchedDirectories(delta, updateOptions = {}) {
|
|
54
|
+
let includeAncestors = updateOptions.includeAncestors ?? true
|
|
55
|
+
let previousDirectories = includeAncestors
|
|
56
|
+
? watchedResolutionDirectories
|
|
57
|
+
: watchedFileDirectories
|
|
58
|
+
let nextWatchedDirectories = new Set(previousDirectories)
|
|
51
59
|
|
|
52
60
|
for (let directory of delta.add) {
|
|
53
61
|
nextWatchedDirectories.add(directory)
|
|
@@ -56,7 +64,17 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
56
64
|
nextWatchedDirectories.delete(directory)
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
|
|
67
|
+
if (includeAncestors) {
|
|
68
|
+
watchedResolutionDirectories = nextWatchedDirectories
|
|
69
|
+
} else {
|
|
70
|
+
watchedFileDirectories = nextWatchedDirectories
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let nextTargets = getWatchTargets({
|
|
74
|
+
rootDir: options.rootDir,
|
|
75
|
+
fileDirectories: [...watchedFileDirectories],
|
|
76
|
+
resolutionDirectories: [...watchedResolutionDirectories],
|
|
77
|
+
})
|
|
60
78
|
let targetsToAdd = [...nextTargets].filter((target) => !watchedTargets.has(target))
|
|
61
79
|
let targetsToRemove = [...watchedTargets].filter((target) => !nextTargets.has(target))
|
|
62
80
|
|
|
@@ -67,7 +85,6 @@ export function createAssetServerWatcher(options: AssetServerWatcherOptions): As
|
|
|
67
85
|
watcher.add(targetsToAdd)
|
|
68
86
|
}
|
|
69
87
|
|
|
70
|
-
watchedDirectories = nextWatchedDirectories
|
|
71
88
|
watchedTargets = nextTargets
|
|
72
89
|
},
|
|
73
90
|
}
|
|
@@ -88,15 +105,20 @@ function resolveChokidarWatchOptions(
|
|
|
88
105
|
}
|
|
89
106
|
}
|
|
90
107
|
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
function getWatchTargets(options: {
|
|
109
|
+
fileDirectories: readonly string[]
|
|
110
|
+
resolutionDirectories: readonly string[]
|
|
111
|
+
rootDir: string
|
|
112
|
+
}): Set<string> {
|
|
113
|
+
let normalizedRootDir = normalizeFilePath(options.rootDir)
|
|
96
114
|
let targets = new Set<string>()
|
|
97
115
|
let configAncestors = new Set<string>()
|
|
98
116
|
|
|
99
|
-
for (let directory of
|
|
117
|
+
for (let directory of options.fileDirectories) {
|
|
118
|
+
targets.add(normalizeFilePath(directory).replace(/\/+$/, ''))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (let directory of options.resolutionDirectories) {
|
|
100
122
|
let normalizedDirectory = normalizeFilePath(directory).replace(/\/+$/, '')
|
|
101
123
|
|
|
102
124
|
targets.add(normalizedDirectory)
|